mirror of
https://github.com/Cloudef/bemenu
synced 2024-06-03 05:16:23 +02:00
93cde4831b
Text is displayed vertically centered in a line. If unspecified, or 0, the previous behaviour of making the height the size of the text, plus two pixels on either side, is used, so there will be no change in behaviour if this option is not used. Fixes https://github.com/Cloudef/bemenu/issues/44.
796 lines
19 KiB
C
796 lines
19 KiB
C
#include "internal.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
|
|
/**
|
|
* Default font.
|
|
*/
|
|
static const char *default_font = "Fixed 9";
|
|
|
|
/**
|
|
* Default hexadecimal colors.
|
|
*/
|
|
static const char *default_colors[BM_COLOR_LAST] = {
|
|
"#121212", // BM_COLOR_TITLE_BG
|
|
"#D81860", // BM_COLOR_TITLE_FG
|
|
"#121212", // BM_COLOR_FILTER_BG
|
|
"#CACACA", // BM_COLOR_FILTER_FG
|
|
"#121212", // BM_COLOR_ITEM_BG
|
|
"#CACACA", // BM_COLOR_ITEM_FG
|
|
"#121212", // BM_COLOR_HIGHLIGHTED_BG
|
|
"#D81860", // BM_COLOR_HIGHLIGHTED_FG
|
|
"#121212", // BM_COLOR_SELECTED_BG
|
|
"#D81860", // BM_COLOR_SELECTED_FG
|
|
"#121212", // BM_COLOR_SCROLLBAR_BG
|
|
"#D81860", // BM_COLOR_SCROLLBAR_FG
|
|
};
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
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);
|
|
|
|
const char *name = secure_getenv("BEMENU_BACKEND");
|
|
name = (name && strlen(name) > 0 ? name : NULL);
|
|
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (!name && !renderer && renderers[i]->api.priorty != BM_PRIO_GUI)
|
|
continue;
|
|
|
|
if ((renderer && strcmp(renderer, renderers[i]->name)) || (name && strcmp(name, renderers[i]->name)))
|
|
continue;
|
|
|
|
if (renderers[i]->api.priorty == BM_PRIO_TERMINAL) {
|
|
/**
|
|
* Some sanity checks that we are in terminal.
|
|
* These however are not reliable, thus we don't auto-detect terminal based renderers.
|
|
* These will be only used when explicitly requested.
|
|
*
|
|
* Launching terminal based menu instance at background is not a good idea.
|
|
*/
|
|
const char *term = getenv("TERM");
|
|
if (!term || !strlen(term) || getppid() == 1)
|
|
continue;
|
|
}
|
|
|
|
if (bm_renderer_activate((struct bm_renderer*)renderers[i], menu))
|
|
break;
|
|
}
|
|
|
|
if (!menu->renderer)
|
|
goto fail;
|
|
|
|
if (!bm_menu_set_font(menu, NULL))
|
|
goto fail;
|
|
|
|
for (uint32_t i = 0; i < BM_COLOR_LAST; ++i) {
|
|
if (!bm_menu_set_color(menu, i, NULL))
|
|
goto fail;
|
|
}
|
|
|
|
return menu;
|
|
|
|
fail:
|
|
bm_menu_free(menu);
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
bm_menu_free(struct bm_menu *menu)
|
|
{
|
|
assert(menu);
|
|
|
|
if (menu->renderer && menu->renderer->api.destructor)
|
|
menu->renderer->api.destructor(menu);
|
|
|
|
free(menu->title);
|
|
free(menu->filter);
|
|
free(menu->old_filter);
|
|
|
|
free(menu->font.name);
|
|
|
|
for (uint32_t i = 0; i < BM_COLOR_LAST; ++i)
|
|
free(menu->colors[i].hex);
|
|
|
|
bm_menu_free_items(menu);
|
|
free(menu);
|
|
}
|
|
|
|
void
|
|
bm_menu_free_items(struct bm_menu *menu)
|
|
{
|
|
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_prefix(struct bm_menu *menu, const char *prefix)
|
|
{
|
|
assert(menu);
|
|
free(menu->prefix);
|
|
menu->prefix = (prefix && strlen(prefix) > 0 ? bm_strdup(prefix) : NULL);
|
|
}
|
|
|
|
const char*
|
|
bm_menu_get_prefix(struct bm_menu *menu)
|
|
{
|
|
assert(menu);
|
|
return menu->prefix;
|
|
}
|
|
|
|
void
|
|
bm_menu_set_filter(struct bm_menu *menu, const char *filter)
|
|
{
|
|
assert(menu);
|
|
|
|
free(menu->filter);
|
|
menu->filter = (filter && strlen(filter) > 0 ? 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)
|
|
{
|
|
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)
|
|
{
|
|
assert(menu);
|
|
return menu->filter_mode;
|
|
}
|
|
|
|
void
|
|
bm_menu_set_lines(struct bm_menu *menu, uint32_t lines)
|
|
{
|
|
assert(menu);
|
|
menu->lines = lines;
|
|
}
|
|
|
|
uint32_t
|
|
bm_menu_get_lines(struct bm_menu *menu)
|
|
{
|
|
assert(menu);
|
|
return menu->lines;
|
|
}
|
|
|
|
void
|
|
bm_menu_set_wrap(struct bm_menu *menu, bool wrap)
|
|
{
|
|
assert(menu);
|
|
menu->wrap = wrap;
|
|
}
|
|
|
|
bool
|
|
bm_menu_get_wrap(const struct bm_menu *menu)
|
|
{
|
|
assert(menu);
|
|
return menu->wrap;
|
|
}
|
|
|
|
bool
|
|
bm_menu_set_title(struct bm_menu *menu, const char *title)
|
|
{
|
|
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)
|
|
{
|
|
assert(menu);
|
|
return menu->title;
|
|
}
|
|
|
|
bool
|
|
bm_menu_set_font(struct bm_menu *menu, const char *font)
|
|
{
|
|
assert(menu);
|
|
|
|
const char *nfont = (font ? font : default_font);
|
|
|
|
char *copy = NULL;
|
|
if (!(copy = bm_strdup(nfont)))
|
|
return false;
|
|
|
|
free(menu->font.name);
|
|
menu->font.name = copy;
|
|
return true;
|
|
}
|
|
|
|
const char*
|
|
bm_menu_get_font(const struct bm_menu *menu)
|
|
{
|
|
assert(menu);
|
|
return menu->font.name;
|
|
}
|
|
|
|
void
|
|
bm_menu_set_line_height(struct bm_menu *menu, uint32_t line_height)
|
|
{
|
|
assert(menu);
|
|
menu->line_height = line_height;
|
|
}
|
|
|
|
uint32_t
|
|
bm_menu_get_line_height(struct bm_menu *menu)
|
|
{
|
|
assert(menu);
|
|
return menu->line_height;
|
|
}
|
|
|
|
bool
|
|
bm_menu_set_color(struct bm_menu *menu, enum bm_color color, const char *hex)
|
|
{
|
|
assert(menu);
|
|
|
|
const char *nhex = (hex ? hex : default_colors[color]);
|
|
|
|
int32_t r, g, b;
|
|
if (sscanf(nhex,"#%2x%2x%2x", &r, &b, &g) != 3)
|
|
return false;
|
|
|
|
char *copy = NULL;
|
|
if (!(copy = bm_strdup(nhex)))
|
|
return false;
|
|
|
|
free(menu->colors[color].hex);
|
|
menu->colors[color].hex = copy;
|
|
menu->colors[color].r = r;
|
|
menu->colors[color].g = g;
|
|
menu->colors[color].b = b;
|
|
return true;
|
|
}
|
|
|
|
const
|
|
char* bm_menu_get_color(const struct bm_menu *menu, enum bm_color color)
|
|
{
|
|
assert(menu);
|
|
return menu->colors[color].hex;
|
|
}
|
|
|
|
void
|
|
bm_menu_set_scrollbar(struct bm_menu *menu, enum bm_scrollbar_mode mode)
|
|
{
|
|
menu->scrollbar = (mode == BM_SCROLLBAR_LAST ? BM_SCROLLBAR_NONE : mode);
|
|
}
|
|
|
|
enum bm_scrollbar_mode
|
|
bm_menu_get_scrollbar(struct bm_menu *menu)
|
|
{
|
|
return menu->scrollbar;
|
|
}
|
|
|
|
void
|
|
bm_menu_set_bottom(struct bm_menu *menu, bool bottom)
|
|
{
|
|
assert(menu);
|
|
|
|
if (menu->bottom == bottom)
|
|
return;
|
|
|
|
menu->bottom = bottom;
|
|
|
|
if (menu->renderer->api.set_bottom)
|
|
menu->renderer->api.set_bottom(menu, bottom);
|
|
}
|
|
|
|
bool
|
|
bm_menu_get_bottom(struct bm_menu *menu)
|
|
{
|
|
assert(menu);
|
|
return menu->bottom;
|
|
}
|
|
|
|
void
|
|
bm_menu_set_monitor(struct bm_menu *menu, uint32_t monitor)
|
|
{
|
|
assert(menu);
|
|
|
|
if (menu->monitor == monitor)
|
|
return;
|
|
|
|
menu->monitor = monitor;
|
|
|
|
if (menu->renderer->api.set_monitor)
|
|
menu->renderer->api.set_monitor(menu, monitor);
|
|
}
|
|
|
|
uint32_t
|
|
bm_menu_get_monitor(struct bm_menu *menu)
|
|
{
|
|
assert(menu);
|
|
return menu->monitor;
|
|
}
|
|
|
|
void
|
|
bm_menu_grab_keyboard(struct bm_menu *menu, bool grab)
|
|
{
|
|
assert(menu);
|
|
|
|
if (menu->grabbed == grab)
|
|
return;
|
|
|
|
menu->grabbed = grab;
|
|
|
|
if (menu->renderer->api.grab_keyboard)
|
|
menu->renderer->api.grab_keyboard(menu, grab);
|
|
}
|
|
|
|
bool
|
|
bm_menu_is_keyboard_grabbed(struct bm_menu *menu)
|
|
{
|
|
return menu->grabbed;
|
|
}
|
|
|
|
void
|
|
bm_menu_set_panel_overlap(struct bm_menu *menu, bool overlap)
|
|
{
|
|
assert(menu);
|
|
|
|
if (menu->overlap == overlap)
|
|
return;
|
|
|
|
menu->overlap = overlap;
|
|
|
|
if (menu->renderer->api.set_overlap)
|
|
menu->renderer->api.set_overlap(menu, overlap);
|
|
}
|
|
|
|
bool
|
|
bm_menu_add_items_at(struct bm_menu *menu, struct bm_item *item, uint32_t index)
|
|
{
|
|
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)
|
|
{
|
|
assert(menu);
|
|
|
|
if (!menu->items.items || menu->items.count <= index)
|
|
return 0;
|
|
|
|
struct bm_item *item = ((struct bm_item**)menu->items.items)[index];
|
|
bool ret = list_remove_item_at(&menu->items, index);
|
|
|
|
if (ret) {
|
|
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)
|
|
{
|
|
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);
|
|
|
|
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)
|
|
{
|
|
assert(menu);
|
|
|
|
uint32_t count;
|
|
struct bm_item **items = bm_menu_get_filtered_items(menu, &count);
|
|
|
|
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);
|
|
|
|
struct bm_item **new_items;
|
|
if (!(new_items = calloc(sizeof(struct bm_item*), nmemb)))
|
|
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)
|
|
{
|
|
assert(menu);
|
|
return list_get_items(&menu->selection, out_nmemb);
|
|
}
|
|
|
|
bool
|
|
bm_menu_set_items(struct bm_menu *menu, const struct bm_item **items, uint32_t nmemb)
|
|
{
|
|
assert(menu);
|
|
|
|
bool ret = list_set_items(&menu->items, items, nmemb, (list_free_fun)bm_item_free);
|
|
|
|
if (ret) {
|
|
list_free_list(&menu->selection);
|
|
list_free_list(&menu->filtered);
|
|
}
|
|
|
|
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)
|
|
{
|
|
assert(menu);
|
|
|
|
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)
|
|
{
|
|
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;
|
|
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;
|
|
}
|
|
|
|
static void
|
|
menu_next(struct bm_menu *menu, uint32_t count, bool wrap)
|
|
{
|
|
if (menu->index < count - 1) {
|
|
menu->index++;
|
|
} else if (wrap) {
|
|
menu->index = 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
menu_prev(struct bm_menu *menu, uint32_t count, bool wrap)
|
|
{
|
|
if (menu->index > 0) {
|
|
menu->index--;
|
|
} else if (wrap) {
|
|
menu->index = count - 1;
|
|
}
|
|
}
|
|
|
|
enum bm_run_result
|
|
bm_menu_run_with_key(struct bm_menu *menu, enum bm_key key, uint32_t unicode)
|
|
{
|
|
assert(menu);
|
|
|
|
uint32_t count;
|
|
bm_menu_get_filtered_items(menu, &count);
|
|
|
|
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->lines > 0 && 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);
|
|
} else if (menu->lines == 0) {
|
|
menu_prev(menu, count, menu->wrap);
|
|
}
|
|
break;
|
|
|
|
case BM_KEY_RIGHT:
|
|
if (menu->lines > 0 && 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);
|
|
} else if (menu->lines == 0) {
|
|
menu_next(menu, count, menu->wrap);
|
|
}
|
|
break;
|
|
|
|
case BM_KEY_HOME:
|
|
menu->curses_cursor = menu->cursor = 0;
|
|
break;
|
|
|
|
case BM_KEY_END:
|
|
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:
|
|
menu_prev(menu, count, menu->wrap);
|
|
break;
|
|
|
|
case BM_KEY_DOWN:
|
|
menu_next(menu, count, menu->wrap);
|
|
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:
|
|
{
|
|
menu_next(menu, count, true);
|
|
}
|
|
break;
|
|
|
|
case BM_KEY_SHIFT_TAB:
|
|
{
|
|
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);
|
|
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);
|
|
|
|
switch (key) {
|
|
case BM_KEY_SHIFT_RETURN:
|
|
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 :*/
|