Make it possible filter manually, and optimized filtering.

This commit is contained in:
Jari Vetoniemi 2014-04-10 23:05:13 +03:00
parent 45e2117159
commit 71beb7583f
4 changed files with 96 additions and 54 deletions

View File

@ -318,6 +318,17 @@ bmItem** bmMenuGetFilteredItems(const bmMenu *menu, unsigned int *outNmemb);
*/
void bmMenuRender(const bmMenu *menu);
/**
* Trigger filtering of menu manually.
* This is useful when adding new items and want to dynamically see them filtered.
*
* Do note that filtering might be heavy, so you should only call it after batch manipulation of items.
* Not after manipulation of each single item.
*
* @param menu bmMenu instance which to filter.
*/
void bmMenuFilter(bmMenu *menu);
/**
* Poll key and unicode from underlying UI toolkit.
*

View File

@ -76,12 +76,13 @@ fail:
* Dmenu filterer that accepts substring function.
*
* @param menu bmMenu instance to filter.
* @param addition This will be 1, if filter is same as previous filter with something appended.
* @param fstrstr Substring function used to match items.
* @param outNmemb unsigned int reference to filtered items outNmemb.
* @param outHighlighted unsigned int reference to new outHighlighted item index.
* @return Pointer to array of bmItem pointers.
*/
bmItem** _bmFilterDmenuFun(bmMenu *menu, char* (*fstrstr)(const char *a, const char *b), unsigned int *outNmemb, unsigned int *outHighlighted)
bmItem** _bmFilterDmenuFun(bmMenu *menu, char addition, char* (*fstrstr)(const char *a, const char *b), unsigned int *outNmemb, unsigned int *outHighlighted)
{
assert(menu);
assert(outNmemb);
@ -89,7 +90,13 @@ bmItem** _bmFilterDmenuFun(bmMenu *menu, char* (*fstrstr)(const char *a, const c
*outNmemb = *outHighlighted = 0;
unsigned int itemsCount;
bmItem **items = bmMenuGetItems(menu, &itemsCount);
bmItem **items;
if (addition) {
items = bmMenuGetFilteredItems(menu, &itemsCount);
} else {
items = bmMenuGetItems(menu, &itemsCount);
}
bmItem **filtered = calloc(itemsCount, sizeof(bmItem*));
if (!filtered)
@ -99,6 +106,7 @@ bmItem** _bmFilterDmenuFun(bmMenu *menu, char* (*fstrstr)(const char *a, const c
unsigned int tokc;
char *buffer = _bmFilterTokenize(menu, &tokv, &tokc);
char found = 0;
unsigned int i, f;
for (f = i = 0; i < itemsCount; ++i) {
bmItem *item = items[i];
@ -112,8 +120,11 @@ bmItem** _bmFilterDmenuFun(bmMenu *menu, char* (*fstrstr)(const char *a, const c
continue;
}
if (f == 0 || item == bmMenuGetHighlightedItem(menu))
if (!found && item == bmMenuGetHighlightedItem(menu)) {
*outHighlighted = f;
found = 1;
}
filtered[f++] = item;
}
@ -130,26 +141,28 @@ bmItem** _bmFilterDmenuFun(bmMenu *menu, char* (*fstrstr)(const char *a, const c
* Filter that mimics the vanilla dmenu filtering.
*
* @param menu bmMenu instance to filter.
* @param addition This will be 1, if filter is same as previous filter with something appended.
* @param outNmemb unsigned int reference to filtered items outNmemb.
* @param outHighlighted unsigned int reference to new outHighlighted item index.
* @return Pointer to array of bmItem pointers.
*/
bmItem** _bmFilterDmenu(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted)
bmItem** _bmFilterDmenu(bmMenu *menu, char addition, unsigned int *outNmemb, unsigned int *outHighlighted)
{
return _bmFilterDmenuFun(menu, strstr, outNmemb, outHighlighted);
return _bmFilterDmenuFun(menu, addition, strstr, outNmemb, outHighlighted);
}
/**
* Filter that mimics the vanilla case-insensitive dmenu filtering.
*
* @param menu bmMenu instance to filter.
* @param addition This will be 1, if filter is same as previous filter with something appended.
* @param outNmemb unsigned int reference to filtered items outNmemb.
* @param outHighlighted unsigned int reference to new outHighlighted item index.
* @return Pointer to array of bmItem pointers.
*/
bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted)
bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, char addition, unsigned int *outNmemb, unsigned int *outHighlighted)
{
return _bmFilterDmenuFun(menu, _bmStrupstr, outNmemb, outHighlighted);
return _bmFilterDmenuFun(menu, addition, _bmStrupstr, outNmemb, outHighlighted);
}
/* vim: set ts=8 sw=4 tw=0 :*/

View File

@ -102,6 +102,11 @@ struct _bmMenu {
*/
char filter[1024];
/**
* Used as optimization.
*/
char *oldFilter;
/**
* Current byte offset on filter text.
*/
@ -136,8 +141,8 @@ int _bmDrawCursesInit(struct _bmRenderApi *api);
int _bmMenuItemIsSelected(const bmMenu *menu, const bmItem *item);
/* filter.c */
bmItem** _bmFilterDmenu(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted);
bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted);
bmItem** _bmFilterDmenu(bmMenu *menu, char addition, unsigned int *outNmemb, unsigned int *outHighlighted);
bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, char addition, unsigned int *outNmemb, unsigned int *outHighlighted);
/* list.c */
void _bmItemListFreeList(struct _bmItemList *list);

View File

@ -7,27 +7,11 @@
/**
* Filter function map.
*/
static bmItem** (*filterFunc[BM_FILTER_MODE_LAST])(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted) = {
static bmItem** (*filterFunc[BM_FILTER_MODE_LAST])(bmMenu *menu, char addition, unsigned int *outNmemb, unsigned int *outHighlighted) = {
_bmFilterDmenu, /* BM_FILTER_DMENU */
_bmFilterDmenuCaseInsensitive /* BM_FILTER_DMENU_CASE_INSENSITIVE */
};
static void _bmMenuFilter(bmMenu *menu)
{
assert(menu);
if (!strlen(menu->filter) || !menu->items.list || menu->items.count <= 0) {
_bmItemListFreeList(&menu->filtered);
return;
}
unsigned int count, selected;
bmItem **filtered = filterFunc[menu->filterMode](menu, &count, &selected);
_bmItemListSetItemsNoCopy(&menu->filtered, filtered, count);
menu->index = selected;
}
int _bmMenuItemIsSelected(const bmMenu *menu, const bmItem *item)
{
assert(menu);
@ -87,6 +71,9 @@ void bmMenuFree(bmMenu *menu)
if (menu->title)
free(menu->title);
if (menu->oldFilter)
free(menu->oldFilter);
bmMenuFreeItems(menu);
free(menu);
}
@ -138,12 +125,7 @@ void* bmMenuGetUserdata(bmMenu *menu)
void bmMenuSetFilterMode(bmMenu *menu, bmFilterMode mode)
{
assert(menu);
bmFilterMode oldMode = mode;
menu->filterMode = (mode >= BM_FILTER_MODE_LAST ? BM_FILTER_MODE_DMENU : mode);
if (oldMode != mode)
_bmMenuFilter(menu);
}
/**
@ -202,13 +184,7 @@ const char* bmMenuGetTitle(const bmMenu *menu)
int bmMenuAddItemAt(bmMenu *menu, bmItem *item, unsigned int index)
{
assert(menu);
int ret = _bmItemListAddItemAt(&menu->items, item, index);
if (ret)
_bmMenuFilter(menu);
return ret;
return _bmItemListAddItemAt(&menu->items, item, index);
}
/**
@ -220,12 +196,7 @@ int bmMenuAddItemAt(bmMenu *menu, bmItem *item, unsigned int index)
*/
int bmMenuAddItem(bmMenu *menu, bmItem *item)
{
int ret = _bmItemListAddItem(&menu->items, item);
if (ret)
_bmMenuFilter(menu);
return ret;
return _bmItemListAddItem(&menu->items, item);
}
/**
@ -250,7 +221,6 @@ int bmMenuRemoveItemAt(bmMenu *menu, unsigned int index)
if (ret) {
_bmItemListRemoveItem(&menu->selection, item);
_bmItemListRemoveItem(&menu->filtered, item);
_bmMenuFilter(menu);
}
return ret;
@ -274,7 +244,6 @@ int bmMenuRemoveItem(bmMenu *menu, bmItem *item)
if (ret) {
_bmItemListRemoveItem(&menu->selection, item);
_bmItemListRemoveItem(&menu->filtered, item);
_bmMenuFilter(menu);
}
return ret;
@ -393,7 +362,6 @@ int bmMenuSetItems(bmMenu *menu, const bmItem **items, unsigned int nmemb)
if (ret) {
_bmItemListFreeList(&menu->selection);
_bmItemListFreeList(&menu->filtered);
_bmMenuFilter(menu);
}
return ret;
@ -447,6 +415,57 @@ void bmMenuRender(const bmMenu *menu)
menu->renderApi.render(menu);
}
/**
* Trigger filtering of menu manually.
* This is useful when adding new items and want to dynamically see them filtered.
*
* Do note that filtering might be heavy, so you should only call it after batch manipulation of items.
* Not after manipulation of each single item.
*
* @param menu bmMenu instance which to filter.
*/
void bmMenuFilter(bmMenu *menu)
{
assert(menu);
char addition = 0;
size_t len = strlen(menu->filter);
if (!len || !menu->items.list || menu->items.count <= 0) {
_bmItemListFreeList(&menu->filtered);
if (menu->oldFilter)
free(menu->oldFilter);
menu->oldFilter = NULL;
return;
}
if (menu->oldFilter) {
size_t oldLen = strlen(menu->oldFilter);
addition = (oldLen < len && !memcmp(menu->oldFilter, menu->filter, oldLen));
}
if (menu->oldFilter && addition && menu->filtered.count <= 0)
return;
if (menu->oldFilter && !strcmp(menu->filter, menu->oldFilter))
return;
unsigned int count, selected;
bmItem **filtered = filterFunc[menu->filterMode](menu, addition, &count, &selected);
_bmItemListSetItemsNoCopy(&menu->filtered, filtered, count);
menu->index = selected;
if (count) {
if (menu->oldFilter)
free(menu->oldFilter);
menu->oldFilter = _bmStrdup(menu->filter);
}
}
/**
* Poll key and unicode from underlying UI toolkit.
*
@ -485,8 +504,6 @@ bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode)
unsigned int itemsCount;
bmMenuGetFilteredItems(menu, &itemsCount);
char *oldFilter = _bmStrdup(menu->filter);
switch (key) {
case BM_KEY_LEFT:
{
@ -619,11 +636,7 @@ bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode)
default: break;
}
if (oldFilter && strcmp(oldFilter, menu->filter))
_bmMenuFilter(menu);
if (oldFilter)
free(oldFilter);
bmMenuFilter(menu);
switch (key) {
case BM_KEY_RETURN: return BM_RUN_RESULT_SELECTED;