From 1c511e30f540bcc7a79aa9ab96f9552097c1c6ec Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Fri, 28 Mar 2014 21:34:38 +0200 Subject: Remove custom output directory paths. --- lib/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) (limited to 'lib/CMakeLists.txt') diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 46c532b..cf489fd 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,6 +1,3 @@ -SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY lib) -SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY lib) - # Sources SET(BEMENU_SOURCE bemenu.c -- cgit v1.2.3-70-g09d2 From 67be25fbe43274340de89049fec7098cdf998b47 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Thu, 10 Apr 2014 01:09:35 +0300 Subject: Basic working bemenu with curses backend --- client/client.c | 95 +++++++++- lib/CMakeLists.txt | 7 +- lib/bemenu.c | 60 ------ lib/bemenu.h | 227 ++++++++++++++++++++++- lib/draw/curses.c | 258 +++++++++++++++++++++++++- lib/filter.c | 64 +++++++ lib/internal.h | 39 +++- lib/item.c | 75 ++++++++ lib/menu.c | 527 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/util.c | 226 +++++++++++++++++++++++ 10 files changed, 1504 insertions(+), 74 deletions(-) delete mode 100644 lib/bemenu.c create mode 100644 lib/filter.c create mode 100644 lib/item.c create mode 100644 lib/menu.c create mode 100644 lib/util.c (limited to 'lib/CMakeLists.txt') diff --git a/client/client.c b/client/client.c index 559651f..1c30498 100644 --- a/client/client.c +++ b/client/client.c @@ -6,8 +6,83 @@ */ #include +#include +#include +#include +#include #include +static ptrdiff_t getLine(char **outLine, size_t *outAllocated, FILE *stream) +{ + size_t len = 0, allocated; + char *s, *buffer; + + assert(outLine != NULL); + assert(outAllocated != NULL); + + if (stream == NULL || feof(stream) || ferror(stream)) + return -1; + + allocated = *outAllocated; + buffer = *outLine; + + if (buffer == NULL || allocated == 0) { + if (!(buffer = calloc(1, (allocated = 1024) + 1))) + return -1; + } + + for (s = buffer;;) { + if (fgets(s, allocated - (s - buffer), stream) == NULL) + return -1; + + len = strlen(s); + if (feof(stream)) + break; + + if (len > 0 && s[len - 1] == '\n') + break; + + if (len + 1 >= allocated - (s - buffer)) { + void *tmp = realloc(buffer, 2 * allocated); + if (!tmp) + break; + + buffer = tmp; + s = buffer + allocated - 1; + memset(s, 0, allocated - (s - buffer)); + allocated *= 2; + } else { + s += len; + } + } + + *outAllocated = allocated; + *outLine = buffer; + + if (s[len - 1] == '\n') + s[len - 1] = 0; + + return s - buffer + len; +} + +static void readItemsToMenuFromStdin(bmMenu *menu) +{ + ptrdiff_t len; + size_t size; + char *line = NULL; + + while ((len = getLine(&line, &size, stdin)) != -1) { + bmItem *item = bmItemNew((len > 0 ? line : NULL)); + if (!item) + break; + + bmMenuAddItem(menu, item); + } + + if (line) + free(line); +} + /** * Main method * @@ -21,16 +96,30 @@ int main(int argc, char **argv) { (void)argc, (void)argv; - bmMenu *menu = bmMenuNew(BM_DRAW_MODE_NONE); + bmMenu *menu = bmMenuNew(BM_DRAW_MODE_CURSES); if (!menu) return EXIT_FAILURE; - bmMenuRender(menu); + bmMenuSetTitle(menu, "bemenu"); + readItemsToMenuFromStdin(menu); + + bmKey key; + unsigned int unicode; + int status = 0; + do { + bmMenuRender(menu); + key = bmMenuGetKey(menu, &unicode); + } while ((status = bmMenuRunWithKey(menu, key, unicode)) == BM_RUN_RESULT_RUNNING); + + if (status == BM_RUN_RESULT_SELECTED) { + bmItem *item = bmMenuGetSelectedItem(menu); + printf("%s\n", bmItemGetText(item)); + } bmMenuFree(menu); - return EXIT_SUCCESS; + return (status == BM_RUN_RESULT_SELECTED ? EXIT_SUCCESS : EXIT_FAILURE); } /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index cf489fd..e669ef5 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,6 +1,9 @@ # Sources SET(BEMENU_SOURCE - bemenu.c + menu.c + item.c + filter.c + util.c draw/curses.c ) SET(BEMENU_INCLUDE) @@ -22,6 +25,6 @@ ENDIF () # Compile INCLUDE_DIRECTORIES(${BEMENU_INCLUDE}) ADD_LIBRARY(bemenu ${BEMENU_SOURCE}) -TARGET_LINK_LIBRARIES(bemenu ${BEMENU_LIBRARIES}) +TARGET_LINK_LIBRARIES(bemenu ${BEMENU_LIBRARIES} dl) # vim: set ts=8 sw=4 tw=0 : diff --git a/lib/bemenu.c b/lib/bemenu.c deleted file mode 100644 index d4ae77e..0000000 --- a/lib/bemenu.c +++ /dev/null @@ -1,60 +0,0 @@ -/** - * @file bemenu.c - */ - -#include "internal.h" -#include -#include - -/** - * 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; - - switch (menu->drawMode) { - default:break; - } - - 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(); - - free(menu); -} - -/** - * 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(bmMenu *menu) -{ - assert(menu != NULL); - - if (menu->renderApi.render) - menu->renderApi.render(menu->items, menu->itemsCount); -} - -/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/bemenu.h b/lib/bemenu.h index 3fdfb0b..b4b9b30 100644 --- a/lib/bemenu.h +++ b/lib/bemenu.h @@ -5,17 +5,71 @@ */ /** - * Draw mode constants for setting bmMenu instance draw mode. + * Draw mode constants for bmMenu instance draw mode. * * BM_DRAW_MODE_LAST is provided for enumerating draw modes. * Instancing with it however provides exactly same functionality as BM_DRAW_MODE_NONE. */ typedef enum bmDrawMode { BM_DRAW_MODE_NONE, + BM_DRAW_MODE_CURSES, BM_DRAW_MODE_LAST } bmDrawMode; +/** + * Filter mode constants for bmMenu instance filter mode. + * + * BM_FILTER_MODE_LAST is provided for enumerating filter modes. + * Using it as filter mode however provides exactly same functionality as BM_FILTER_MODE_DMENU. + */ +typedef enum bmFilterMode { + BM_FILTER_MODE_DMENU, + BM_FILTER_MODE_DMENU_CASE_INSENSITIVE, + BM_FILTER_MODE_LAST +} bmFilterMode; + +/** + * Result constants from bmMenuRunWithKey function. + * + * BM_RUN_RESULT_RUNNING means that menu is running and thus should be still renderer && ran. + * BM_RUN_RESULT_SELECTED means that menu was closed and items were selected. + * BM_RUN_RESULT_CANCEL means that menu was closed and selection was canceled. + */ +typedef enum bmRunResult { + BM_RUN_RESULT_RUNNING, + BM_RUN_RESULT_SELECTED, + BM_RUN_RESULT_CANCEL, +} bmRunResult; + +/** + * Key constants. + * + * BM_KEY_LAST is provided for enumerating keys. + */ +typedef enum bmKey { + BM_KEY_NONE, + BM_KEY_UP, + BM_KEY_DOWN, + BM_KEY_LEFT, + BM_KEY_RIGHT, + BM_KEY_HOME, + BM_KEY_END, + BM_KEY_PAGE_UP, + BM_KEY_PAGE_DOWN, + BM_KEY_BACKSPACE, + BM_KEY_DELETE, + BM_KEY_LINE_DELETE_LEFT, + BM_KEY_LINE_DELETE_RIGHT, + BM_KEY_WORD_DELETE, + BM_KEY_TAB, + BM_KEY_ESCAPE, + BM_KEY_RETURN, + BM_KEY_UNICODE, + BM_KEY_LAST +} bmKey; + typedef struct _bmMenu bmMenu; +typedef struct _bmItem bmItem; /** * Create new bmMenu instance. @@ -32,11 +86,180 @@ bmMenu* bmMenuNew(bmDrawMode drawMode); */ void bmMenuFree(bmMenu *menu); +/** + * Release items inside bmMenu instance. + * + * @param menu bmMenu instance which items will be freed from memory. + */ +void bmMenuFreeItems(bmMenu *menu); + +/** + * 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); + +/** + * Get active filter mode from bmMenu instance. + * + * @param menu bmMenu instance where to get filter mode. + * @return bmFilterMode constant. + */ +bmFilterMode bmMenuGetFilterMode(const bmMenu *menu); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * Remove item from bmMenu instance. + * The item won't be freed, use bmItemFree to do that. + * + * @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); + +/** + * 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); + +/** + * 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); + +/** + * 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); + +/** + * Set items to bmMenu instance. + * Will replace all the old items on bmMenu instance. + * + * If items is NULL, or nmemb is zero, all items will be freed from the menu. + * + * @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); + /** * Render bmMenu instance using chosen draw method. * * @param menu bmMenu instance to be rendered. */ -void bmMenuRender(bmMenu *menu); +void bmMenuRender(const bmMenu *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); + +/** + * 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); + +/** + * Allocate a new item. + * + * @param text Pointer to null terminated C "string", can be NULL for empty text. + * @return bmItem for new item instance, NULL if creation failed. + */ +bmItem* bmItemNew(const char *text); + +/** + * Release bmItem instance. + * + * @param item bmItem instance to be freed from memory. + */ +void bmItemFree(bmItem *item); + +/** + * Set text to bmItem instance. + * + * @param item bmItem instance where to set text. + * @param text C "string" to set as text, can be NULL for empty text. + */ +int bmItemSetText(bmItem *item, const char *text); + +/** + * Get text from bmItem instance. + * + * @param item bmItem instance where to get text from. + * @return Pointer to null terminated C "string", can be NULL for empty text. + */ +const char* bmItemGetText(const bmItem *item); /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/draw/curses.c b/lib/draw/curses.c index c873496..48a146a 100644 --- a/lib/draw/curses.c +++ b/lib/draw/curses.c @@ -2,8 +2,260 @@ * @file curses.c */ -/* - * code goes here - */ +#include "../internal.h" +#include +#include +#include +#include +#include +#include +#include + +/* ncurses.h likes to define stuff for us. + * This unforunately mangles with our struct. */ +#undef erase +#undef refresh +#undef mvprintw +#undef move +#undef init_pair +#undef attroff +#undef attron +#undef getmaxx +#undef getmaxy +#undef timeout + +static struct curses { + void *handle; + WINDOW *stdscr; + WINDOW* (*initscr)(void); + int (*endwin)(void); + int (*refresh)(void); + int (*erase)(void); + int (*get_wch)(wint_t *wch); + int (*mvprintw)(int x, int y, const char *fmt, ...); + int (*move)(int x, int y); + int (*init_pair)(short color, short f, short b); + int (*attroff)(int attrs); + int (*attron)(int attrs); + int (*start_color)(void); + int (*getmaxx)(WINDOW *win); + int (*getmaxy)(WINDOW *win); + int (*keypad)(WINDOW *win, bool bf); + int *ESCDELAY; +} curses; + +static void _bmDrawCursesDrawLine(int pair, int y, const char *format, ...) +{ + static int ncols = 0; + static char *buffer = NULL; + int new_ncols = curses.getmaxx(curses.stdscr); + + if (new_ncols <= 0) + return; + + if (!buffer || new_ncols > ncols) { + if (buffer) + free(buffer); + + ncols = new_ncols; + + if (!(buffer = calloc(1, ncols + 1))) + return; + } + + va_list args; + va_start(args, format); + int tlen = vsnprintf(NULL, 0, format, args) + 1; + if (tlen > ncols) + tlen = ncols; + va_end(args); + + va_start(args, format); + vsnprintf(buffer, tlen, format, args); + va_end(args); + + memset(buffer + tlen - 1, ' ', ncols - tlen + 1); + + if (pair > 0) + curses.attron(COLOR_PAIR(pair)); + + curses.mvprintw(y, 0, buffer); + + if (pair > 0) + curses.attroff(COLOR_PAIR(pair)); +} + +static void _bmDrawCursesRender(const bmMenu *menu) +{ + if (!curses.stdscr) { + freopen("/dev/tty", "rw", stdin); + setlocale(LC_CTYPE, ""); + if ((curses.stdscr = curses.initscr()) == NULL) + return; + + *curses.ESCDELAY = 25; + curses.keypad(curses.stdscr, true); + + curses.start_color(); + curses.init_pair(1, COLOR_BLACK, COLOR_RED); + curses.init_pair(2, COLOR_RED, COLOR_BLACK); + } + + const unsigned int lines = curses.getmaxy(curses.stdscr); + curses.erase(); + + size_t titleLen = (menu->title ? strlen(menu->title) + 1 : 0); + + _bmDrawCursesDrawLine(0, 0, "%*s%s", titleLen, "", menu->filter); + + if (menu->title) { + curses.attron(COLOR_PAIR(1)); + curses.mvprintw(0, 0, menu->title); + curses.attroff(COLOR_PAIR(1)); + } + + unsigned int i, cl = 1; + unsigned int itemsCount; + bmItem **items = bmMenuGetFilteredItems(menu, &itemsCount); + for (i = (menu->index / (lines - 1)) * (lines - 1); i < itemsCount && cl < lines; ++i) { + int selected = (items[i] == bmMenuGetSelectedItem(menu)); + _bmDrawCursesDrawLine((selected ? 2 : 0), cl++, "%s%s", (selected ? ">> " : " "), items[i]->text); + } + + curses.move(0, titleLen + menu->cursesCursor); + curses.refresh(); +} + +static void _bmDrawCursesEndWin(void) +{ + if (curses.endwin) + curses.endwin(); + + curses.stdscr = NULL; +} + +static bmKey _bmDrawCursesGetKey(unsigned int *unicode) +{ + assert(unicode != NULL); + *unicode = 0; + + if (!curses.stdscr) + return BM_KEY_NONE; + + curses.get_wch(unicode); + switch (*unicode) { + case 16: /* C-p */ + case KEY_UP: return BM_KEY_UP; + + case 14: /* C-n */ + case KEY_DOWN: return BM_KEY_DOWN; + + case 2: /* C-b */ + case KEY_LEFT: return BM_KEY_LEFT; + + case 6: /* C-f */ + case KEY_RIGHT: return BM_KEY_RIGHT; + + case 1: /* C-a */ + case KEY_HOME: return BM_KEY_HOME; + + case 5: /* C-e */ + case KEY_END: return BM_KEY_END; + + case KEY_PPAGE: return BM_KEY_PAGE_UP; + case KEY_NPAGE: return BM_KEY_PAGE_DOWN; + + case 8: /* C-h */ + case KEY_BACKSPACE: return BM_KEY_BACKSPACE; + + case 4: /* C-d */ + case KEY_DC: return BM_KEY_DELETE; + + case 21: return BM_KEY_LINE_DELETE_LEFT; /* C-u */ + case 11: return BM_KEY_LINE_DELETE_RIGHT; /* C-k */ + case 23: return BM_KEY_WORD_DELETE; /* C-w */ + + case 9: return BM_KEY_TAB; /* Tab */ + + case 10: /* Return */ + _bmDrawCursesEndWin(); + return BM_KEY_RETURN; + + case 7: /* C-g */ + case 27: /* Escape */ + _bmDrawCursesEndWin(); + return BM_KEY_ESCAPE; + + default: break; + } + + return BM_KEY_UNICODE; +} + +static void _bmDrawCursesFree(void) +{ + _bmDrawCursesEndWin(); + + if (curses.handle) + dlclose(curses.handle); + + memset(&curses, 0, sizeof(curses)); +} + +int _bmDrawCursesInit(struct _bmRenderApi *api) +{ + memset(&curses, 0, sizeof(curses)); + + /* FIXME: hardcoded and not cross-platform */ + curses.handle = dlopen("/usr/lib/libncursesw.so.5", RTLD_LAZY); + + if (!curses.handle) + return 0; + +#define bmLoadFunction(x) (curses.x = dlsym(curses.handle, #x)) + + if (!bmLoadFunction(initscr)) + goto function_pointer_exception; + if (!bmLoadFunction(endwin)) + goto function_pointer_exception; + if (!bmLoadFunction(refresh)) + goto function_pointer_exception; + if (!bmLoadFunction(get_wch)) + goto function_pointer_exception; + if (!bmLoadFunction(erase)) + goto function_pointer_exception; + if (!bmLoadFunction(mvprintw)) + goto function_pointer_exception; + if (!bmLoadFunction(move)) + goto function_pointer_exception; + if (!bmLoadFunction(init_pair)) + goto function_pointer_exception; + if (!bmLoadFunction(attroff)) + goto function_pointer_exception; + if (!bmLoadFunction(attron)) + goto function_pointer_exception; + if (!bmLoadFunction(start_color)) + goto function_pointer_exception; + if (!bmLoadFunction(getmaxx)) + goto function_pointer_exception; + if (!bmLoadFunction(getmaxy)) + goto function_pointer_exception; + if (!bmLoadFunction(keypad)) + goto function_pointer_exception; + if (!bmLoadFunction(ESCDELAY)) + goto function_pointer_exception; + +#undef bmLoadFunction + + api->getKey = _bmDrawCursesGetKey; + api->render = _bmDrawCursesRender; + api->free = _bmDrawCursesFree; + + return 1; + +function_pointer_exception: + _bmDrawCursesFree(); + return 0; +} /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/filter.c b/lib/filter.c new file mode 100644 index 0000000..b1cf668 --- /dev/null +++ b/lib/filter.c @@ -0,0 +1,64 @@ +/** + * @file filter.c + */ + +#include "internal.h" +#include +#include +#include + +/** + * Filter that mimics the vanilla dmenu filtering. + * + * @param menu bmMenu instance to filter. + * @param count unsigned int reference to filtered items count. + * @param selected unsigned int reference to new selected item index. + * @return Pointer to array of bmItem pointers. + */ +bmItem** _bmFilterDmenu(bmMenu *menu, unsigned int *count, unsigned int *selected) +{ + assert(menu != NULL); + assert(count != NULL); + assert(selected != NULL); + *count = *selected = 0; + + /* FIXME: not real dmenu like filtering at all */ + + bmItem **filtered = calloc(menu->itemsCount, sizeof(bmItem*)); + if (!filtered) + return NULL; + + unsigned int i, f; + for (f = i = 0; i < menu->itemsCount; ++i) { + bmItem *item = menu->items[i]; + if (item->text && strstr(item->text, menu->filter)) { + if (f == 0 || item == bmMenuGetSelectedItem(menu)) + *selected = f; + filtered[f++] = item; + } + } + + return _bmShrinkItemList(&filtered, menu->itemsCount, (*count = f)); +} + +/** + * Filter that mimics the vanilla case-insensitive dmenu filtering. + * + * @param menu bmMenu instance to filter. + * @param count unsigned int reference to filtered items count. + * @param selected unsigned int reference to new selected item index. + * @return Pointer to array of bmItem pointers. + */ +bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, unsigned int *count, unsigned int *selected) +{ + assert(menu != NULL); + assert(count != NULL); + assert(selected != NULL); + *count = *selected = 0; + + /* FIXME: stub */ + + return NULL; +} + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/internal.h b/lib/internal.h index c79ffd1..786e082 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -4,6 +4,10 @@ #include "bemenu.h" +#ifndef size_t +# include /* for size_t */ +#endif + /** * Internal bmItem struct that is not exposed to public. * Represents a single item in menu. @@ -17,7 +21,8 @@ struct _bmItem { * Renderers should be able to fill this one as they see fit. */ struct _bmRenderApi { - void (*render)(struct _bmItem **items, unsigned int nmemb); + bmKey (*getKey)(unsigned int *unicode); + void (*render)(const bmMenu *menu); void (*free)(void); }; @@ -25,10 +30,36 @@ struct _bmRenderApi { * Internal bmMenu struct that is not exposed to public. */ struct _bmMenu { - bmDrawMode drawMode; struct _bmRenderApi renderApi; - struct _bmItem **items; - unsigned int itemsCount; + struct _bmItem **items, **filteredItems; + char *title, filter[1024]; + unsigned int cursor, cursesCursor; + unsigned int itemsCount, allocatedCount; + unsigned int filteredCount; + unsigned int index; + bmFilterMode filterMode; + bmDrawMode drawMode; }; +/* draw/curses.c */ +int _bmDrawCursesInit(struct _bmRenderApi *api); + +/* menu.c */ +int _bmMenuShouldRenderItem(const bmMenu *menu, const bmItem *item); + +/* filter.c */ +bmItem** _bmFilterDmenu(bmMenu *menu, unsigned int *count, unsigned int *selected); +bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, unsigned int *count, unsigned int *selected); + +/* util.c */ +char* _bmStrdup(const char *s); +bmItem** _bmShrinkItemList(bmItem ***list, size_t osize, size_t nsize); +int _bmUtf8StringScreenWidth(const char *string); +size_t _bmUtf8RuneNext(const char *string, size_t start); +size_t _bmUtf8RunePrev(const char *string, size_t start); +size_t _bmUtf8RuneWidth(const char *rune, unsigned int u8len); +size_t _bmUtf8RuneRemove(char *string, size_t start, size_t *runeWidth); +size_t _bmUtf8RuneInsert(char *string, size_t bufSize, size_t start, const char *rune, unsigned int u8len, size_t *runeWidth); +size_t _bmUnicodeInsert(char *string, size_t bufSize, size_t start, unsigned int unicode, size_t *runeWidth); + /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/item.c b/lib/item.c new file mode 100644 index 0000000..40ebb52 --- /dev/null +++ b/lib/item.c @@ -0,0 +1,75 @@ +/** + * @file item.c + */ + +#include "internal.h" +#include +#include +#include + +/** + * Allocate a new item. + * + * @param text Pointer to null terminated C "string", can be NULL for empty text. + * @return bmItem instance. + */ +bmItem* bmItemNew(const char *text) +{ + bmItem *item = calloc(1, sizeof(bmItem)); + + if (!item) + return NULL; + + bmItemSetText(item, text); + return item; +} + +/** + * Release bmItem instance. + * + * @param item bmItem instance to be freed from memory. + */ +void bmItemFree(bmItem *item) +{ + assert(item != NULL); + + if (item->text) + free(item->text); + + free(item); +} + +/** + * Set text to bmItem instance. + * + * @param item bmItem instance where to set text. + * @param text C "string" to set as text, can be NULL for empty text. + */ +int bmItemSetText(bmItem *item, const char *text) +{ + assert(item != NULL); + + char *copy = NULL; + if (text && !(copy = _bmStrdup(text))) + return 0; + + if (item->text) + free(item->text); + + item->text = copy; + return 1; +} + +/** + * Get text from bmItem instance. + * + * @param item bmItem instance where to get text from. + * @return Pointer to null terminated C "string", can be NULL for empty text. + */ +const char* bmItemGetText(const bmItem *item) +{ + assert(item != NULL); + return item->text; +} + +/* vim: set ts=8 sw=4 tw=0 :*/ 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 +#include +#include +#include + +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 :*/ diff --git a/lib/util.c b/lib/util.c new file mode 100644 index 0000000..8c9bc0f --- /dev/null +++ b/lib/util.c @@ -0,0 +1,226 @@ +/** + * @file util.c + */ + +#include "internal.h" +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include + +/** + * Portable strdup. + * + * @param string C "string" to copy. + * @return Copy of the given C "string". + */ +char* _bmStrdup(const char *string) +{ + size_t len = strlen(string); + if (len == 0) + return NULL; + + void *copy = calloc(1, len + 1); + if (copy == NULL) + return NULL; + + return (char *)memcpy(copy, string, len); +} + +/** + * Shrink bmItem** list pointer. + * + * Useful helper function for filter functions. + * + * @param list Pointer to pointer to list of bmItem pointers. + * @param osize Current size of the list. + * @param nsize New size the list will be shrinked to. + * @return Pointer to list of bmItem pointers. + */ +bmItem** _bmShrinkItemList(bmItem ***list, size_t osize, size_t nsize) +{ + if (nsize >= osize) + return *list; + + void *tmp = malloc(sizeof(bmItem*) * nsize); + if (!tmp) + return *list; + + memcpy(tmp, *list, sizeof(bmItem*) * nsize); + free(*list); + *list = tmp; + return *list; +} + +/** + * Determite columns needed to display UTF8 string. + * + * @param string C "string" to determite. + * @return Number of columns, or -1 on failure. + */ +int _bmUtf8StringScreenWidth(const char *string) +{ + if (!string) + return 0; + + int num_char = mbstowcs(NULL, string, 0) + 1; + wchar_t *wstring = malloc((num_char + 1) * sizeof (wstring[0])); + + if (mbstowcs(wstring, string, num_char) == (size_t)(-1)) { + free(wstring); + return strlen(string); + } + + int length = wcswidth(wstring, num_char); + free(wstring); + return length; +} + +/** + * Figure out how many bytes to shift to next UTF8 rune. + * + * @param string C "string" which contains the runes. + * @param start Offset where to figure out next rune. (cursor) + * @return Number of bytes to next UTF8 rune. + */ +size_t _bmUtf8RuneNext(const char *string, size_t start) +{ + assert(string != NULL); + + size_t len = strlen(string), i = start; + if (len == 0 || len <= i || !*string) + return 0; + + while (++i < len && (string[i] & 0xc0) == 0x80); + return i - start; +} + +/** + * Figure out how many bytes to shift to previous UTF8 rune. + * + * @param string C "string" which contains the runes. + * @param start Offset where to figure out previous rune. (cursor) + * @return Number of bytes to previous UTF8 rune. + */ +size_t _bmUtf8RunePrev(const char *string, size_t start) +{ + assert(string != NULL); + + size_t len = strlen(string), i = start; + if (i == 0 || len < start || !*string) + return 0; + + while (--i > 0 && (string[i] & 0xc0) == 0x80); + return start - i; +} + +/** + * Figure out how many columns are needed to display UTF8 rune. + * + * @param rune Buffer which contains the rune. + * @param u8len Byte length of the rune. + * @return Number of columns, or -1 on failure. + */ +size_t _bmUtf8RuneWidth(const char *rune, unsigned int u8len) +{ + assert(rune != NULL); + char mb[5] = { 0, 0, 0, 0, 0 }; + memcpy(mb, rune, (u8len > 4 ? 4 : u8len)); + return _bmUtf8StringScreenWidth(mb); +} + +/** + * Remove previous UTF8 rune from buffer. + * + * @param string Null terminated C "string". + * @param start Start offset where to delete from. (cursor) + * @param runeWidth Reference to size_t, return number of columns for removed rune, or -1 on failure. + * @return Number of bytes removed from buffer. + */ +size_t _bmUtf8RuneRemove(char *string, size_t start, size_t *runeWidth) +{ + assert(string != NULL); + + if (runeWidth) + *runeWidth = 0; + + size_t len = strlen(string), oldStart = start; + if (len == 0 || len < start || !*string) + return 0; + + start -= _bmUtf8RunePrev(string, start); + + if (runeWidth) + *runeWidth = _bmUtf8RuneWidth(string + start, oldStart - start); + + memmove(string + start, string + oldStart, len - oldStart); + string[len - (oldStart - start)] = 0; + return (oldStart - start); +} + +/** + * Insert UTF8 rune to buffer. + * + * @param string Null terminated C "string". + * @param bufSize Size of the buffer. + * @param start Start offset where to insert to. (cursor) + * @param rune Buffer to insert to string. + * @param u8len Byte length of the rune. + * @param runeWidth Reference to size_t, return number of columns for inserted rune, or -1 on failure. + * @return Number of bytes inserted to buffer. + */ +size_t _bmUtf8RuneInsert(char *string, size_t bufSize, size_t start, const char *rune, unsigned int u8len, size_t *runeWidth) +{ + assert(string != NULL); + + if (runeWidth) + *runeWidth = 0; + + size_t len = strlen(string); + if (len + u8len >= bufSize) + return 0; + + if (u8len == 1 && iscntrl(*rune)) + return 0; + + char *str = string + start; + memmove(str + u8len, str, len - start); + memcpy(str, rune, u8len); + + if (runeWidth) + *runeWidth = _bmUtf8RuneWidth(rune, u8len); + return u8len; +} + +/** + * Insert unicode character to UTF8 buffer. + * + * @param string Null terminated C "string". + * @param bufSize Size of the buffer. + * @param start Start offset where to insert to. (cursor) + * @param unicode Unicode character to insert. + * @param runeWidth Reference to size_t, return number of columns for inserted rune, or -1 on failure. + * @return Number of bytes inserted to buffer. + */ +size_t _bmUnicodeInsert(char *string, size_t bufSize, size_t start, unsigned int unicode, size_t *runeWidth) +{ + assert(string != NULL); + + char u8len = ((unicode < 0x80) ? 1 : ((unicode < 0x800) ? 2 : ((unicode < 0x10000) ? 3 : 4))); + char mb[5] = { 0, 0, 0, 0 }; + + if (u8len == 1) { + mb[0] = unicode; + } else { + size_t i, j; + for (i = j = u8len; j > 1; --j) mb[j - 1] = 0x80 | (0x3F & (unicode >> ((i - j) * 6))); + mb[0] = (~0) << (8 - i); + mb[0] |= (unicode >> (i * 6 - 6)); + } + + return _bmUtf8RuneInsert(string, bufSize, start, mb, u8len, runeWidth); +} + +/* vim: set ts=8 sw=4 tw=0 :*/ -- cgit v1.2.3-70-g09d2 From a6d0413b972580f3bbfde8750090270b0d8d463e Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Thu, 10 Apr 2014 20:04:06 +0300 Subject: Implement list structure, and feature for multiple selections. --- client/client.c | 8 +- lib/CMakeLists.txt | 1 + lib/bemenu.h | 56 ++++++++++++-- lib/draw/curses.c | 8 +- lib/filter.c | 28 +++---- lib/internal.h | 64 ++++++++++----- lib/list.c | 135 ++++++++++++++++++++++++++++++++ lib/menu.c | 223 ++++++++++++++++++++++++++++++++--------------------- 8 files changed, 386 insertions(+), 137 deletions(-) create mode 100644 lib/list.c (limited to 'lib/CMakeLists.txt') diff --git a/client/client.c b/client/client.c index 4bdfdc6..ecaed25 100644 --- a/client/client.c +++ b/client/client.c @@ -101,10 +101,10 @@ int main(int argc, char **argv) } while ((status = bmMenuRunWithKey(menu, key, unicode)) == BM_RUN_RESULT_RUNNING); if (status == BM_RUN_RESULT_SELECTED) { - bmItem *item = bmMenuGetSelectedItem(menu); - - if (item) - printf("%s\n", bmItemGetText(item)); + unsigned int i, count; + bmItem **items = bmMenuGetSelectedItems(menu, &count); + for (i = 0; i < count; ++i) + printf("%s\n", bmItemGetText(items[i])); } bmMenuFree(menu); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index e669ef5..f53dfc6 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -3,6 +3,7 @@ SET(BEMENU_SOURCE menu.c item.c filter.c + list.c util.c draw/curses.c ) diff --git a/lib/bemenu.h b/lib/bemenu.h index f2f0372..250ad53 100644 --- a/lib/bemenu.h +++ b/lib/bemenu.h @@ -85,6 +85,7 @@ typedef enum bmKey { BM_KEY_TAB, BM_KEY_ESCAPE, BM_KEY_RETURN, + BM_KEY_SHIFT_RETURN, BM_KEY_UNICODE, BM_KEY_LAST } bmKey; @@ -200,24 +201,52 @@ int bmMenuRemoveItemAt(bmMenu *menu, unsigned int index); */ int bmMenuRemoveItem(bmMenu *menu, bmItem *item); + /** - * Get selected item from bmMenu instance. + * Get highlighted item from bmMenu instance. * - * @param menu bmMenu instance from where to get selected item. - * @return Selected bmItem instance, **NULL** if none selected. + * @param menu bmMenu instance from where to get highlighted item. + * @return Selected bmItem instance, **NULL** if none highlighted. */ -bmItem* bmMenuGetSelectedItem(const bmMenu *menu); +bmItem* bmMenuGetHighlightedItem(const bmMenu *menu); /** - * Get items from bmMenu instance. + * Highlight item in menu by index. * - * @warning The pointer returned by this function may be invalid after removing or adding new items. + * @param menu bmMenu instance from where to highlight item. + * @return 1 on successful highlight, 0 on failure. + */ +int bmMenuSetHighlightedIndex(bmMenu *menu, unsigned int index); + +/** + * Highlight item in menu. * - * @param menu bmMenu instance from where to get items. + * @param menu bmMenu instance from where to highlight item. + * @param item bmItem instance to highlight. + * @return 1 on successful highlight, 0 on failure. + */ +int bmMenuSetHighlighted(bmMenu *menu, bmItem *item); + +/** + * Get selected items from bmMenu instance. + * + * @param menu bmMenu instance from where to get selected items. * @param outNmemb 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 *outNmemb); +bmItem** bmMenuGetSelectedItems(const bmMenu *menu, unsigned int *outNmemb); + +/** + * Set selected items to bmMenu instance. + * + * @warning The list won't be copied. + * + * @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 bmMenuSetSelectedItems(bmMenu *menu, bmItem **items, unsigned int nmemb); /** * Get filtered (displayed) items from bmMenu instance. @@ -231,6 +260,17 @@ bmItem** bmMenuGetItems(const bmMenu *menu, unsigned int *outNmemb); */ bmItem** bmMenuGetFilteredItems(const bmMenu *menu, unsigned int *outNmemb); +/** + * Get items from bmMenu instance. + * + * @warning The pointer returned by this function may be invalid after removing or adding new items. + * + * @param menu bmMenu instance from where to get items. + * @param outNmemb 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 *outNmemb); + /** * Set items to bmMenu instance. * Will replace all the old items on bmMenu instance. diff --git a/lib/draw/curses.c b/lib/draw/curses.c index 0e19538..336b21d 100644 --- a/lib/draw/curses.c +++ b/lib/draw/curses.c @@ -120,8 +120,9 @@ static void _bmDrawCursesRender(const bmMenu *menu) unsigned int itemsCount; bmItem **items = bmMenuGetFilteredItems(menu, &itemsCount); for (i = (menu->index / (lines - 1)) * (lines - 1); i < itemsCount && cl < lines; ++i) { - int selected = (items[i] == bmMenuGetSelectedItem(menu)); - _bmDrawCursesDrawLine((selected ? 2 : 0), cl++, "%s%s", (selected ? ">> " : " "), items[i]->text); + int highlighted = (items[i] == bmMenuGetHighlightedItem(menu)); + int color = (highlighted ? 2 : (_bmMenuItemIsSelected(menu, items[i]) ? 1 : 0)); + _bmDrawCursesDrawLine(color, cl++, "%s%s", (highlighted ? ">> " : " "), items[i]->text); } curses.move(0, titleLen + menu->cursesCursor); @@ -200,6 +201,9 @@ static bmKey _bmDrawCursesGetKey(unsigned int *unicode) case 9: /* Tab */ return BM_KEY_TAB; + case 18: /* C-r */ + return BM_KEY_SHIFT_RETURN; + case 10: /* Return */ _bmDrawCursesEndWin(); return BM_KEY_RETURN; diff --git a/lib/filter.c b/lib/filter.c index 33126c0..4677e73 100644 --- a/lib/filter.c +++ b/lib/filter.c @@ -8,33 +8,33 @@ * * @param menu bmMenu instance to filter. * @param outNmemb unsigned int reference to filtered items outNmemb. - * @param outSelected unsigned int reference to new outSelected item index. + * @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 *outSelected) +bmItem** _bmFilterDmenu(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted) { assert(menu); assert(outNmemb); - assert(outSelected); - *outNmemb = *outSelected = 0; + assert(outHighlighted); + *outNmemb = *outHighlighted = 0; /* FIXME: not real dmenu like filtering at all */ - bmItem **filtered = calloc(menu->itemsCount, sizeof(bmItem*)); + bmItem **filtered = calloc(menu->items.count, sizeof(bmItem*)); if (!filtered) return NULL; unsigned int i, f; - for (f = i = 0; i < menu->itemsCount; ++i) { - bmItem *item = menu->items[i]; + for (f = i = 0; i < menu->items.count; ++i) { + bmItem *item = menu->items.list[i]; if (item->text && strstr(item->text, menu->filter)) { - if (f == 0 || item == bmMenuGetSelectedItem(menu)) - *outSelected = f; + if (f == 0 || item == bmMenuGetHighlightedItem(menu)) + *outHighlighted = f; filtered[f++] = item; } } - return _bmShrinkItemList(&filtered, menu->itemsCount, (*outNmemb = f)); + return _bmShrinkItemList(&filtered, menu->items.count, (*outNmemb = f)); } /** @@ -42,15 +42,15 @@ bmItem** _bmFilterDmenu(bmMenu *menu, unsigned int *outNmemb, unsigned int *outS * * @param menu bmMenu instance to filter. * @param outNmemb unsigned int reference to filtered items outNmemb. - * @param outSelected unsigned int reference to new outSelected item index. + * @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 *outSelected) +bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted) { assert(menu); assert(outNmemb); - assert(outSelected); - *outNmemb = *outSelected = 0; + assert(outHighlighted); + *outNmemb = *outHighlighted = 0; /* FIXME: stub */ diff --git a/lib/internal.h b/lib/internal.h index 66eabbc..d0a9848 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -38,6 +38,23 @@ struct _bmRenderApi { void (*free)(void); }; +struct _bmItemList { + /** + * Items in the list. + */ + struct _bmItem **list; + + /** + * Number of items. + */ + unsigned int count; + + /** + * Number of allocated items. + */ + unsigned int allocated; +}; + /** * Internal bmMenu struct that is not exposed to public. */ @@ -48,14 +65,19 @@ struct _bmMenu { struct _bmRenderApi renderApi; /** - * All items contained in menu instance. + * Items contained in menu instance. */ - struct _bmItem **items; + struct _bmItemList items; /** * Filtered/displayed items contained in menu instance. */ - struct _bmItem **filteredItems; + struct _bmItemList filtered; + + /** + * Selected items. + */ + struct _bmItemList selection; /** * Menu instance title. @@ -79,22 +101,7 @@ struct _bmMenu { unsigned int cursesCursor; /** - * Number of items in menu instance. - */ - unsigned int itemsCount; - - /** - * Number of filtered items in menu instance. - */ - unsigned int filteredCount; - - /** - * Number of allocated items in menu instance. - */ - unsigned int allocatedCount; - - /** - * Current filtered item index in menu instance. + * Current filtered/highlighted item index in menu instance. * This index is valid for the list returned by bmMenuGetFilteredItems. */ unsigned int index; @@ -113,9 +120,24 @@ struct _bmMenu { /* draw/curses.c */ int _bmDrawCursesInit(struct _bmRenderApi *api); +/* menu.c */ +int _bmMenuItemIsSelected(const bmMenu *menu, const bmItem *item); + /* filter.c */ -bmItem** _bmFilterDmenu(bmMenu *menu, unsigned int *outNmemb, unsigned int *outSelected); -bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, unsigned int *outNmemb, unsigned int *outSelected); +bmItem** _bmFilterDmenu(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted); +bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted); + +/* list.c */ +void _bmItemListFreeList(struct _bmItemList *list); +void _bmItemListFreeItems(struct _bmItemList *list); +bmItem** _bmItemListGetItems(const struct _bmItemList *list, unsigned int *outNmemb); +int _bmItemListSetItemsNoCopy(struct _bmItemList *list, bmItem **items, unsigned int nmemb); +int _bmItemListSetItems(struct _bmItemList *list, const bmItem **items, unsigned int nmemb); +int _bmItemListGrow(struct _bmItemList *list, unsigned int step); +int _bmItemListAddItemAt(struct _bmItemList *list, bmItem *item, unsigned int index); +int _bmItemListAddItem(struct _bmItemList *list, bmItem *item); +int _bmItemListRemoveItemAt(struct _bmItemList *list, unsigned int index); +int _bmItemListRemoveItem(struct _bmItemList *list, const bmItem *item); /* util.c */ char* _bmStrdup(const char *s); diff --git a/lib/list.c b/lib/list.c new file mode 100644 index 0000000..2123818 --- /dev/null +++ b/lib/list.c @@ -0,0 +1,135 @@ +#include "internal.h" +#include +#include +#include + +void _bmItemListFreeList(struct _bmItemList *list) +{ + assert(list); + + if (list->list) + free(list->list); + + list->allocated = list->count = 0; + list->list = NULL; +} + +void _bmItemListFreeItems(struct _bmItemList *list) +{ + assert(list); + + unsigned int i; + for (i = 0; i < list->count; ++i) + bmItemFree(list->list[i]); + + _bmItemListFreeList(list); +} + +bmItem** _bmItemListGetItems(const struct _bmItemList *list, unsigned int *outNmemb) +{ + assert(list); + + if (outNmemb) + *outNmemb = list->count; + + return list->list; +} + +int _bmItemListSetItemsNoCopy(struct _bmItemList *list, bmItem **items, unsigned int nmemb) +{ + assert(list); + + _bmItemListFreeList(list); + + if (!items || nmemb == 0) { + items = NULL; + nmemb = 0; + } + + list->list = items; + list->allocated = list->count = nmemb; + return 1; +} + +int _bmItemListSetItems(struct _bmItemList *list, const bmItem **items, unsigned int nmemb) +{ + assert(list); + + if (!items || nmemb == 0) { + _bmItemListFreeItems(list); + return 1; + } + + bmItem **newItems; + if (!(newItems = calloc(sizeof(bmItem*), nmemb))) + return 0; + + memcpy(newItems, items, sizeof(bmItem*) * nmemb); + return _bmItemListSetItemsNoCopy(list, newItems, nmemb); +} + +int _bmItemListGrow(struct _bmItemList *list, unsigned int step) +{ + assert(list); + + void *tmp; + unsigned int nsize = sizeof(bmItem*) * (list->allocated + step); + + if (!list->list || !(tmp = realloc(list->list, nsize))) { + if (!(tmp = malloc(nsize))) + return 0; + + if (list->list) + memcpy(tmp, list->list, sizeof(bmItem*) * list->allocated); + } + + list->list = tmp; + list->allocated += step; + memset(&list->list[list->count], 0, sizeof(bmItem*) * (list->allocated - list->count)); + return 1; +} + +int _bmItemListAddItemAt(struct _bmItemList *list, bmItem *item, unsigned int index) +{ + assert(list); + assert(item); + + if ((!list->list || list->allocated <= list->count) && !_bmItemListGrow(list, 32)) + return 0; + + if (index + 1 != list->count) { + unsigned int i = index; + memmove(&list->list[i + 1], &list->list[i], sizeof(bmItem*) * (list->count - i)); + } + + list->list[index] = item; + list->count++; + return 1; +} + +int _bmItemListAddItem(struct _bmItemList *list, bmItem *item) +{ + assert(list); + return _bmItemListAddItemAt(list, item, list->count); +} + +int _bmItemListRemoveItemAt(struct _bmItemList *list, unsigned int index) +{ + assert(list); + + unsigned int i = index; + if (!list->list || list->count <= i) + return 0; + + memmove(&list->list[i], &list->list[i], sizeof(bmItem*) * (list->count - i)); + return 1; +} + +int _bmItemListRemoveItem(struct _bmItemList *list, const bmItem *item) +{ + unsigned int i; + for (i = 0; i < list->count && list->list[i] != item; ++i); + return _bmItemListRemoveItemAt(list, i); +} + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/menu.c b/lib/menu.c index 3d41121..6274fe3 100644 --- a/lib/menu.c +++ b/lib/menu.c @@ -7,7 +7,7 @@ /** * Filter function map. */ -static bmItem** (*filterFunc[BM_FILTER_MODE_LAST])(bmMenu *menu, unsigned int *outNmemb, unsigned int *outSelected) = { +static bmItem** (*filterFunc[BM_FILTER_MODE_LAST])(bmMenu *menu, unsigned int *outNmemb, unsigned int *outHighlighted) = { _bmFilterDmenu, /* BM_FILTER_DMENU */ _bmFilterDmenuCaseInsensitive /* BM_FILTER_DMENU_CASE_INSENSITIVE */ }; @@ -16,37 +16,22 @@ static void _bmMenuFilter(bmMenu *menu) { assert(menu); - 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; + _bmItemListSetItemsNoCopy(&menu->filtered, filtered, count); menu->index = selected; } -static int _bmMenuGrowItems(bmMenu *menu) +int _bmMenuItemIsSelected(const bmMenu *menu, const bmItem *item) { - 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); - } + assert(menu); + assert(item); - menu->items = tmp; - menu->allocatedCount += step; - memset(&menu->items[menu->itemsCount], 0, sizeof(bmItem*) * (menu->allocatedCount - menu->itemsCount)); - return 1; + unsigned int i, count; + bmItem **items = bmMenuGetSelectedItems(menu, &count); + for (i = 0; i < count && items[i] != item; ++i); + return (i < count); } /** @@ -97,8 +82,8 @@ void bmMenuFree(bmMenu *menu) if (menu->title) free(menu->title); - if (menu->filteredItems) - free(menu->filteredItems); + if (menu->filtered.list) + free(menu->filtered.list); bmMenuFreeItems(menu); free(menu); @@ -112,14 +97,9 @@ void bmMenuFree(bmMenu *menu) void bmMenuFreeItems(bmMenu *menu) { assert(menu); - - 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; + _bmItemListFreeList(&menu->selection); + _bmItemListFreeList(&menu->filtered); + _bmItemListFreeItems(&menu->items); } /** @@ -195,19 +175,7 @@ const char* bmMenuGetTitle(const bmMenu *menu) int bmMenuAddItemAt(bmMenu *menu, bmItem *item, unsigned int index) { assert(menu); - assert(item); - - 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; + return _bmItemListAddItemAt(&menu->items, item, index);; } /** @@ -219,7 +187,7 @@ int bmMenuAddItemAt(bmMenu *menu, bmItem *item, unsigned int index) */ int bmMenuAddItem(bmMenu *menu, bmItem *item) { - return bmMenuAddItemAt(menu, item, menu->itemsCount); + return _bmItemListAddItem(&menu->items, item); } /** @@ -235,12 +203,18 @@ int bmMenuRemoveItemAt(bmMenu *menu, unsigned int index) { assert(menu); - unsigned int i = index; - if (i >= menu->itemsCount) + if (!menu->items.list || menu->items.count <= index) return 0; - memmove(&menu->items[i], &menu->items[i], sizeof(bmItem*) * (menu->itemsCount - i)); - return 1; + bmItem *item = menu->items.list[index]; + int ret = _bmItemListRemoveItemAt(&menu->items, index); + + if (ret) { + _bmItemListRemoveItem(&menu->selection, item); + _bmItemListRemoveItem(&menu->filtered, item); + } + + return ret; } /** @@ -255,20 +229,24 @@ int bmMenuRemoveItemAt(bmMenu *menu, unsigned int index) int bmMenuRemoveItem(bmMenu *menu, bmItem *item) { assert(menu); - assert(item); - unsigned int i; - for (i = 0; i < menu->itemsCount && menu->items[i] != item; ++i); - return bmMenuRemoveItemAt(menu, i); + int ret = _bmItemListRemoveItem(&menu->items, item); + + if (ret) { + _bmItemListRemoveItem(&menu->selection, item); + _bmItemListRemoveItem(&menu->filtered, item); + } + + return ret; } /** - * Get selected item from bmMenu instance. + * Get highlighted item from bmMenu instance. * - * @param menu bmMenu instance from where to get selected item. - * @return Selected bmItem instance, **NULL** if none selected. + * @param menu bmMenu instance from where to get highlighted item. + * @return Selected bmItem instance, **NULL** if none highlighted. */ -bmItem* bmMenuGetSelectedItem(const bmMenu *menu) +bmItem* bmMenuGetHighlightedItem(const bmMenu *menu) { assert(menu); @@ -282,22 +260,70 @@ bmItem* bmMenuGetSelectedItem(const bmMenu *menu) } /** - * Get items from bmMenu instance. + * Highlight item in menu by index. * - * @warning The pointer returned by this function may be invalid after removing or adding new items. + * @param menu bmMenu instance from where to highlight item. + * @return 1 on successful highlight, 0 on failure. + */ +int bmMenuSetHighlightedIndex(bmMenu *menu, unsigned int index) +{ + assert(menu); + unsigned int itemsCount = (menu->filtered.list ? menu->filtered.count : menu->items.count); + + if (itemsCount <= index) + return 0; + + return (menu->index = index); +} + +/** + * Highlight item in menu. * - * @param menu bmMenu instance from where to get items. + * @param menu bmMenu instance from where to highlight item. + * @param item bmItem instance to highlight. + * @return 1 on successful highlight, 0 on failure. + */ +int bmMenuSetHighlighted(bmMenu *menu, bmItem *item) +{ + assert(menu); + + unsigned int i, itemsCount; + bmItem **items = bmMenuGetFilteredItems(menu, &itemsCount); + for (i = 0; i < itemsCount && items[i] != item; ++i); + + if (itemsCount <= i) + return 0; + + return (menu->index = i); +} + +/** + * Get selected items from bmMenu instance. + * + * @param menu bmMenu instance from where to get selected items. * @param outNmemb 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 *outNmemb) +bmItem** bmMenuGetSelectedItems(const bmMenu *menu, unsigned int *outNmemb) { assert(menu); + return _bmItemListGetItems(&menu->selection, outNmemb); +} - if (outNmemb) - *outNmemb = menu->itemsCount; - - return menu->items; +/** + * Set selected items to bmMenu instance. + * + * @warning The list won't be copied. + * + * @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 bmMenuSetSelectedItems(bmMenu *menu, bmItem **items, unsigned int nmemb) +{ + assert(menu); + return _bmItemListSetItemsNoCopy(&menu->selection, items, nmemb); } /** @@ -314,10 +340,25 @@ bmItem** bmMenuGetFilteredItems(const bmMenu *menu, unsigned int *outNmemb) { assert(menu); - if (outNmemb) - *outNmemb = (menu->filteredItems ? menu->filteredCount : menu->itemsCount); + if (menu->filtered.list) + return _bmItemListGetItems(&menu->filtered, outNmemb); - return (menu->filteredItems ? menu->filteredItems : menu->items); + return _bmItemListGetItems(&menu->items, outNmemb); +} + +/** + * Get items from bmMenu instance. + * + * @warning The pointer returned by this function may be invalid after removing or adding new items. + * + * @param menu bmMenu instance from where to get items. + * @param outNmemb 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 *outNmemb) +{ + assert(menu); + return _bmItemListGetItems(&menu->items, outNmemb); } /** @@ -335,21 +376,14 @@ int bmMenuSetItems(bmMenu *menu, const bmItem **items, unsigned int nmemb) { assert(menu); - if (!items || nmemb == 0) { - bmMenuFreeItems(menu); - return 1; - } + int ret = _bmItemListSetItems(&menu->items, items, nmemb); - bmItem **newItems; - if (!(newItems = calloc(sizeof(bmItem*), nmemb))) - return 0; - - memcpy(newItems, items, sizeof(bmItem*) * nmemb); - bmMenuFreeItems(menu); + if (ret) { + _bmItemListFreeList(&menu->selection); + _bmItemListFreeList(&menu->filtered); + } - menu->items = newItems; - menu->allocatedCount = menu->itemsCount = nmemb; - return 1; + return ret; } /** @@ -400,7 +434,7 @@ bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode) { assert(menu); char *oldFilter = _bmStrdup(menu->filter); - unsigned int itemsCount = (menu->filteredItems ? menu->filteredCount : menu->itemsCount); + unsigned int itemsCount = (menu->filtered.list ? menu->filtered.count : menu->items.count); switch (key) { case BM_KEY_LEFT: @@ -502,9 +536,9 @@ bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode) case BM_KEY_TAB: { - bmItem *selected = bmMenuGetSelectedItem(menu); - if (selected && bmItemGetText(selected)) { - const char *text = bmItemGetText(selected); + bmItem *highlighted = bmMenuGetHighlightedItem(menu); + if (highlighted && bmItemGetText(highlighted)) { + const char *text = bmItemGetText(highlighted); size_t len = strlen(text); if (len > sizeof(menu->filter) - 1) @@ -518,6 +552,19 @@ bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode) } break; + case BM_KEY_SHIFT_RETURN: + case BM_KEY_RETURN: + { + bmItem *highlighted = bmMenuGetHighlightedItem(menu); + if (highlighted && !_bmMenuItemIsSelected(menu, highlighted)) + _bmItemListAddItem(&menu->selection, highlighted); + } + break; + + case BM_KEY_ESCAPE: + _bmItemListFreeList(&menu->selection); + break; + default: break; } -- cgit v1.2.3-70-g09d2 From 06972a357983a4686dc37a2aa5a8b0bfd2dd4e7e Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Sat, 12 Apr 2014 22:12:44 +0300 Subject: CLI interface, aka option parsing. --- client/client.c | 142 ++++++++++++++++++++++++++++++++++++++++++++++++++++- lib/CMakeLists.txt | 7 ++- lib/bemenu.h | 21 ++++++++ lib/library.c | 8 +++ lib/version.h.in | 3 ++ 5 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 lib/library.c create mode 100644 lib/version.h.in (limited to 'lib/CMakeLists.txt') diff --git a/client/client.c b/client/client.c index 2f95d52..fb94ea5 100644 --- a/client/client.c +++ b/client/client.c @@ -3,6 +3,140 @@ #include #include #include +#include + +static struct { + bmFilterMode filterMode; + int wrap; + unsigned int lines; + const char *title; + int selected; + int bottom; + int grab; + int monitor; +} client = { + BM_FILTER_MODE_DMENU, /* filterMode */ + 0, /* wrap */ + 0, /* lines */ + "bemenu", /* title */ + 0, /* selected */ + 0, /* bottom */ + 0, /* grab */ + 0 /* monitor */ +}; + +static void printVersion(const char *name) +{ + char *base = strrchr(name, '/'); + printf("%s v%s\n", (base ? base + 1 : name), bmVersion()); + puts(" Date: Sat, 12 Apr 2014 22:42:37 +0300 Subject: Improve CMakeLists and make stuff installable. --- client/CMakeLists.txt | 8 ++++++-- lib/CMakeLists.txt | 28 ++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) (limited to 'lib/CMakeLists.txt') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index aceaaf5..6af4e3e 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,7 +1,7 @@ # Sources SET(CLIENT_SOURCE client.c) -SET(CLIENT_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/../lib") -SET(CLIENT_LIBRARIES bemenu) +SET(CLIENT_INCLUDE ${BEMENU_INCLUDE_DIRS}) +SET(CLIENT_LIBRARIES ${BEMENU_LIBRARIES}) # Warnings IF (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) @@ -20,5 +20,9 @@ ENDIF () INCLUDE_DIRECTORIES(${CLIENT_INCLUDE}) ADD_EXECUTABLE(client ${CLIENT_SOURCE}) TARGET_LINK_LIBRARIES(client ${CLIENT_LIBRARIES}) +SET_TARGET_PROPERTIES(client PROPERTIES OUTPUT_NAME bemenu) + +# Install +INSTALL(TARGETS client DESTINATION bin) # vim: set ts=8 sw=4 tw=0 : diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index bfdb843..a52bc7b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -8,9 +8,8 @@ SET(BEMENU_SOURCE library.c draw/curses.c ) -SET(BEMENU_INCLUDE) -SET(BEMENU_LIBRARIES) +# Configure CONFIGURE_FILE(version.h.in version.h @ONLY) # Warnings @@ -26,9 +25,30 @@ IF (UNIX AND CMAKE_COMPILER_IS_GNUCC) ENDIF () ENDIF () +# Parse soversion version +STRING(REGEX MATCHALL "[0-9]+" VERSION_COMPONENTS ${BEMENU_VERSION}) +LIST(GET VERSION_COMPONENTS 0 SOVERSION) + # Compile INCLUDE_DIRECTORIES(${BEMENU_INCLUDE} ${CMAKE_CURRENT_BINARY_DIR}) -ADD_LIBRARY(bemenu ${BEMENU_SOURCE}) -TARGET_LINK_LIBRARIES(bemenu ${BEMENU_LIBRARIES} dl) +ADD_LIBRARY(bemenu SHARED ${BEMENU_SOURCE}) +SET_TARGET_PROPERTIES(bemenu PROPERTIES + VERSION ${BEMENU_VERSION} + SOVERSION ${SOVERSION}) +TARGET_LINK_LIBRARIES(bemenu dl) + +# Install +INSTALL(TARGETS bemenu DESTINATION lib) +INSTALL(FILES bemenu.h DESTINATION include) + +# Unexport +SET(BEMENU_INCLUDES) +SET(BEMENU_INCLUDE_DIRS) +SET(BEMENU_LIBRARIES) + +# Export +SET(BEMENU_INCLUDES "bemenu.h" CACHE STRING "bemenu includes exported from CMake") +SET(BEMENU_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} CACHE STRING "bemenu public header include path exported from CMake") +SET(BEMENU_LIBRARIES "bemenu" "dl" CACHE STRING "bemenu libraries exported from CMake") # vim: set ts=8 sw=4 tw=0 : -- cgit v1.2.3-70-g09d2