summaryrefslogtreecommitdiff
path: root/lib/menu.c
diff options
context:
space:
mode:
authorJari Vetoniemi <mailroxas@gmail.com>2014-04-10 01:09:35 +0300
committerJari Vetoniemi <mailroxas@gmail.com>2014-04-10 01:10:05 +0300
commit67be25fbe43274340de89049fec7098cdf998b47 (patch)
treef8d6be283944fc90e1c224c461f6e92b3ae78d6f /lib/menu.c
parenta3498b25f414a888aa01ffd1cf9b2e44f6c54764 (diff)
downloadbemenu-67be25fbe43274340de89049fec7098cdf998b47.tar.gz
bemenu-67be25fbe43274340de89049fec7098cdf998b47.tar.bz2
bemenu-67be25fbe43274340de89049fec7098cdf998b47.zip
Basic working bemenu with curses backend
Diffstat (limited to 'lib/menu.c')
-rw-r--r--lib/menu.c527
1 files changed, 527 insertions, 0 deletions
diff --git a/lib/menu.c b/lib/menu.c
new file mode 100644
index 0000000..50311cf
--- /dev/null
+++ b/lib/menu.c
@@ -0,0 +1,527 @@
+/**
+ * @file bemenu.c
+ */
+
+#include "internal.h"
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+static bmItem** (*filterFunc[BM_FILTER_MODE_LAST])(bmMenu *menu, unsigned int *count, unsigned int *selected) = {
+ _bmFilterDmenu,
+ _bmFilterDmenuCaseInsensitive
+};
+
+static void _bmMenuFilter(bmMenu *menu)
+{
+ assert(menu != NULL);
+
+ if (menu->filteredItems)
+ free(menu->filteredItems);
+
+ menu->filteredCount = 0;
+ menu->filteredItems = NULL;
+
+ unsigned int count, selected;
+ bmItem **filtered = filterFunc[menu->filterMode](menu, &count, &selected);
+
+ menu->filteredItems = filtered;
+ menu->filteredCount = count;
+ menu->index = selected;
+}
+
+static int _bmMenuGrowItems(bmMenu *menu)
+{
+ void *tmp;
+ static const unsigned int step = 32;
+ unsigned int nsize = sizeof(bmItem*) * (menu->allocatedCount + step);
+
+ if (!(tmp = realloc(menu->items, nsize))) {
+ if (!(tmp = malloc(nsize)))
+ return 0;
+
+ memcpy(tmp, menu->items, sizeof(bmItem*) * menu->allocatedCount);
+ }
+
+ menu->items = tmp;
+ menu->allocatedCount += step;
+ memset(&menu->items[menu->itemsCount], 0, sizeof(bmItem*) * (menu->allocatedCount - menu->itemsCount));
+ return 1;
+}
+
+/**
+ * Create new bmMenu instance.
+ *
+ * @param drawMode Render method to be used for this menu instance.
+ * @return bmMenu for new menu instance, NULL if creation failed.
+ */
+bmMenu* bmMenuNew(bmDrawMode drawMode)
+{
+ bmMenu *menu = calloc(1, sizeof(bmMenu));
+
+ menu->drawMode = drawMode;
+
+ if (!menu)
+ return NULL;
+
+ int status = 1;
+
+ switch (menu->drawMode) {
+ case BM_DRAW_MODE_CURSES:
+ status = _bmDrawCursesInit(&menu->renderApi);
+ break;
+
+ default:break;
+ }
+
+ if (status == 0) {
+ bmMenuFree(menu);
+ return NULL;
+ }
+
+ return menu;
+}
+
+/**
+ * Release bmMenu instance.
+ *
+ * @param menu bmMenu instance to be freed from memory.
+ */
+void bmMenuFree(bmMenu *menu)
+{
+ assert(menu != NULL);
+
+ if (menu->renderApi.free)
+ menu->renderApi.free();
+
+ if (menu->title)
+ free(menu->title);
+
+ bmMenuFreeItems(menu);
+ free(menu);
+}
+
+/**
+ * Release items inside bmMenu instance.
+ *
+ * @param menu bmMenu instance which items will be freed from memory.
+ */
+void bmMenuFreeItems(bmMenu *menu)
+{
+ assert(menu != NULL);
+
+ unsigned int i;
+ for (i = 0; i < menu->itemsCount; ++i)
+ bmItemFree(menu->items[i]);
+
+ free(menu->items);
+ menu->allocatedCount = menu->itemsCount = 0;
+ menu->items = NULL;
+}
+
+/**
+ * Set active filter mode to bmMenu instance.
+ *
+ * @param menu bmMenu instance where to set filter mode.
+ * @param mode bmFilterMode constant.
+ */
+void bmMenuSetFilterMode(bmMenu *menu, bmFilterMode mode)
+{
+ assert(menu != NULL);
+
+ bmFilterMode oldMode = mode;
+ menu->filterMode = (mode >= BM_FILTER_MODE_LAST ? BM_FILTER_MODE_DMENU : mode);
+
+ if (oldMode != mode)
+ _bmMenuFilter(menu);
+}
+
+/**
+ * Get active filter mode from bmMenu instance.
+ *
+ * @param menu bmMenu instance where to get filter mode.
+ * @return bmFilterMode constant.
+ */
+bmFilterMode bmMenuGetFilterMode(const bmMenu *menu)
+{
+ assert(menu != NULL);
+ return menu->filterMode;
+}
+
+/**
+ * Set title to bmMenu instance.
+ *
+ * @param menu bmMenu instance where to set title.
+ * @param title C "string" to set as title, can be NULL for empty title.
+ */
+int bmMenuSetTitle(bmMenu *menu, const char *title)
+{
+ assert(menu != NULL);
+
+ char *copy = NULL;
+ if (title && !(copy = _bmStrdup(title)))
+ return 0;
+
+ if (menu->title)
+ free(menu->title);
+
+ menu->title = copy;
+ return 1;
+}
+
+/**
+ * Get title from bmMenu instance.
+ *
+ * @param menu bmMenu instance where to get title from.
+ * @return Pointer to null terminated C "string", can be NULL for empty title.
+ */
+const char* bmMenuGetTitle(const bmMenu *menu)
+{
+ assert(menu != NULL);
+ return menu->title;
+}
+
+/**
+ * Add item to bmMenu instance at specific index.
+ *
+ * @param menu bmMenu instance where item will be added.
+ * @param item bmItem instance to add.
+ * @param index Index where item will be added.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuAddItemAt(bmMenu *menu, bmItem *item, unsigned int index)
+{
+ assert(menu != NULL);
+ assert(item != NULL);
+
+ if (menu->itemsCount >= menu->allocatedCount && !_bmMenuGrowItems(menu))
+ return 0;
+
+ if (index + 1 != menu->itemsCount) {
+ unsigned int i = index;
+ memmove(&menu->items[i + 1], &menu->items[i], sizeof(bmItem*) * (menu->itemsCount - i));
+ }
+
+ menu->items[index] = item;
+ menu->itemsCount++;
+ return 1;
+}
+
+/**
+ * Add item to bmMenu instance.
+ *
+ * @param menu bmMenu instance where item will be added.
+ * @param item bmItem instance to add.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuAddItem(bmMenu *menu, bmItem *item)
+{
+ return bmMenuAddItemAt(menu, item, menu->itemsCount);
+}
+
+/**
+ * Remove item from bmMenu instance at specific index.
+ *
+ * @param menu bmMenu instance from where item will be removed.
+ * @param index Index of item to remove.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuRemoveItemAt(bmMenu *menu, unsigned int index)
+{
+ assert(menu != NULL);
+
+ unsigned int i = index;
+ if (i >= menu->itemsCount)
+ return 0;
+
+ memmove(&menu->items[i], &menu->items[i], sizeof(bmItem*) * (menu->itemsCount - i));
+ return 1;
+}
+
+/**
+ * Remove item from bmMenu instance.
+ *
+ * @param menu bmMenu instance from where item will be removed.
+ * @param item bmItem instance to remove.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuRemoveItem(bmMenu *menu, bmItem *item)
+{
+ assert(menu != NULL);
+ assert(item != NULL);
+
+ unsigned int i;
+ for (i = 0; i < menu->itemsCount && menu->items[i] != item; ++i);
+ return bmMenuRemoveItemAt(menu, i);
+}
+
+/**
+ * Get selected item from bmMenu instance.
+ *
+ * @param menu bmMenu instance from where to get selected item.
+ * @return Selected bmItem instance, NULL if none selected.
+ */
+bmItem* bmMenuGetSelectedItem(const bmMenu *menu)
+{
+ assert(menu != NULL);
+
+ unsigned int count;
+ bmItem **items = bmMenuGetFilteredItems(menu, &count);
+
+ if (!items || count < menu->index)
+ return NULL;
+
+ return items[menu->index];
+}
+
+/**
+ * Get items from bmMenu instance.
+ *
+ * @param menu bmMenu instance from where to get items.
+ * @param nmemb Reference to unsigned int where total count of returned items will be stored.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** bmMenuGetItems(const bmMenu *menu, unsigned int *nmemb)
+{
+ assert(menu != NULL);
+
+ if (nmemb)
+ *nmemb = menu->itemsCount;
+
+ return menu->items;
+}
+
+/**
+ * Get filtered (displayed) items from bmMenu instance.
+ *
+ * @warning The pointer returned by this function _will_ be invalid when menu internally filters its list again.
+ * Do not store this pointer.
+ *
+ * @param menu bmMenu instance from where to get filtered items.
+ * @param nmemb Reference to unsigned int where total count of returned items will be stored.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** bmMenuGetFilteredItems(const bmMenu *menu, unsigned int *nmemb)
+{
+ assert(menu != NULL);
+
+ if (nmemb)
+ *nmemb = (menu->filteredItems ? menu->filteredCount : menu->itemsCount);
+
+ return (menu->filteredItems ? menu->filteredItems : menu->items);
+}
+
+/**
+ * Set items to bmMenu instance.
+ * Will replace all the old items on bmMenu instance.
+ *
+ * @param menu bmMenu instance where items will be set.
+ * @param items Array of bmItem pointers to set.
+ * @param nmemb Total count of items in array.
+ * @return 1 on successful set, 0 on failure.
+ */
+int bmMenuSetItems(bmMenu *menu, const bmItem **items, unsigned int nmemb)
+{
+ assert(menu != NULL);
+
+ if (items == NULL || nmemb == 0) {
+ bmMenuFreeItems(menu);
+ return 1;
+ }
+
+ bmItem **newItems;
+ if (!(newItems = calloc(sizeof(bmItem*), nmemb)))
+ return 0;
+
+ memcpy(newItems, items, sizeof(bmItem*) * nmemb);
+ bmMenuFreeItems(menu);
+
+ menu->items = newItems;
+ menu->allocatedCount = menu->itemsCount = nmemb;
+ return 1;
+}
+
+/**
+ * Create new bmMenu instance.
+ *
+ * @param drawMode Render method to be used for this menu instance.
+ * @return bmMenu for new menu instance, NULL if creation failed.
+ */
+void bmMenuRender(const bmMenu *menu)
+{
+ assert(menu != NULL);
+
+ if (menu->renderApi.render)
+ menu->renderApi.render(menu);
+}
+
+/**
+ * Poll key and unicode from underlying UI toolkit.
+ *
+ * This function will block on CURSES draw mode.
+ *
+ * @param menu bmMenu instance from which to poll.
+ * @param unicode Reference to unsigned int.
+ * @return bmKey for polled key.
+ */
+bmKey bmMenuGetKey(bmMenu *menu, unsigned int *unicode)
+{
+ assert(menu != NULL);
+ assert(unicode != NULL);
+
+ *unicode = 0;
+ bmKey key = BM_KEY_NONE;
+
+ if (menu->renderApi.getKey)
+ key = menu->renderApi.getKey(unicode);
+
+ return key;
+}
+
+/**
+ * Advances menu logic with key and unicode as input.
+ *
+ * @param menu bmMenu instance to be advanced.
+ * @return bmRunResult for menu state.
+ */
+bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode)
+{
+ assert(menu != NULL);
+ char *oldFilter = _bmStrdup(menu->filter);
+ unsigned int itemsCount = (menu->filteredItems ? menu->filteredCount : menu->itemsCount);
+
+ switch (key) {
+ case BM_KEY_LEFT:
+ {
+ unsigned int oldCursor = menu->cursor;
+ menu->cursor -= _bmUtf8RunePrev(menu->filter, menu->cursor);
+ menu->cursesCursor -= _bmUtf8RuneWidth(menu->filter + menu->cursor, oldCursor - menu->cursor);
+ }
+ break;
+
+ case BM_KEY_RIGHT:
+ {
+ unsigned int oldCursor = menu->cursor;
+ menu->cursor += _bmUtf8RuneNext(menu->filter, menu->cursor);
+ menu->cursesCursor += _bmUtf8RuneWidth(menu->filter + oldCursor, menu->cursor - oldCursor);
+ }
+ break;
+
+ case BM_KEY_HOME:
+ menu->cursesCursor = menu->cursor = 0;
+ break;
+
+ case BM_KEY_END:
+ menu->cursor = strlen(menu->filter);
+ menu->cursesCursor = _bmUtf8StringScreenWidth(menu->filter);
+ break;
+
+ case BM_KEY_UP:
+ if (menu->index > 0)
+ menu->index--;
+ break;
+
+ case BM_KEY_DOWN:
+ if (menu->index < itemsCount - 1)
+ menu->index++;
+ break;
+
+ case BM_KEY_PAGE_UP:
+ menu->index = 0;
+ break;
+
+ case BM_KEY_PAGE_DOWN:
+ menu->index = itemsCount - 1;
+ break;
+
+ case BM_KEY_BACKSPACE:
+ {
+ size_t width;
+ menu->cursor -= _bmUtf8RuneRemove(menu->filter, menu->cursor, &width);
+ menu->cursesCursor -= width;
+ }
+ break;
+
+ case BM_KEY_DELETE:
+ _bmUtf8RuneRemove(menu->filter, menu->cursor + 1, NULL);
+ break;
+
+ case BM_KEY_LINE_DELETE_LEFT:
+ {
+ while (menu->cursor > 0) {
+ size_t width;
+ menu->cursor -= _bmUtf8RuneRemove(menu->filter, menu->cursor, &width);
+ menu->cursesCursor -= width;
+ }
+ }
+ break;
+
+ case BM_KEY_LINE_DELETE_RIGHT:
+ menu->filter[menu->cursor] = 0;
+ break;
+
+ case BM_KEY_WORD_DELETE:
+ {
+ while (menu->cursor < strlen(menu->filter) && !isspace(menu->filter[menu->cursor])) {
+ unsigned int oldCursor = menu->cursor;
+ menu->cursor += _bmUtf8RuneNext(menu->filter, menu->cursor);
+ menu->cursesCursor += _bmUtf8RuneWidth(menu->filter + oldCursor, menu->cursor - oldCursor);
+ }
+ while (menu->cursor > 0 && isspace(menu->filter[menu->cursor - 1])) {
+ unsigned int oldCursor = menu->cursor;
+ menu->cursor -= _bmUtf8RunePrev(menu->filter, menu->cursor);
+ menu->cursesCursor -= _bmUtf8RuneWidth(menu->filter + menu->cursor, oldCursor - menu->cursor);
+ }
+ while (menu->cursor > 0 && !isspace(menu->filter[menu->cursor - 1])) {
+ size_t width;
+ menu->cursor -= _bmUtf8RuneRemove(menu->filter, menu->cursor, &width);
+ menu->cursesCursor -= width;
+ }
+ }
+ break;
+
+ case BM_KEY_UNICODE:
+ {
+ size_t width;
+ menu->cursor += _bmUnicodeInsert(menu->filter, sizeof(menu->filter) - 1, menu->cursor, unicode, &width);
+ menu->cursesCursor += width;
+ }
+ break;
+
+ case BM_KEY_TAB:
+ {
+ bmItem *selected = bmMenuGetSelectedItem(menu);
+ if (selected && bmItemGetText(selected)) {
+ const char *text = bmItemGetText(selected);
+ size_t len = strlen(text);
+
+ if (len > sizeof(menu->filter) - 1)
+ len = sizeof(menu->filter) - 1;
+
+ memset(menu->filter, 0, strlen(menu->filter));
+ memcpy(menu->filter, text, len);
+ menu->cursor = strlen(menu->filter);
+ menu->cursesCursor = _bmUtf8StringScreenWidth(menu->filter);
+ }
+ }
+ break;
+
+ case BM_KEY_RETURN:
+ return BM_RUN_RESULT_SELECTED;
+
+ case BM_KEY_ESCAPE:
+ return BM_RUN_RESULT_CANCEL;
+
+ default: break;
+ }
+
+ if (oldFilter && strcmp(oldFilter, menu->filter))
+ _bmMenuFilter(menu);
+
+ if (oldFilter)
+ free(oldFilter);
+ return BM_RUN_RESULT_RUNNING;
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/