bemenu/lib/menu.c

540 lines
14 KiB
C
Raw Normal View History

#include "internal.h"
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
/**
* Filter function map.
*/
static struct bm_item** (*filter_func[BM_FILTER_MODE_LAST])(struct bm_menu *menu, bool addition, uint32_t *out_nmemb) = {
bm_filter_dmenu, /* BM_FILTER_DMENU */
bm_filter_dmenu_case_insensitive /* BM_FILTER_DMENU_CASE_INSENSITIVE */
};
bool
bm_menu_item_is_selected(const struct bm_menu *menu, const struct bm_item *item)
{
assert(menu);
assert(item);
uint32_t i, count;
struct bm_item **items = bm_menu_get_selected_items(menu, &count);
for (i = 0; i < count && items[i] != item; ++i);
return (i < count);
}
struct bm_menu*
bm_menu_new(const char *renderer, enum bm_prioritory prioritory)
{
struct bm_menu *menu;
if (!(menu = calloc(1, sizeof(struct bm_menu))))
return NULL;
uint32_t count;
const struct bm_renderer **renderers = bm_get_renderers(&count);
for (uint32_t i = 0; i < count; ++i) {
if (prioritory != BM_PRIO_ANY && renderers[i]->api.prioritory != prioritory)
continue;
if (renderer && strcmp(renderer, renderers[i]->name))
continue;
if (renderers[i]->api.prioritory == BM_PRIO_TERMINAL) {
const char *term = getenv("TERM");
if (!term || !strlen(term))
continue;
}
if (bm_renderer_activate((struct bm_renderer*)renderers[i], menu))
break;
}
if (!menu->renderer) {
bm_menu_free(menu);
return NULL;
}
return menu;
}
void
bm_menu_free(struct bm_menu *menu)
{
2014-04-09 22:41:32 +00:00
assert(menu);
if (menu->renderer && menu->renderer->api.destructor)
menu->renderer->api.destructor(menu);
free(menu->title);
free(menu->filter);
free(menu->old_filter);
2014-04-12 17:52:29 +00:00
bm_menu_free_items(menu);
free(menu);
}
void
bm_menu_free_items(struct bm_menu *menu)
{
2014-04-09 22:41:32 +00:00
assert(menu);
list_free_list(&menu->selection);
list_free_list(&menu->filtered);
list_free_items(&menu->items, (list_free_fun)bm_item_free);
}
void
bm_menu_set_userdata(struct bm_menu *menu, void *userdata)
{
assert(menu);
menu->userdata = userdata;
}
void*
bm_menu_get_userdata(struct bm_menu *menu)
{
assert(menu);
return menu->userdata;
}
void
bm_menu_set_filter(struct bm_menu *menu, const char *filter)
{
assert(menu);
free(menu->filter);
menu->filter = (filter ? bm_strdup(filter) : NULL);
menu->filter_size = (filter ? strlen(filter) : 0);
}
const char*
bm_menu_get_filter(struct bm_menu *menu)
{
assert(menu);
return menu->filter;
}
void
bm_menu_set_filter_mode(struct bm_menu *menu, enum bm_filter_mode mode)
{
2014-04-09 22:41:32 +00:00
assert(menu);
menu->filter_mode = (mode >= BM_FILTER_MODE_LAST ? BM_FILTER_MODE_DMENU : mode);
}
enum bm_filter_mode
bm_menu_get_filter_mode(const struct bm_menu *menu)
{
2014-04-09 22:41:32 +00:00
assert(menu);
return menu->filter_mode;
}
void
bm_menu_set_wrap(struct bm_menu *menu, bool wrap)
2014-04-14 16:25:16 +00:00
{
assert(menu);
menu->wrap = wrap;
2014-04-14 16:25:16 +00:00
}
bool
bm_menu_get_wrap(const struct bm_menu *menu)
2014-04-14 16:25:16 +00:00
{
assert(menu);
return menu->wrap;
}
bool
bm_menu_set_title(struct bm_menu *menu, const char *title)
{
2014-04-09 22:41:32 +00:00
assert(menu);
char *copy = NULL;
if (title && !(copy = bm_strdup(title)))
return false;
free(menu->title);
menu->title = copy;
return true;
}
const char*
bm_menu_get_title(const struct bm_menu *menu)
{
2014-04-09 22:41:32 +00:00
assert(menu);
return menu->title;
}
bool
bm_menu_add_items_at(struct bm_menu *menu, struct bm_item *item, uint32_t index)
{
2014-04-09 22:41:32 +00:00
assert(menu);
return list_add_item_at(&menu->items, item, index);
}
bool
bm_menu_add_item(struct bm_menu *menu, struct bm_item *item)
{
return list_add_item(&menu->items, item);
}
bool
bm_menu_remove_item_at(struct bm_menu *menu, uint32_t index)
{
2014-04-09 22:41:32 +00:00
assert(menu);
if (!menu->items.items || menu->items.count <= index)
return 0;
bool ret = list_remove_item_at(&menu->items, index);
if (ret) {
struct bm_item *item = ((struct bm_item**)menu->items.items)[index];
list_remove_item(&menu->selection, item);
list_remove_item(&menu->filtered, item);
}
return ret;
}
bool
bm_menu_remove_item(struct bm_menu *menu, struct bm_item *item)
{
2014-04-09 22:41:32 +00:00
assert(menu);
bool ret = list_remove_item(&menu->items, item);
if (ret) {
list_remove_item(&menu->selection, item);
list_remove_item(&menu->filtered, item);
}
return ret;
}
bool
bm_menu_set_highlighted_index(struct bm_menu *menu, uint32_t index)
{
assert(menu);
2014-04-10 19:02:47 +00:00
uint32_t count;
bm_menu_get_filtered_items(menu, &count);
if (count <= index)
return 0;
return (menu->index = index);
}
bool
bm_menu_set_highlighted_item(struct bm_menu *menu, struct bm_item *item)
{
assert(menu);
uint32_t i, count;
struct bm_item **items = bm_menu_get_filtered_items(menu, &count);
for (i = 0; i < count && items[i] != item; ++i);
if (count <= i)
return 0;
return (menu->index = i);
}
struct bm_item*
bm_menu_get_highlighted_item(const struct bm_menu *menu)
{
2014-04-09 22:41:32 +00:00
assert(menu);
2014-04-10 17:11:41 +00:00
uint32_t count;
struct bm_item **items = bm_menu_get_filtered_items(menu, &count);
2014-04-10 17:11:41 +00:00
if (!items || count <= menu->index)
return NULL;
return items[menu->index];
}
bool
bm_menu_set_selected_items(struct bm_menu *menu, struct bm_item **items, uint32_t nmemb)
{
assert(menu);
2014-04-12 17:56:00 +00:00
struct bm_item **new_items;
if (!(new_items = calloc(sizeof(struct bm_item*), nmemb)))
2014-04-12 17:56:00 +00:00
return 0;
memcpy(new_items, items, sizeof(struct bm_item*) * nmemb);
return list_set_items_no_copy(&menu->selection, new_items, nmemb);
}
struct bm_item**
bm_menu_get_selected_items(const struct bm_menu *menu, uint32_t *out_nmemb)
{
2014-04-09 22:41:32 +00:00
assert(menu);
return list_get_items(&menu->selection, out_nmemb);
2014-04-10 17:11:41 +00:00
}
bool
bm_menu_set_items(struct bm_menu *menu, const struct bm_item **items, uint32_t nmemb)
2014-04-10 17:11:41 +00:00
{
assert(menu);
bool ret = list_set_items(&menu->items, items, nmemb, (list_free_fun)bm_item_free);
2014-04-10 17:11:41 +00:00
if (ret) {
list_free_list(&menu->selection);
list_free_list(&menu->filtered);
2014-04-10 17:11:41 +00:00
}
return ret;
}
struct bm_item**
bm_menu_get_items(const struct bm_menu *menu, uint32_t *out_nmemb)
{
assert(menu);
return list_get_items(&menu->items, out_nmemb);
}
struct bm_item**
bm_menu_get_filtered_items(const struct bm_menu *menu, uint32_t *out_nmemb)
{
2014-04-09 22:41:32 +00:00
assert(menu);
2014-04-12 17:52:29 +00:00
if (menu->filter && strlen(menu->filter))
return list_get_items(&menu->filtered, out_nmemb);
return list_get_items(&menu->items, out_nmemb);
}
void
bm_menu_render(const struct bm_menu *menu)
{
2014-04-09 22:41:32 +00:00
assert(menu);
if (menu->renderer->api.render)
menu->renderer->api.render(menu);
}
void
bm_menu_filter(struct bm_menu *menu)
{
assert(menu);
char addition = 0;
2014-04-12 17:52:29 +00:00
size_t len = (menu->filter ? strlen(menu->filter) : 0);
if (!len || !menu->items.items || menu->items.count <= 0) {
list_free_list(&menu->filtered);
free(menu->old_filter);
menu->old_filter = NULL;
return;
}
if (menu->old_filter) {
size_t oldLen = strlen(menu->old_filter);
addition = (oldLen < len && !memcmp(menu->old_filter, menu->filter, oldLen));
}
if (menu->old_filter && addition && menu->filtered.count <= 0)
return;
if (menu->old_filter && !strcmp(menu->filter, menu->old_filter))
return;
uint32_t count;
struct bm_item **filtered = filter_func[menu->filter_mode](menu, addition, &count);
list_set_items_no_copy(&menu->filtered, filtered, count);
menu->index = 0;
free(menu->old_filter);
menu->old_filter = bm_strdup(menu->filter);
}
enum bm_key
bm_menu_poll_key(struct bm_menu *menu, uint32_t *out_unicode)
{
assert(menu && out_unicode);
*out_unicode = 0;
enum bm_key key = BM_KEY_NONE;
if (menu->renderer->api.poll_key)
key = menu->renderer->api.poll_key(menu, out_unicode);
return key;
}
enum bm_run_result
bm_menu_run_with_key(struct bm_menu *menu, enum bm_key key, uint32_t unicode)
{
2014-04-09 22:41:32 +00:00
assert(menu);
2014-04-10 19:02:47 +00:00
uint32_t count;
bm_menu_get_filtered_items(menu, &count);
2014-04-10 19:02:47 +00:00
uint32_t displayed = 0;
if (menu->renderer->api.get_displayed_count)
displayed = menu->renderer->api.get_displayed_count(menu);
if (!displayed)
displayed = count;
switch (key) {
case BM_KEY_LEFT:
if (menu->filter) {
uint32_t oldCursor = menu->cursor;
menu->cursor -= bm_utf8_rune_prev(menu->filter, menu->cursor);
menu->curses_cursor -= bm_utf8_rune_width(menu->filter + menu->cursor, oldCursor - menu->cursor);
}
break;
case BM_KEY_RIGHT:
if (menu->filter) {
uint32_t oldCursor = menu->cursor;
menu->cursor += bm_utf8_rune_next(menu->filter, menu->cursor);
menu->curses_cursor += bm_utf8_rune_width(menu->filter + oldCursor, menu->cursor - oldCursor);
}
break;
case BM_KEY_HOME:
menu->curses_cursor = menu->cursor = 0;
break;
case BM_KEY_END:
2014-04-12 17:52:29 +00:00
menu->cursor = (menu->filter ? strlen(menu->filter) : 0);
menu->curses_cursor = (menu->filter ? bm_utf8_string_screen_width(menu->filter) : 0);
break;
case BM_KEY_UP:
2014-04-14 16:25:16 +00:00
if (menu->index > 0) {
menu->index--;
2014-04-14 16:25:16 +00:00
} else if (menu->wrap) {
menu->index = count - 1;
2014-04-14 16:25:16 +00:00
}
break;
case BM_KEY_DOWN:
if (menu->index < count - 1) {
menu->index++;
2014-04-14 16:25:16 +00:00
} else if (menu->wrap) {
menu->index = 0;
}
break;
case BM_KEY_PAGE_UP:
menu->index = (menu->index < displayed ? 0 : menu->index - (displayed - 1));
break;
case BM_KEY_PAGE_DOWN:
menu->index = (menu->index + displayed >= count ? count - 1 : menu->index + (displayed - 1));
break;
case BM_KEY_SHIFT_PAGE_UP:
menu->index = 0;
break;
case BM_KEY_SHIFT_PAGE_DOWN:
menu->index = count - 1;
break;
case BM_KEY_BACKSPACE:
if (menu->filter) {
size_t width;
menu->cursor -= bm_utf8_rune_remove(menu->filter, menu->cursor, &width);
menu->curses_cursor -= width;
}
break;
case BM_KEY_DELETE:
if (menu->filter)
bm_utf8_rune_remove(menu->filter, menu->cursor + 1, NULL);
break;
case BM_KEY_LINE_DELETE_LEFT:
if (menu->filter) {
while (menu->cursor > 0) {
size_t width;
menu->cursor -= bm_utf8_rune_remove(menu->filter, menu->cursor, &width);
menu->curses_cursor -= width;
}
}
break;
case BM_KEY_LINE_DELETE_RIGHT:
if (menu->filter)
menu->filter[menu->cursor] = 0;
break;
case BM_KEY_WORD_DELETE:
if (menu->filter) {
while (menu->cursor < strlen(menu->filter) && !isspace(menu->filter[menu->cursor])) {
uint32_t oldCursor = menu->cursor;
menu->cursor += bm_utf8_rune_next(menu->filter, menu->cursor);
menu->curses_cursor += bm_utf8_rune_width(menu->filter + oldCursor, menu->cursor - oldCursor);
}
while (menu->cursor > 0 && isspace(menu->filter[menu->cursor - 1])) {
uint32_t oldCursor = menu->cursor;
menu->cursor -= bm_utf8_rune_prev(menu->filter, menu->cursor);
menu->curses_cursor -= bm_utf8_rune_width(menu->filter + menu->cursor, oldCursor - menu->cursor);
}
while (menu->cursor > 0 && !isspace(menu->filter[menu->cursor - 1])) {
size_t width;
menu->cursor -= bm_utf8_rune_remove(menu->filter, menu->cursor, &width);
menu->curses_cursor -= width;
}
}
break;
case BM_KEY_UNICODE:
{
size_t width;
menu->cursor += bm_unicode_insert(&menu->filter, &menu->filter_size, menu->cursor, unicode, &width);
menu->curses_cursor += width;
}
break;
case BM_KEY_TAB:
{
2014-04-12 17:52:29 +00:00
const char *text;
struct bm_item *highlighted = bm_menu_get_highlighted_item(menu);
if (highlighted && (text = bm_item_get_text(highlighted))) {
bm_menu_set_filter(menu, text);
2014-04-12 17:52:29 +00:00
menu->cursor = (menu->filter ? strlen(menu->filter) : 0);
menu->curses_cursor = (menu->filter ? bm_utf8_string_screen_width(menu->filter) : 0);
}
}
break;
case BM_KEY_CONTROL_RETURN:
case BM_KEY_RETURN:
{
struct bm_item *highlighted = bm_menu_get_highlighted_item(menu);
if (highlighted && !bm_menu_item_is_selected(menu, highlighted))
list_add_item(&menu->selection, highlighted);
}
break;
case BM_KEY_SHIFT_RETURN:
case BM_KEY_ESCAPE:
list_free_list(&menu->selection);
break;
default: break;
}
bm_menu_filter(menu);
2014-04-09 22:26:00 +00:00
switch (key) {
case BM_KEY_SHIFT_RETURN:
2014-04-09 22:26:00 +00:00
case BM_KEY_RETURN: return BM_RUN_RESULT_SELECTED;
case BM_KEY_ESCAPE: return BM_RUN_RESULT_CANCEL;
default: break;
}
return BM_RUN_RESULT_RUNNING;
}
/* vim: set ts=8 sw=4 tw=0 :*/