forked from RepoMirrors/bemenu
Basic working bemenu with curses backend
This commit is contained in:
parent
a3498b25f4
commit
67be25fbe4
@ -6,8 +6,83 @@
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include <bemenu.h>
|
||||
|
||||
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 :*/
|
||||
|
@ -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 :
|
||||
|
60
lib/bemenu.c
60
lib/bemenu.c
@ -1,60 +0,0 @@
|
||||
/**
|
||||
* @file bemenu.c
|
||||
*/
|
||||
|
||||
#include "internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
/**
|
||||
* 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 :*/
|
227
lib/bemenu.h
227
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 :*/
|
||||
|
@ -2,8 +2,260 @@
|
||||
* @file curses.c
|
||||
*/
|
||||
|
||||
/*
|
||||
* code goes here
|
||||
*/
|
||||
#include "../internal.h"
|
||||
#include <wchar.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <ncurses.h>
|
||||
#include <dlfcn.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* 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 :*/
|
||||
|
64
lib/filter.c
Normal file
64
lib/filter.c
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @file filter.c
|
||||
*/
|
||||
|
||||
#include "internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* 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 :*/
|
@ -4,6 +4,10 @@
|
||||
|
||||
#include "bemenu.h"
|
||||
|
||||
#ifndef size_t
|
||||
# include <stddef.h> /* 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 :*/
|
||||
|
75
lib/item.c
Normal file
75
lib/item.c
Normal file
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* @file item.c
|
||||
*/
|
||||
|
||||
#include "internal.h"
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* 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 :*/
|
527
lib/menu.c
Normal file
527
lib/menu.c
Normal file
@ -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 :*/
|
226
lib/util.c
Normal file
226
lib/util.c
Normal file
@ -0,0 +1,226 @@
|
||||
/**
|
||||
* @file util.c
|
||||
*/
|
||||
|
||||
#include "internal.h"
|
||||
#define _XOPEN_SOURCE 700
|
||||
#include <wchar.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
|
||||
/**
|
||||
* 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 :*/
|
Loading…
Reference in New Issue
Block a user