diff options
author | Jari Vetoniemi <mailroxas@gmail.com> | 2014-08-21 01:47:30 +0300 |
---|---|---|
committer | Jari Vetoniemi <mailroxas@gmail.com> | 2014-08-21 01:47:30 +0300 |
commit | f1bb87a808ff1383a2d27dc2c4a1f8812c470c31 (patch) | |
tree | 6669801544e51621c9269e5e89d3ae7a83c73ba8 | |
parent | 5bd81e8d384e82926a7671527e62aebafed1183f (diff) | |
parent | 536eee6d0bf8eb99b6566f7f4fd646b3cd76f532 (diff) | |
download | bemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.tar.gz bemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.tar.bz2 bemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.zip |
Merge branch 'develop'
31 files changed, 4534 insertions, 60 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index db0851d..ac22069 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,9 @@ CMAKE_MINIMUM_REQUIRED(VERSION 2.8) PROJECT(bemenu) +INCLUDE(CTest) SET(BEMENU_NAME "bemenu") SET(BEMENU_DESCRIPTION "Dynamic menu library and client program inspired by dmenu") +SET(BEMENU_VERSION "1.0.0") SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${chck_SOURCE_DIR}/CMake/modules) # Compile library @@ -14,6 +14,7 @@ Dynamic menu library and client program inspired by dmenu for C sources with following exceptions: * spaces not tabs * indentation size is 4 characters (spaces) + * function and variable names are camelCase except for global constants * [Standard style](http://legacy.python.org/dev/peps/pep-0008/) for Python * **Build system** - [CMake](http://www.cmake.org/) * **API documentation** - [Doxygen](http://www.stack.nl/~dimitri/doxygen/index.html) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 70ecd4b..6af4e3e 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -1,9 +1,7 @@ -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin) - # Sources SET(CLIENT_SOURCE client.c) -SET(CLIENT_INCLUDE) -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) @@ -22,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/client/client.c b/client/client.c index de5468e..54906c4 100644 --- a/client/client.c +++ b/client/client.c @@ -1,30 +1,269 @@ -/** - * @file client.c - * - * Sample client using the libbemenu. - * Also usable as dmenu replacement. - */ - +#define _XOPEN_SOURCE 500 #include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> +#include <signal.h> +#include <getopt.h> +#include <bemenu.h> + +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 discoTrap(int sig) +{ + (void)sig; + printf("\e[?25h\n"); + fflush(stdout); + exit(EXIT_FAILURE); +} + +static void disco(void) +{ + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + action.sa_handler = discoTrap; + sigaction(SIGABRT, &action, NULL); + sigaction(SIGSEGV, &action, NULL); + sigaction(SIGTRAP, &action, NULL); + sigaction(SIGINT, &action, NULL); + + unsigned int i, cc, c = 80; + printf("\e[?25l"); + while (1) { + for (i = 1; i < c - 1; ++i) { + printf("\r %*s%s %s %s ", (i > c / 2 ? c - i : i), " ", ((i % 2) ? "<o/" : "\\o>"), ((i % 4) ? "DISCO" : " "), ((i %2) ? "\\o>" : "<o/")); + for (cc = 0; cc < (i < c / 2 ? c / 2 - i : i - c / 2); ++cc) printf(((i % 2) ? "^" : "'")); + printf("%s %s \r %s %s", ((i % 2) ? "*" : "•"), ((i % 3) ? "\\o" : "<o"), ((i % 3) ? "o/" : "o>"), ((i % 2) ? "*" : "•")); + for (cc = 2; cc < (i > c / 2 ? c - i : i); ++cc) printf(((i % 2) ? "^" : "'")); + fflush(stdout); + usleep(140 * 1000); + } + } + printf("\e[?25h"); + exit(EXIT_SUCCESS); +} + +static void version(const char *name) +{ + char *base = strrchr(name, '/'); + printf("%s v%s\n", (base ? base + 1 : name), bmVersion()); + exit(EXIT_SUCCESS); +} + +static void usage(FILE *out, const char *name) +{ + char *base = strrchr(name, '/'); + fprintf(out, "usage: %s [options]\n", (base ? base + 1 : name)); + fputs("Options\n" + " -h, --help display this help and exit.\n" + " -v, --version display version.\n" + " -i, --ignorecase match items case insensitively.\n" + " -w, --wrap wraps cursor selection.\n" + " -l, --list list items vertically with the given number of lines.\n" + " -p, --prompt defines the prompt text to be displayed.\n" + " -I, --index select item at index automatically.\n\n" + + "Backend specific options\n" + " c = ncurses\n" // x == x11 + " (...) At end of help indicates the backend support for option.\n\n" + + " -b, --bottom appears at the bottom of the screen. ()\n" + " -f, --grab grabs the keyboard before reading stdin. ()\n" + " -m, --monitor index of monitor where menu will appear. ()\n" + " -fn, --fn defines the font to be used. ()\n" + " -nb, --nb defines the normal background color. ()\n" + " -nf, --nf defines the normal foreground color. ()\n" + " -sb, --sb defines the selected background color. ()\n" + " -sf, --sf defines the selected foreground color. ()\n", out); + exit((out == stderr ? EXIT_FAILURE : EXIT_SUCCESS)); +} + +static void parseArgs(int *argc, char **argv[]) +{ + static const struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'v' }, + + { "ignorecase", no_argument, 0, 'i' }, + { "wrap", no_argument, 0, 'w' }, + { "list", required_argument, 0, 'l' }, + { "prompt", required_argument, 0, 'p' }, + { "index", required_argument, 0, 'I' }, + + { "bottom", no_argument, 0, 'b' }, + { "grab", no_argument, 0, 'f' }, + { "monitor", required_argument, 0, 'm' }, + { "fn", required_argument, 0, 0x100 }, + { "nb", required_argument, 0, 0x101 }, + { "nf", required_argument, 0, 0x102 }, + { "sb", required_argument, 0, 0x103 }, + { "sf", required_argument, 0, 0x104 }, + + { "disco", no_argument, 0, 0x105 }, + { 0, 0, 0, 0 } + }; + + /* TODO: getopt does not support -sf, -sb etc.. + * Either break the interface and make them --sf, --sb (like they are now), + * or parse them before running getopt.. */ + + for (;;) { + int opt = getopt_long(*argc, *argv, "hviwl:I:p:I:bfm:", opts, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'h': + usage(stdout, *argv[0]); + break; + case 'v': + version(*argv[0]); + break; + + case 'i': + client.filterMode = BM_FILTER_MODE_DMENU_CASE_INSENSITIVE; + break; + case 'w': + client.wrap = 1; + break; + case 'l': + client.lines = strtol(optarg, NULL, 10); + break; + case 'p': + client.title = optarg; + break; + case 'I': + client.selected = strtol(optarg, NULL, 10); + break; + + case 'b': + client.bottom = 1; + break; + case 'f': + client.grab = 1; + break; + case 'm': + client.monitor = strtol(optarg, NULL, 10); + break; + + case 0x100: + case 0x101: + case 0x102: + case 0x103: + case 0x104: + break; + + case 0x105: + disco(); + break; + + case ':': + case '?': + fputs("\n", stderr); + usage(stderr, *argv[0]); + break; + } + } + + *argc -= optind; + *argv += optind; +} + +static void readItemsToMenuFromStdin(bmMenu *menu) +{ + assert(menu); + + size_t step = 1024, allocated; + char *buffer; + + if (!(buffer = malloc((allocated = step)))) + return; + + size_t read; + while ((read = fread(buffer + (allocated - step), 1, step, stdin)) == step) { + void *tmp; + if (!(tmp = realloc(buffer, (allocated += step)))) { + free(buffer); + return; + } + buffer = tmp; + } + buffer[allocated - step + read - 1] = 0; + + size_t pos; + char *s = buffer; + while ((pos = strcspn(s, "\n")) != 0) { + size_t next = pos + (s[pos] != 0); + s[pos] = 0; + + bmItem *item = bmItemNew(s); + if (!item) + break; + + bmMenuAddItem(menu, item); + s += next; + } + + free(buffer); +} -/** - * Main method - * - * This function gives and takes the life of our program. - * - * @param argc Number of arguments from command line - * @param argv Pointer to array of the arguments - * @return exit status of the program - */ int main(int argc, char **argv) { - (void)argc, (void)argv; + parseArgs(&argc, &argv); + + bmMenu *menu = bmMenuNew(BM_DRAW_MODE_CURSES); + if (!menu) + return EXIT_FAILURE; + + bmMenuSetTitle(menu, client.title); + bmMenuSetFilterMode(menu, client.filterMode); + bmMenuSetWrap(menu, client.wrap); + + readItemsToMenuFromStdin(menu); + + bmMenuSetHighlightedIndex(menu, client.selected); + + 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) { + unsigned int i, count; + bmItem **items = bmMenuGetSelectedItems(menu, &count); + for (i = 0; i < count; ++i) { + const char *text = bmItemGetText(items[i]); + printf("%s\n", (text ? text : "")); + } - /* - * code goes here - */ + if (!count && bmMenuGetFilter(menu)) + printf("%s\n", bmMenuGetFilter(menu)); + } - return EXIT_SUCCESS; + bmMenuFree(menu); + return (status == BM_RUN_RESULT_SELECTED ? EXIT_SUCCESS : EXIT_FAILURE); } /* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/doxygen/CMakeLists.txt b/doxygen/CMakeLists.txt index e1a364b..0d2c406 100644 --- a/doxygen/CMakeLists.txt +++ b/doxygen/CMakeLists.txt @@ -4,6 +4,8 @@ IF (DOXYGEN_FOUND) CONFIGURE_FILE(Doxyfile.in Doxyfile @ONLY) ADD_CUSTOM_TARGET(doxygen ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/doxygen-qmi-style/navtree ${CMAKE_CURRENT_BINARY_DIR}/html + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/doxygen-qmi-style/search ${CMAKE_CURRENT_BINARY_DIR}/html/search WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating bemenu documentation with Doxygen" VERBATIM) MESSAGE("-!- Use 'make doxygen' to generate documentation") diff --git a/doxygen/Doxyfile.in b/doxygen/Doxyfile.in index 292260d..b65debf 100644 --- a/doxygen/Doxyfile.in +++ b/doxygen/Doxyfile.in @@ -32,19 +32,19 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = @BEMENU_NAME@ +PROJECT_NAME = "@BEMENU_NAME@" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = "@BEMENU_VERSION@" # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = @BEMENU_DESCRIPTION@ +PROJECT_BRIEF = "@BEMENU_DESCRIPTION@" # With the PROJECT_LOGO tag one can specify an logo or icon that is included in # the documentation. The maximum height of the logo should not exceed 55 pixels @@ -526,7 +526,7 @@ INLINE_INFO = YES # name. If set to NO the members will appear in declaration order. # The default value is: YES. -SORT_MEMBER_DOCS = YES +SORT_MEMBER_DOCS = NO # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member @@ -743,7 +743,9 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = @CMAKE_CURRENT_SOURCE_DIR@/../lib @CMAKE_CURRENT_SOURCE_DIR@/../client +INPUT = "@CMAKE_CURRENT_SOURCE_DIR@/Mainpage.dox" \ + "@CMAKE_CURRENT_SOURCE_DIR@/../lib" \ + "@CMAKE_CURRENT_SOURCE_DIR@/../client" # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -763,7 +765,7 @@ INPUT_ENCODING = UTF-8 # *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, # *.qsf, *.as and *.js. -FILE_PATTERNS = +FILE_PATTERNS = *.h *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -794,7 +796,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = internal.h # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -1035,7 +1037,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = "@CMAKE_CURRENT_SOURCE_DIR@/doxygen-qmi-style/header.html" # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1045,7 +1047,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = "@CMAKE_CURRENT_SOURCE_DIR@/doxygen-qmi-style/footer.html" # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1057,7 +1059,7 @@ HTML_FOOTER = # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_STYLESHEET = +HTML_STYLESHEET = "@CMAKE_CURRENT_SOURCE_DIR@/doxygen-qmi-style/qmi.css" # The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- # defined cascading style sheet that is included after the standard style sheets @@ -1352,7 +1354,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. diff --git a/doxygen/Mainpage.dox b/doxygen/Mainpage.dox new file mode 100644 index 0000000..1b91ebf --- /dev/null +++ b/doxygen/Mainpage.dox @@ -0,0 +1,21 @@ +/** +@mainpage Main Page + +bemenu is a dynamic menu library and client program inspired by dmenu. +You can create flexible menu oriented programs using the library interface in your favorite language that has bemenu bindings available. + +Unlike old dmenu approach, with library you don't have to feed unneccessary metadata into client program and parse the result. +Instead your program will be aware of the items and possible metadata inside the menu. +It's also possible to use multiple menus or dynamically remove/insert items. + +Features: + - Multiple layouts + - Different filtering algorithms: + - Vanilla dmenu filtering + - Rendering backends (UI toolkits are loaded dynamically, not depended): + - Curses + +bemenu also provides 'bemenu' executable that is compatible with dmenu interface. + +Get started on the <a href="modules.html">Modules</a> page. +*/ diff --git a/doxygen/doxygen-qmi-style/README.md b/doxygen/doxygen-qmi-style/README.md new file mode 100644 index 0000000..7fb3e13 --- /dev/null +++ b/doxygen/doxygen-qmi-style/README.md @@ -0,0 +1,41 @@ +Qmi is a "**Q**t **Mi**nimal" theme for the Doxygen HTML documentation. +It based on official Qt4 documentation's style. + +# How to setup + +To use `qmi` style make the following changes in your Doxyfile: + + # Project section + BRIEF_MEMBER_DESC = NO + + # HTML section + HTML_HEADER = ${path_to_qmi}/header.html + HTML_FOOTER = ${path_to_qmi}/footer.html + HTML_STYLESHEET = ${path_to_qmi}/qmi.css + +**NOTE**: + +* If you use **_tree navigation panel_** then copy contents of the `navtree` dir to the documentation html dir. +* If you use **_search_** feature then copy contents of the `search` dir to the `html/search`. + +# Examples + +If you want to see `qmi` style in action then use the following links with examples: + +* [Qwt docs](http://skozlovf.github.com/doxygen-qmi-style/qwt) +* [libxml++ docs](http://skozlovf.github.com/doxygen-qmi-style/libxmlpp) (with tree navigation and search) + + +## Screenshots + +* **Main page**: + + ![](http://skozlovf.github.com/doxygen-qmi-style/shot1.png) + +* **Index page**: + + ![](http://skozlovf.github.com/doxygen-qmi-style/shot2.png) + +* **Member description**: + + ![](http://skozlovf.github.com/doxygen-qmi-style/shot3.png) diff --git a/doxygen/doxygen-qmi-style/footer.html b/doxygen/doxygen-qmi-style/footer.html new file mode 100644 index 0000000..14115b4 --- /dev/null +++ b/doxygen/doxygen-qmi-style/footer.html @@ -0,0 +1,20 @@ +<!-- HTML footer for doxygen 1.8.6--> +<!-- start footer part --> +<!--BEGIN GENERATE_TREEVIEW--> +<div id="nav-path" class="navpath"><!-- id is needed for treeview function! --> + <ul> + $navpath + <li class="footer">$generatedby + <span class="qmi"><a href="http://github.com/skozlovf/doxygen-qmi-style">qmi style</a> | </span> + <a href="http://www.doxygen.org/index.html">doxygen</a> $doxygenversion </li> + </ul> + </div> +<!--END GENERATE_TREEVIEW--> +<!--BEGIN !GENERATE_TREEVIEW--> +<hr class="footer"/><address class="footer"><small> +<span class="qmi"><a href="http://github.com/skozlovf/doxygen-qmi-style">qmi style</a></span> +$generatedby <a href="http://www.doxygen.org/index.html">doxygen</a> $doxygenversion +</small></address> +<!--END !GENERATE_TREEVIEW--> +</body> +</html> diff --git a/doxygen/doxygen-qmi-style/header.html b/doxygen/doxygen-qmi-style/header.html new file mode 100644 index 0000000..0b634ee --- /dev/null +++ b/doxygen/doxygen-qmi-style/header.html @@ -0,0 +1,55 @@ +<!-- HTML header for doxygen 1.8.6--> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> +<meta http-equiv="X-UA-Compatible" content="IE=9"/> +<meta name="generator" content="Doxygen $doxygenversion"/> +<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME--> +<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME--> +<link href="$relpath$qmi.css" rel="stylesheet" type="text/css" /> +<script type="text/javascript" src="$relpath^jquery.js"></script> +<script type="text/javascript" src="$relpath^dynsections.js"></script> +$treeview +$search +$mathjax +<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" /> +$extrastylesheet +</head> +<body> +<div id="top"><!-- do not remove this div, it is closed by doxygen! --> + +<!--BEGIN TITLEAREA--> +<div id="titlearea"> +<table cellspacing="0" cellpadding="0"> + <tbody> + <tr style="height: 56px;"> + <!--BEGIN PROJECT_LOGO--> + <td id="projectlogo"><img alt="Logo" src="$relpath$$projectlogo"/></td> + <!--END PROJECT_LOGO--> + <!--BEGIN PROJECT_NAME--> + <!--td style="padding-left: 0.5em;"--> + <td> + <div id="projectname">$projectname + <!--BEGIN PROJECT_NUMBER--><span id="projectnumber">$projectnumber</span><!--END PROJECT_NUMBER--> + </div> + <!--BEGIN PROJECT_BRIEF--><div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF--> + </td> + <!--END PROJECT_NAME--> + <!--BEGIN !PROJECT_NAME--> + <!--BEGIN PROJECT_BRIEF--> + <td style="padding-left: 0.5em;"> + <div id="projectbrief">$projectbrief</div> + </td> + <!--END PROJECT_BRIEF--> + <!--END !PROJECT_NAME--> + <!--BEGIN DISABLE_INDEX--> + <!--BEGIN SEARCHENGINE--> + <td>$searchbox</td> + <!--END SEARCHENGINE--> + <!--END DISABLE_INDEX--> + </tr> + </tbody> +</table> +</div> +<!--END TITLEAREA--> diff --git a/doxygen/doxygen-qmi-style/navtree/ftv2mlastnode.png b/doxygen/doxygen-qmi-style/navtree/ftv2mlastnode.png Binary files differnew file mode 100644 index 0000000..6dfeb5d --- /dev/null +++ b/doxygen/doxygen-qmi-style/navtree/ftv2mlastnode.png diff --git a/doxygen/doxygen-qmi-style/navtree/ftv2mnode.png b/doxygen/doxygen-qmi-style/navtree/ftv2mnode.png Binary files differnew file mode 100644 index 0000000..48e70f0 --- /dev/null +++ b/doxygen/doxygen-qmi-style/navtree/ftv2mnode.png diff --git a/doxygen/doxygen-qmi-style/navtree/ftv2plastnode.png b/doxygen/doxygen-qmi-style/navtree/ftv2plastnode.png Binary files differnew file mode 100644 index 0000000..2b99c65 --- /dev/null +++ b/doxygen/doxygen-qmi-style/navtree/ftv2plastnode.png diff --git a/doxygen/doxygen-qmi-style/navtree/ftv2pnode.png b/doxygen/doxygen-qmi-style/navtree/ftv2pnode.png Binary files differnew file mode 100644 index 0000000..02f42f7 --- /dev/null +++ b/doxygen/doxygen-qmi-style/navtree/ftv2pnode.png diff --git a/doxygen/doxygen-qmi-style/navtree/navtree.css b/doxygen/doxygen-qmi-style/navtree/navtree.css new file mode 100644 index 0000000..64fa6b5 --- /dev/null +++ b/doxygen/doxygen-qmi-style/navtree/navtree.css @@ -0,0 +1,117 @@ +#nav-tree .children_ul { + margin:0; + padding:4px; +} + +#nav-tree ul { + list-style:none outside none; + margin:0px; + padding:0px; +} + +#nav-tree li { + white-space:nowrap; + margin:0px; + padding:0px; +} + +#nav-tree .plus { + margin:0px; +} + +#nav-tree .selected { + background-image: none; + background-color: #B0B0B0; + color: #fff; +} + +#nav-tree img { + margin:0px; + padding:0px; + border:0px; + vertical-align: middle; +} + +#nav-tree a { + text-decoration:none; + padding:0px; + margin:0px; + outline:none; +} + +#nav-tree .label { + margin:0px; + padding:0px; +} + +#nav-tree .label a { + padding:2px; +} + +#nav-tree .selected a { + text-decoration:none; + padding:2px; + margin:0px; + color:#fff; +} + +#nav-tree .children_ul { + margin:0px; + padding:0px; +} + +#nav-tree .item { + margin:0px; + padding:0px; +} + +#nav-tree { + padding: 0px 0px; + background-image:none; + background-color: #F6F6F6; + font-size:14px; + overflow:auto; +} + +#doc-content { + overflow:auto; + display:block; + padding:0px; + margin:0px; +} + +#side-nav { + padding:0 4px 0 0; + margin: 0px; + display:block; + position: absolute; + left: 0px; + width: 300px; +} + +.ui-resizable .ui-resizable-handle { + display:block; +} + +.ui-resizable-e { + background: none; + background-color: #EBEBEB; + cursor:e-resize; + height:100%; + right:0; + top:0; + width:4px; +} + +.ui-resizable-handle { + display:none; + font-size:0.1px; + position:absolute; + z-index:1; +} + +#nav-tree-contents { + margin: 6px 0px 0px 0px; +} + + diff --git a/doxygen/doxygen-qmi-style/qmi.css b/doxygen/doxygen-qmi-style/qmi.css new file mode 100644 index 0000000..4f1a70b --- /dev/null +++ b/doxygen/doxygen-qmi-style/qmi.css @@ -0,0 +1,1033 @@ +/******************************************************************************* + * Qmi style css for the doxygen. + * Based on tabs.css + doxygen.css and Qt4 documentation look&feel. + * Sergey Kozlov. + ******************************************************************************/ + + +/*********************************************************** + * Globals. + **********************************************************/ +body +{ + background-color: white; + color: black; + margin: 0; +} + +body, table, div, p, dl +{ + font: normal 13px/1.2 Verdana; + color: #363534; +} + +/* Treeview. */ + +#nav-sync +{ + display:none; +} + +/* Header. */ + +#titlearea +{ + padding: 0 0 0 8px; + margin: 0px; +} + +#projectlogo +{ + text-align: center; + vertical-align: bottom; + border-collapse: separate; +} + +#projectlogo img +{ + border: 0px none; +} + +#projectname +{ + font-size: 24px; + font-weight: bold; +} + +#projectbrief +{ + color: #575757; + font-size: 0.9em; + margin: 0 0 10px 0; + padding: 0px; +} + +#projectnumber +{ + font-size: 0.5em; + margin: 0px; + padding: 0px; +} + +/* Footer. */ + +/* Bottom left label. */ +.qmi +{ + float:left; + text-align: left; + font-style: normal; + margin-left:10px; + padding-bottom: 6px; + color: #C2C2C2; +} + +.qmi a { color: #5E5E5E; } + +address { font-style: normal; } +address.footer +{ + text-align: right; + padding-right: 12px; + padding-bottom: 6px; +} + +hr.footer +{ + height: 1px; +} + + +/* Not used. */ +/* +img.footer +{ + border: 0px; + vertical-align: middle; +} +*/ + + +h1 { font-size: 150%; } +h2 { font-size: 120%; } +h3 { font-size: 100%; } +dt { font-weight: bold; } +dl { padding: 0 0 0 10px; } +dl.el { margin-left: -1cm; } + +hr +{ + height: 0px; + border: none; + border-top: 1px solid #C4C4C4; +} + +div.center +{ + text-align: center; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; +} + +div.center img { border: 0px; } + +/* Link to reference (classes etc). */ +a.el { font-weight: bold; } + +/** Use normal font on the index page. */ +.qindex + table a.el { font-weight: normal; } +/* a.elRef {} */ + +/* Links in code section. */ +a.code { font-weight: bold; } +a.codeRef { color: #4665A2; } + + +/* Code section */ +.fragment +{ + font-family: monospace, fixed; + font-size: 11px; +} + +pre.fragment +{ + padding: 4px 6px; + margin: 4px 20px 4px 10px; + overflow: auto; + word-wrap: break-word; + font-size: 9pt; + line-height: 125%; + + background-color: #F6F6F6; + border-color: #E6E6E6; + border-width: 1px; + border-style: solid; + -moz-border-radius: 7px 7px 7px 7px; + -webkit-border-radius: 7px 7px 7px 7px; + border-radius: 7px 7px 7px 7px; +} + + +/*********************************************************** + * Top navigation tabs. + * General info: + * navrow1 - div id of the first tab row. + * tabs, tabs2, tabs3 - are divs of of each tab row. + * tablist - list of tabs in a row. + * tablist li - tab contents. + **********************************************************/ + +#navrow1 { background-color: #EBEBEB; } + +.tabs, .tabs2, .tabs3 +{ + width: 100%; + z-index: 101; + font-size: 13px; +} + +.tabs2 +{ + font-size: 10px; +} + +.tabs3 { font-size: 9px; } + +.tablist +{ + margin: 0; + padding: 0; + display: table; + width: 100%; +} + +.tablist li +{ + float: left; + display: table-cell; + line-height: 24px; + list-style: none; +} + +.tablist a +{ + color: #00732F; + display: block; + padding: 0 20px 0 10px; +} + +.tabs3 .tablist a { padding: 0 10px; } + +/* Not used. */ +/* .tablist a:hover{} */ + +/** Currently selected tab. */ +.tablist li.current a { font-weight: bold; } + + + +/*********************************************************** + * Unsorted stuff. + **********************************************************/ + +div.multicol { + -moz-column-gap: 1em; + -webkit-column-gap: 1em; + -moz-column-count: 3; + -webkit-column-count: 3; +} + +p.startli, p.startdd, p.starttd { + margin-top: 2px; +} + +p.endli { + margin-bottom: 0px; +} + +p.enddd { + margin-bottom: 4px; +} + +p.endtd { + margin-bottom: 2px; +} + +/* @end */ + +caption { + font-weight: bold; +} + +span.legend { + font-size: 70%; + text-align: center; +} + +/* +h3.version { + font-size: 90%; + text-align: center; +} +*/ + +div.qindex, div.navtab{ + /*background-color: #EBEFF6; + border: 1px solid #A3B4D7;*/ + text-align: center; + background-color: #F6F6F6; + border-color: #E6E6E6; + border-width: 1px; + border-style: solid; + -moz-border-radius: 7px 7px 7px 7px; + -webkit-border-radius: 7px 7px 7px 7px; + border-radius: 7px 7px 7px 7px; + font-size: 16px; +} + +div.qindex, div.navpath { + width: 100%; + line-height: 140%; +} + +div.navtab { + margin-right: 15px; +} + +/* @group Link Styling */ + +a { + color: #00732F; + font-weight: normal; + text-decoration: none; +} + +.contents a:visited { + color: #005C26; +} + +a:hover { + text-decoration: underline; +} + +a.qindex { + font-weight: bold; +} + +a.qindexHL { + font-weight: bold; + background-color: #F6F6F6; + color: #ffffff; + border: 1px double #E6E6E6; +} + +.contents a.qindexHL:visited { + color: #ffffff; +} + + +/* @end */ + +/* Letter in the Class Index page */ +div.ah +{ + font-weight: bold; + width: 100%; +} + +div.groupHeader { + margin-left: 16px; + margin-top: 12px; + font-weight: bold; +} + +div.groupText { + margin-left: 16px; + font-style: italic; +} + +div.contents { + margin-top: 10px; + margin-left: 8px; + margin-right: 8px; +} + +td.indexkey { + background-color: #F6F6F6; + font-weight: bold; + margin: 2px 0px 2px 0; + padding: 2px 10px; +} + +td.indexvalue { + background-color: #F6F6F6; + padding: 2px 10px; + margin: 2px 0px; +} + +tr.memlist { + background-color: #F6F6F6; +} + +p.formulaDsp { + text-align: center; +} + +img.formulaDsp { + +} + +img.formulaInl { + vertical-align: middle; +} + + + +/* @group Code Colorization */ + +span.keyword { + color: #840000 +} + +span.keywordtype { + color: #604020 +} + +span.keywordflow { + color: #e08000 +} + +span.comment { + color: #800000 +} + +span.preprocessor { + color: #806020 +} + +span.stringliteral { + color: #002080 +} + +span.charliteral { + color: #008080 +} + +span.vhdldigit { + color: #ff00ff +} + +span.vhdlchar { + color: #000000 +} + +span.vhdlkeyword { + color: #700070 +} + +span.vhdllogic { + color: #ff0000 +} + +/* @end */ + +/* +.search { + color: #003399; + font-weight: bold; +} + +form.search { + margin-bottom: 0px; + margin-top: 0px; +} + +input.search { + font-size: 75%; + color: #000080; + font-weight: normal; + background-color: #e8eef2; +} +*/ + +td.tiny { + font-size: 75%; +} + +.dirtab { + padding: 4px; + border-collapse: collapse; + border: 1px solid #E6E6E6; +} + +th.dirtab { + background: #EBEFF6; + font-weight: bold; +} + + +/* @group Member Descriptions */ + +table.memberdecls { + border-spacing: 0px; + padding: 0px; +} + +.mdescLeft, .mdescRight, +.memItemLeft, .memItemRight, +.memTemplItemLeft, .memTemplItemRight, .memTemplParams { + background-color: #F6F6F6; + border: none; + margin: 4px; + padding: 1px 0 0 8px; +} + +.mdescLeft, .mdescRight { + padding: 0px 8px 4px 8px; + color: #555; +} +/* +.memItemLeft, .memItemRight, .memTemplParams { + border-top: 1px solid #C4CFE5; +} +*/ +.memItemLeft, .memTemplItemLeft { + white-space: nowrap; +} + +.memItemRight { + width: 100%; +} + +.memTemplParams { + color: #4665A2; + white-space: nowrap; +} + +/* @end */ + +/* @group Member Details */ + +/* Styles for detailed member documentation */ + +.memtemplate { + font-size: 80%; + color: #4665A2; + font-weight: normal; + margin-left: 9px; +} + +.memnav { + background-color: #F6F6F6; + border: 1px solid #E6E6E6; + text-align: center; + margin: 2px; + margin-right: 15px; + padding: 2px; +} + +.mempage { + width: 100%; +} + +.memitem { + padding: 0; + margin-bottom: 35px; + margin-right: 5px; +} + +.memname { + white-space: nowrap; + font-weight: bold; + margin-left: 6px; +} + +.memproto, dl.reflist dt { + /*border-top: 1px solid #A8B8D9; + border-left: 1px solid #A8B8D9; + border-right: 1px solid #A8B8D9;*/ + padding: 6px 0px 6px 0px; + /*color: #253555;*/ + font-weight: bold; + /*text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9);*/ + /* opera specific markup */ + /*box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + border-top-right-radius: 8px; + border-top-left-radius: 8px;*/ + /* firefox specific markup */ + /*-moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + -moz-border-radius-topright: 8px; + -moz-border-radius-topleft: 8px;*/ + /* webkit specific markup */ + /*-webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + -webkit-border-top-right-radius: 8px; + -webkit-border-top-left-radius: 8px; + background-image:url('nav_f.png'); + background-repeat:repeat-x;*/ + background-color: #F6F6F6; + border-color: #E6E6E6; + border-width: 1px; + border-style: solid; + -moz-border-radius: 7px 7px 7px 7px; + -webkit-border-radius: 7px 7px 7px 7px; + border-radius: 7px 7px 7px 7px; +} + +.memdoc, dl.reflist dd { + /*border-bottom: 1px solid #A8B8D9; + border-left: 1px solid #A8B8D9; + border-right: 1px solid #A8B8D9;*/ + padding: 0 5px; + /*background-color: #FBFCFD;*/ + border-top-width: 0; + /* opera specific markup */ + /*border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15);*/ + /* firefox specific markup */ + /*-moz-border-radius-bottomleft: 8px; + -moz-border-radius-bottomright: 8px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 5px; + background-image: -moz-linear-gradient(center top, #FFFFFF 0%, #FFFFFF 60%, #F7F8FB 95%, #EEF1F7);*/ + /* webkit specific markup */ + /*-webkit-border-bottom-left-radius: 8px; + -webkit-border-bottom-right-radius: 8px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); + background-image: -webkit-gradient(linear,center top,center bottom,from(#FFFFFF), color-stop(0.6,#FFFFFF), color-stop(0.60,#FFFFFF), color-stop(0.95,#F7F8FB), to(#EEF1F7));*/ +} + +dl.reflist dt { + padding: 5px; +} + +dl.reflist dd { + margin: 0px 0px 10px 0px; + padding: 5px; +} + +.paramkey { + text-align: right; +} + +.paramtype { + white-space: nowrap; +} + +.paramname { + color: #602020; + white-space: nowrap; +} +.paramname em { + font-style: normal; +} + +.params, .retval, .exception, .tparams { + border-spacing: 6px 2px; +} + +.params .paramname, .retval .paramname { + font-weight: bold; + vertical-align: top; +} + +.params .paramtype { + font-style: italic; + vertical-align: top; +} + +.params .paramdir { + font-family: "courier new",courier,monospace; + vertical-align: top; +} + + + + +/* @end */ + +/* @group Directory (tree) */ + +/* for the tree view */ + +.ftvtree { + /*font-family: sans-serif;*/ + margin: 0px; +} + +/* these are for tree view when used as main index */ + +.directory { + font-size: 9pt; + font-weight: bold; + margin: 5px; +} + +.directory h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} + +/* +The following two styles can be used to replace the root node title +with an image of your choice. Simply uncomment the next two styles, +specify the name of your image and be sure to set 'height' to the +proper pixel height of your image. +*/ + +/* +.directory h3.swap { + height: 61px; + background-repeat: no-repeat; + background-image: url("yourimage.gif"); +} +.directory h3.swap span { + display: none; +} +*/ + +.directory > h3 { + margin-top: 0; +} + +.directory p { + margin: 0px; + white-space: nowrap; +} + +.directory div { + display: none; + margin: 0px; +} + +.directory img { + vertical-align: -30%; +} + +/* these are for tree view when not used as main index */ + +.directory-alt { + font-size: 100%; + font-weight: bold; +} + +.directory-alt h3 { + margin: 0px; + margin-top: 1em; + font-size: 11pt; +} + +.directory-alt > h3 { + margin-top: 0; +} + +.directory-alt p { + margin: 0px; + white-space: nowrap; +} + +.directory-alt div { + display: none; + margin: 0px; +} + +.directory-alt img { + vertical-align: -30%; +} + +/* @end */ + +div.dynheader { + margin-top: 8px; +} + +table.doxtable { + border-collapse:collapse; +} + +table.doxtable td, table.doxtable th { + border: 1px solid #2D4068; + padding: 3px 7px 2px; +} + +table.doxtable th { + background-color: #374F7F; + color: #FFFFFF; + font-size: 110%; + padding-bottom: 4px; + padding-top: 5px; + text-align:left; +} + +table.fieldtable { + width: 100%; + margin-bottom: 10px; + border: 1px solid #A8B8D9; + border-spacing: 0px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: rgba(0, 0, 0, 0.15) 2px 2px 2px; + -webkit-box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); + box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.15); +} + +.fieldtable td, .fieldtable th { + padding: 3px 7px 2px; +} + +.fieldtable td.fieldtype, .fieldtable td.fieldname { + white-space: nowrap; + border-right: 1px solid #A8B8D9; + border-bottom: 1px solid #A8B8D9; + vertical-align: top; +} + +.fieldtable td.fielddoc { + border-bottom: 1px solid #A8B8D9; + width: 100%; +} + +.fieldtable tr:last-child td { + border-bottom: none; +} + +.fieldtable th { + background-image:url('nav_f.png'); + background-repeat:repeat-x; + background-color: #E2E8F2; + font-size: 90%; + color: #253555; + padding-bottom: 4px; + padding-top: 5px; + text-align:left; + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom: 1px solid #A8B8D9; +} + + +.tabsearch { + top: 0px; + left: 10px; + height: 36px; + background-image: url('tab_b.png'); + z-index: 101; + overflow: hidden; + font-size: 13px; +} + +.navpath ul +{ + font-size: 11px; + height:30px; + line-height:30px; + border-top: 1px solid #C4C4C4; + overflow:hidden; + margin:0px; + padding:0px; +} + +/** TODO: use image as marker. */ +.navpath li +{ + list-style-type:disc; + float:left; + padding-left:5px; + padding-right:5px; + margin-right: 25px; +} + +.navpath li.navelem a +{ + height:32px; + display:block; + text-decoration: none; + outline: none; +} + +.navpath li.navelem a:hover +{ + text-decoration: underline; +} + +.navpath li.footer +{ + list-style-type:none; + float:right; + padding-bottom: 6px; + background-image:none; + background-repeat:no-repeat; + background-position:right; + font-size: 8pt; +} + + +div.summary +{ + float: right; + font-size: 8pt; + padding-right: 5px; + width: 50%; + text-align: right; +} + +div.summary a +{ + white-space: nowrap; +} + +div.ingroups +{ + margin-left: 5px; + font-size: 8pt; + padding-left: 5px; + width: 50%; + text-align: left; +} + +div.ingroups a +{ + white-space: nowrap; +} + +div.header +{ + /*background-image:url('nav_h.png'); + background-repeat:repeat-x; + background-color: #F9FAFC;*/ + margin-top: 10px; + /*border-bottom: 1px solid #C4CFE5;*/ +} + +div.headertitle +{ + padding: 5px 5px 5px 7px; +} + + +dl.note, dl.warning, dl.attention, dl.pre, dl.post, dl.invariant, dl.deprecated, dl.todo, dl.test, dl.bug +{ + border-left:4px solid; + padding: 0 0 0 6px; +} + +dl.note +{ + border-color: #D0C000; +} + +dl.warning, dl.attention +{ + border-color: #FF0000; +} + +dl.pre, dl.post, dl.invariant +{ + border-color: #00D000; +} + +dl.deprecated +{ + border-color: #505050; +} + +dl.todo +{ + border-color: #00C0E0; +} + +dl.test +{ + border-color: #3030E0; +} + +dl.bug +{ + border-color: #C08050; +} + +.title +{ + font-size: 150%; + font-weight: bold; + margin: 10px 2px; +} + +.image +{ + text-align: center; +} + +.dotgraph +{ + text-align: center; +} + +.mscgraph +{ + text-align: center; +} + +.caption +{ + font-weight: bold; +} + +div.zoom +{ + border: 1px solid #E6E6E6; +} + +dl.citelist { + margin-bottom:50px; +} + +dl.citelist dt { + color:#334975; + float:left; + font-weight:bold; + margin-right:10px; + padding:5px; +} + +dl.citelist dd { + margin:2px 0; + padding:5px 0; +} + +@media print +{ + #top { display: none; } + #side-nav { display: none; } + #nav-path { display: none; } + body { overflow:visible; } + h1, h2, h3, h4, h5, h6 { page-break-after: avoid; } + .summary { display: none; } + .memitem { page-break-inside: avoid; } + #doc-content + { + margin-left:0 !important; + height:auto !important; + width:auto !important; + overflow:inherit; + display:inline; + } + pre.fragment + { + overflow: visible; + text-wrap: unrestricted; + white-space: -moz-pre-wrap; /* Moz */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + white-space: pre-wrap; /* CSS3 */ + word-wrap: break-word; /* IE 5.5+ */ + } +} + diff --git a/doxygen/doxygen-qmi-style/search/search.css b/doxygen/doxygen-qmi-style/search/search.css new file mode 100644 index 0000000..e5d5064 --- /dev/null +++ b/doxygen/doxygen-qmi-style/search/search.css @@ -0,0 +1,238 @@ +/*---------------- Search Box */ + +#FSearchBox { + float: left; +} + +#MSearchBox { + white-space : nowrap; + position: absolute; + float: none; + display: inline; + margin-top: 3px; + right: 0px; + width: 170px; + z-index: 102; +} + +#MSearchBox .left +{ + display:block; + position:absolute; + left:10px; + width:20px; + height:19px; + background:url('search_l.png') no-repeat; + background-position:right; +} + +#MSearchSelect { + display:block; + position:absolute; + width:20px; + height:19px; +} + +.left #MSearchSelect { + left:4px; +} + +.right #MSearchSelect { + right:5px; +} + +#MSearchField { + display:block; + position:absolute; + padding:0; + margin:0; + height:19px; + background:url('search_m.png') repeat-x; + border:none; + width:116px; + margin-left:20px; + padding-left:4px; + color:#909090; + outline:none; + font:9pt Arial, Verdana, sans-serif; +} + +#FSearchBox #MSearchField { + margin-left:15px; +} + +#MSearchBox .right { + display:block; + position:absolute; + right:10px; + width:20px; + height:19px; + background:url('search_r.png') no-repeat; + background-position:left; +} + +#MSearchClose { + display: none; + position: absolute; + top: 4px; + background : none; + border: none; + margin: 0px 4px 0px 0px; + padding: 0px 0px; + outline: none; +} + +.left #MSearchClose { + left: 6px; +} + +.right #MSearchClose { + right: 2px; +} + +.MSearchBoxActive #MSearchField { + color: #000000; +} + +/*---------------- Search filter selection */ + +#MSearchSelectWindow { + display: none; + position: absolute; + left: 0; top: 0; + border: 1px solid #E6E6E6; + background-color: #F6F6F6; + z-index: 1; + padding-top: 4px; + padding-bottom: 4px; + -moz-border-radius: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.15); +} + +.SelectItem { + font: 8pt Arial, Verdana, sans-serif; + padding-left: 2px; + padding-right: 12px; + border: 0px; +} + +span.SelectionMark { + margin-right: 4px; + font-family: monospace; + outline-style: none; + text-decoration: none; +} + +a.SelectItem { + display: block; + outline-style: none; + color: #000000; + text-decoration: none; + padding-left: 6px; + padding-right: 12px; +} + +a.SelectItem:focus, +a.SelectItem:active { + color: #000000; + outline-style: none; + text-decoration: none; +} + +a.SelectItem:hover { + color: #FFFFFF; + background-color: #B0B0B0; + outline-style: none; + text-decoration: none; + cursor: pointer; + display: block; +} + +/*---------------- Search results window */ + +iframe#MSearchResults { + width: 60ex; + height: 15em; +} + +#MSearchResultsWindow { + display: none; + position: absolute; + left: 0; top: 0; + border: 1px solid #8A8A8A; + background-color: #F6F6F6; +} + +/* ----------------------------------- */ + + +#SRIndex { + clear:both; + padding-bottom: 15px; +} + +.SREntry { + font-size: 10pt; + padding-left: 1ex; +} + +.SRPage .SREntry { + font-size: 8pt; + padding: 1px 5px; +} + +body.SRPage { + margin: 5px 2px; +} + +.SRChildren { + padding-left: 3ex; padding-bottom: .5em +} + +.SRPage .SRChildren { + display: none; +} + +.SRSymbol { + font-weight: bold; + color: #00732F; + font-family: Arial, Verdana, sans-serif; + text-decoration: none; + outline: none; +} + +a.SRScope { + display: block; + color: #00732F; + font-family: Arial, Verdana, sans-serif; + text-decoration: none; + outline: none; +} + +a.SRSymbol:focus, a.SRSymbol:active, +a.SRScope:focus, a.SRScope:active { + text-decoration: underline; +} + +span.SRScope { + padding-left: 4px; +} + +.SRPage .SRStatus { + padding: 2px 5px; + font-size: 8pt; + font-style: italic; +} + +.SRResult { + display: none; +} + +DIV.searchresults { + margin-left: 10px; + margin-right: 10px; +} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 46c532b..a52bc7b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,13 +1,16 @@ -SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY lib) -SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY lib) - # Sources SET(BEMENU_SOURCE - bemenu.c + menu.c + item.c + list.c + util.c + filter.c + library.c draw/curses.c ) -SET(BEMENU_INCLUDE) -SET(BEMENU_LIBRARIES) + +# Configure +CONFIGURE_FILE(version.h.in version.h @ONLY) # Warnings IF (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) @@ -22,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}) -ADD_LIBRARY(bemenu ${BEMENU_SOURCE}) -TARGET_LINK_LIBRARIES(bemenu ${BEMENU_LIBRARIES}) +INCLUDE_DIRECTORIES(${BEMENU_INCLUDE} ${CMAKE_CURRENT_BINARY_DIR}) +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 : diff --git a/lib/bemenu.c b/lib/bemenu.c deleted file mode 100644 index 2f92f27..0000000 --- a/lib/bemenu.c +++ /dev/null @@ -1,9 +0,0 @@ -/** - * @file bemenu.c - */ - -/* - * code goes here - */ - -/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/bemenu.h b/lib/bemenu.h new file mode 100644 index 0000000..5457cca --- /dev/null +++ b/lib/bemenu.h @@ -0,0 +1,484 @@ +/** + * @file bemenu.h + * + * Public API header. + */ + +typedef struct _bmMenu bmMenu; +typedef struct _bmItem bmItem; + +/** + * @defgroup Library + * @brief Library functions. + * + * Query library version, etc... + */ + +/** + * @defgroup Menu + * @brief Menu container. + * + * Holds all the items, runs logic and gets rendered. + */ + +/** + * @defgroup Item + * @brief Item container. + * + * Contains properties for visual representation of item. + */ + +/** + * @addtogroup Library + * @{ */ + +/** + * @name Library Version + * @{ */ + +/** + * Get version of the library in 'major.minor.patch' format. + * + * @see @link http://semver.org/ Semantic Versioning @endlink + * + * @return Null terminated C "string" to version string. + */ +const char* bmVersion(void); + +/** @} Library Version */ + +/** @} Library */ + +/** + * @addtogroup Menu + * @{ */ + +/** + * Draw mode constants for bmMenu instance draw mode. + * + * @link ::bmDrawMode BM_DRAW_MODE_LAST @endlink 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. + * + * @link ::bmFilterMode BM_FILTER_MODE_LAST @endlink 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. + * + * - @link ::bmRunResult BM_RUN_RESULT_RUNNING @endlink means that menu is running and thus should be still renderer && ran. + * - @link ::bmRunResult BM_RUN_RESULT_SELECTED @endlink means that menu was closed and items were selected. + * - @link ::bmRunResult BM_RUN_RESULT_CANCEL @endlink 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. + * + * @link ::bmKey BM_KEY_LAST @endlink 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_SHIFT_PAGE_UP, + BM_KEY_SHIFT_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_SHIFT_RETURN, + BM_KEY_CONTROL_RETURN, + BM_KEY_UNICODE, + BM_KEY_LAST +} bmKey; + +/** + * @name Menu Memory + * @{ */ + +/** + * 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); + +/** + * Release bmMenu instance. + * + * @param menu bmMenu instance to be freed from memory. + */ +void bmMenuFree(bmMenu *menu); + +/** + * Release items inside bmMenu instance. + * + * @param menu bmMenu instance which items will be freed from memory. + */ +void bmMenuFreeItems(bmMenu *menu); + +/** @} Menu Memory */ + +/** + * @name Menu Properties + * @{ */ + +/** + * Set userdata pointer to bmMenu instance. + * Userdata will be carried unmodified by the instance. + * + * @param menu bmMenu instance where to set userdata pointer. + * @param userdata Pointer to userdata. + */ +void bmMenuSetUserdata(bmMenu *menu, void *userdata); + +/** + * Get userdata pointer from bmMenu instance. + * + * @param menu bmMenu instance which userdata pointer to get. + * @return Pointer for unmodified userdata. + */ +void* bmMenuGetUserdata(bmMenu *menu); + +/** + * Set filter text to bmMenu instance. + * + * @param menu bmMenu instance where to set filter. + * @param filter Null terminated C "string" to act as filter. + */ +void bmMenuSetFilter(bmMenu *menu, const char *filter); + +/** + * Get filter text from bmMenu instance. + * + * @param menu bmMenu instance where to get filter. + * @return Const pointer to current filter text, may be **NULL** if empty. + */ +const char* bmMenuGetFilter(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 selection wrapping on/off. + * + * @param menu bmMenu instance where to toggle selection wrapping. + * @param int 1 == on, 0 == off. + */ +void bmMenuSetWrap(bmMenu *menu, int wrap); + +/** + * Get selection wrapping state. + * + * @param menu bmMenu instance where to get selection wrapping state. + * @return int for wrap state. + */ +int bmMenuGetWrap(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); + +/** @} Properties */ + +/** + * @name Menu Items + * @{ */ + +/** + * 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. + * + * @warning The item won't be freed, use bmItemFree to do that. + * + * @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. + * + * @warning 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); + +/** + * Highlight item in menu by index. + * + * @param menu bmMenu instance from where to highlight item. + * @param index Index of item to highlight. + * @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 highlight item. + * @param item bmItem instance to highlight. + * @return 1 on successful highlight, 0 on failure. + */ +int bmMenuSetHighlighted(bmMenu *menu, bmItem *item); + +/** + * Get highlighted item from bmMenu instance. + * + * @warning The pointer returned by this function may be invalid after items change. + * + * @param menu bmMenu instance from where to get highlighted item. + * @return Selected bmItem instance, **NULL** if none highlighted. + */ +bmItem* bmMenuGetHighlightedItem(const bmMenu *menu); + +/** + * Set selected items to 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 bmMenuSetSelectedItems(bmMenu *menu, bmItem **items, unsigned int nmemb); + +/** + * Get selected items from bmMenu instance. + * + * @warning The pointer returned by this function may be invalid after selection or items change. + * + * @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** bmMenuGetSelectedItems(const bmMenu *menu, unsigned int *outNmemb); + +/** + * 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); + +/** + * 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); + +/** + * 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 outNmemb 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 *outNmemb); + +/** @} Menu Items */ + +/** + * @name Menu Logic + * @{ */ + +/** + * Render bmMenu instance using chosen draw method. + * + * @param menu bmMenu instance to be rendered. + */ +void bmMenuRender(const bmMenu *menu); + +/** + * Trigger filtering of menu manually. + * This is useful when adding new items and want to dynamically see them filtered. + * + * Do note that filtering might be heavy, so you should only call it after batch manipulation of items. + * Not after manipulation of each single item. + * + * @param menu bmMenu instance which to filter. + */ +void bmMenuFilter(bmMenu *menu); + +/** + * Poll key and unicode from underlying UI toolkit. + * + * This function will block on @link ::bmDrawMode BM_DRAW_MODE_CURSES @endlink draw mode. + * + * @param menu bmMenu instance from which to poll. + * @param outUnicode Reference to unsigned int. + * @return bmKey for polled key. + */ +bmKey bmMenuGetKey(bmMenu *menu, unsigned int *outUnicode); + +/** + * Advances menu logic with key and unicode as input. + * + * @param menu bmMenu instance to be advanced. + * @param key Key input that will advance menu logic. + * @param unicode Unicode input that will advance menu logic. + * @return bmRunResult for menu state. + */ +bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode); + +/** @} Menu Logic */ + +/** @} Menu */ + +/** + * @addtogroup Item + * @{ */ + +/** + * @name Item Memory + * @{ */ + +/** + * 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); + +/** @} Item Memory */ + +/** + * @name Item Properties + * @{ */ + +/** + * Set userdata pointer to bmItem instance. + * Userdata will be carried unmodified by the instance. + * + * @param item bmItem instance where to set userdata pointer. + * @param userdata Pointer to userdata. + */ +void bmItemSetUserdata(bmItem *item, void *userdata); + +/** + * Get userdata pointer from bmItem instance. + * + * @param item bmItem instance which userdata pointer to get. + * @return Pointer for unmodified userdata. + */ +void* bmItemGetUserdata(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); + +/** @} Item Properties */ + +/** @} Item */ + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/draw/curses.c b/lib/draw/curses.c index c873496..a1f23ef 100644 --- a/lib/draw/curses.c +++ b/lib/draw/curses.c @@ -1,9 +1,498 @@ +#include "../internal.h" + +#if __APPLE__ +# define _C99_SOURCE +# include <stdio.h> /* vsnprintf */ +# undef _C99_SOURCE +#endif + +#define _XOPEN_SOURCE 500 +#include <signal.h> /* sigaction */ +#include <stdarg.h> /* vsnprintf */ +#undef _XOPEN_SOURCE + +#include <wchar.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <locale.h> +#include <ncurses.h> +#include <dlfcn.h> +#include <assert.h> + +#if _WIN32 +static const char *TTY = "CON"; +#else +static const char *TTY = "/dev/tty"; +#endif + +#if __APPLE__ + const char *DL_PATH[] = { + "libncursesw.5.dylib", + "libncurses.5.dylib", + NULL + }; +#elif _WIN32 +# error FIXME: Compile with windows... or use relative path? +#else + const char *DL_PATH[] = { + "libncursesw.so.5", + "libncurses.so.5", + NULL + }; +#endif + +/* these are implemented as macros in older curses */ +#ifndef NCURSES_OPAQUE +static int wrap_getmaxx(WINDOW *win) { return getmaxx(win); } +static int wrap_getmaxy(WINDOW *win) { return getmaxy(win); } +#endif + +/* ncurses.h likes to define stuff for us. + * This unforunately mangles with our struct. */ +#undef erase +#undef getch +#undef get_wch +#undef refresh +#undef mvprintw +#undef move +#undef init_pair +#undef attroff +#undef attron +#undef getmaxx +#undef getmaxy + /** - * @file curses.c + * Dynamically loaded curses API. */ +static struct curses { + struct sigaction abrtAction; + struct sigaction segvAction; + struct sigaction winchAction; + void *handle; + WINDOW *stdscr; + WINDOW* (*initscr)(void); + int (*endwin)(void); + int (*refresh)(void); + int (*erase)(void); + int (*getch)(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 (*use_default_colors)(void); + int (*getmaxx)(WINDOW *win); + int (*getmaxy)(WINDOW *win); + int (*keypad)(WINDOW *win, bool bf); + int (*curs_set)(int visibility); + int (*flushinp)(void); + int (*noecho)(void); + int (*raw)(void); + int *ESCDELAY; + int oldStdin; + int oldStdout; +} curses; -/* - * code goes here - */ +static int _bmDrawCursesResizeBuffer(char **buffer, size_t *osize, size_t nsize) +{ + assert(buffer); + assert(osize); + + if (nsize == 0 || nsize <= *osize) + return 0; + + void *tmp; + if (!*buffer || !(tmp = realloc(*buffer, nsize))) { + if (!(tmp = malloc(nsize))) + return 0; + + if (*buffer) { + memcpy(tmp, *buffer, *osize); + free(*buffer); + } + } + + *buffer = tmp; + *osize = nsize; + return 1; +} + +#if __GNUC__ +__attribute__((format(printf, 3, 4))) +#endif +static void _bmDrawCursesDrawLine(int pair, int y, const char *format, ...) +{ + static size_t blen = 0; + static char *buffer = NULL; + + size_t ncols = curses.getmaxx(curses.stdscr); + if (ncols <= 0) + return; + + va_list args; + va_start(args, format); + size_t nlen = vsnprintf(NULL, 0, format, args); + va_end(args); + + if ((!buffer || nlen > blen) && !_bmDrawCursesResizeBuffer(&buffer, &blen, nlen + 1)) + return; + + va_start(args, format); + vsnprintf(buffer, blen - 1, format, args); + va_end(args); + + size_t dw = 0, i = 0; + while (dw < ncols && i < nlen) { + if (buffer[i] == '\t') buffer[i] = ' '; + int next = _bmUtf8RuneNext(buffer, i); + dw += _bmUtf8RuneWidth(buffer + i, next); + i += (next ? next : 1); + } + + if (dw < ncols) { + /* line is too short, widen it */ + size_t offset = i + (ncols - dw); + if (blen <= offset && !_bmDrawCursesResizeBuffer(&buffer, &blen, offset + 1)) + return; + + memset(buffer + nlen, ' ', offset - nlen); + buffer[offset] = 0; + } else if (i < blen) { + /* line is too long, shorten it */ + i -= _bmUtf8RunePrev(buffer, i - (dw - ncols)) - 1; + size_t cc = dw - (dw - ncols); + + size_t offset = i - (dw - ncols) + (ncols - cc) + 1; + if (blen <= offset) { + int diff = offset - blen + 1; + if (!_bmDrawCursesResizeBuffer(&buffer, &blen, blen + diff)) + return; + } + + memset(buffer + i - (dw - ncols), ' ', (ncols - cc) + 1); + buffer[offset] = 0; + } + + if (pair > 0) + curses.attron(COLOR_PAIR(pair)); + + curses.mvprintw(y, 0, "%s", buffer); + + if (pair > 0) + curses.attroff(COLOR_PAIR(pair)); +} + +static void _bmDrawCursesRender(const bmMenu *menu) +{ + if (!curses.stdscr) { + curses.oldStdin = dup(STDIN_FILENO); + curses.oldStdout = dup(STDOUT_FILENO); + + freopen(TTY, "w", stdout); + freopen(TTY, "r", stdin); + + setlocale(LC_CTYPE, ""); + + if ((curses.stdscr = curses.initscr()) == NULL) + return; + + *curses.ESCDELAY = 25; + curses.flushinp(); + curses.keypad(curses.stdscr, true); + curses.curs_set(1); + curses.noecho(); + curses.raw(); + + curses.start_color(); + curses.use_default_colors(); + curses.init_pair(1, COLOR_BLACK, COLOR_RED); + curses.init_pair(2, COLOR_RED, -1); + } + + const unsigned int lines = curses.getmaxy(curses.stdscr); + curses.erase(); + + unsigned int ncols = curses.getmaxx(curses.stdscr); + unsigned int titleLen = (menu->title ? strlen(menu->title) + 1 : 0); + + if (titleLen >= ncols) + titleLen = 0; + + unsigned int ccols = ncols - titleLen - 1; + unsigned int dcols = 0, doffset = menu->cursor; + + while (doffset > 0 && dcols < ccols) { + int prev = _bmUtf8RunePrev(menu->filter, doffset); + dcols += _bmUtf8RuneWidth(menu->filter + doffset - prev, prev); + doffset -= (prev ? prev : 1); + } + + _bmDrawCursesDrawLine(0, 0, "%*s%s", titleLen, "", (menu->filter ? menu->filter + doffset : "")); + + if (menu->title && titleLen > 0) { + 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 highlighted = (items[i] == bmMenuGetHighlightedItem(menu)); + int color = (highlighted ? 2 : (_bmMenuItemIsSelected(menu, items[i]) ? 1 : 0)); + _bmDrawCursesDrawLine(color, cl++, "%s%s", (highlighted ? ">> " : " "), (items[i]->text ? items[i]->text : "")); + } + + curses.move(0, titleLen + (menu->cursesCursor < ccols ? menu->cursesCursor : ccols)); + curses.refresh(); +} + +static unsigned int _bmDrawCursesDisplayedCount(const bmMenu *menu) +{ + (void)menu; + return (curses.stdscr ? curses.getmaxy(curses.stdscr) : 0); +} + +static void _bmDrawCursesEndWin(void) +{ + if (!curses.stdscr) + return; + + freopen(TTY, "w", stdout); + + if (curses.refresh) + curses.refresh(); + + if (curses.endwin) + curses.endwin(); + + dup2(curses.oldStdin, STDIN_FILENO); + dup2(curses.oldStdout, STDOUT_FILENO); + close(curses.oldStdin); + close(curses.oldStdout); + + curses.stdscr = NULL; +} + +static bmKey _bmDrawCursesGetKey(unsigned int *unicode) +{ + assert(unicode); + *unicode = 0; + + if (!curses.stdscr) + return BM_KEY_NONE; + + if (curses.get_wch) + curses.get_wch((wint_t*)unicode); + else if (curses.getch) + *unicode = curses.getch(); + + switch (*unicode) { +#if KEY_RESIZE + case KEY_RESIZE: + return BM_KEY_NONE; +#endif + + 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 391: /* S-Home */ + case KEY_HOME: + return BM_KEY_HOME; + + case 5: /* C-e */ + case 386: /* S-End */ + case KEY_END: + return BM_KEY_END; + + case KEY_PPAGE: /* Page up */ + return BM_KEY_PAGE_UP; + + case KEY_NPAGE: /* Page down */ + return BM_KEY_PAGE_DOWN; + + case 550: /* C-Page up */ + case 398: /* S-Page up */ + return BM_KEY_SHIFT_PAGE_UP; + + case 545: /* C-Page down */ + case 396: /* S-Page down */ + return BM_KEY_SHIFT_PAGE_DOWN; + + case 8: /* C-h */ + case 127: /* Delete */ + case KEY_BACKSPACE: + return BM_KEY_BACKSPACE; + + case 4: /* C-d */ + case KEY_DC: + return BM_KEY_DELETE; + + case 383: /* S-Del */ + case 21: /* C-u */ + return BM_KEY_LINE_DELETE_LEFT; + + case 11: /* C-k */ + return BM_KEY_LINE_DELETE_RIGHT; + + case 23: /* C-w */ + return BM_KEY_WORD_DELETE; + + case 9: /* Tab */ + return BM_KEY_TAB; + + case 18: /* C-r */ + return BM_KEY_CONTROL_RETURN; + + case 20: /* C-t */ + case 331: /* Insert */ + _bmDrawCursesEndWin(); + return BM_KEY_SHIFT_RETURN; + + 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); + + sigaction(SIGABRT, &curses.abrtAction, NULL); + sigaction(SIGSEGV, &curses.segvAction, NULL); + sigaction(SIGWINCH, &curses.winchAction, NULL); + memset(&curses, 0, sizeof(curses)); +} + +static void _bmDrawCursesCrashHandler(int sig) +{ + (void)sig; + _bmDrawCursesFree(); +} + +static void _bmDrawCursesResizeHandler(int sig) +{ + (void)sig; + if (!curses.stdscr) + return; + + curses.endwin(); + curses.refresh(); +} + +int _bmDrawCursesInit(struct _bmRenderApi *api) +{ + memset(&curses, 0, sizeof(curses)); + const char *lib = NULL, *func = NULL; + + unsigned int i; + for (i = 0; DL_PATH[i] && !curses.handle; ++i) + curses.handle = dlopen((lib = DL_PATH[i]), RTLD_LAZY); + + if (!curses.handle) + return 0; + +#define bmLoadFunction(x) (curses.x = dlsym(curses.handle, (func = #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) && !bmLoadFunction(getch)) + 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(use_default_colors)) + goto function_pointer_exception; + if (!bmLoadFunction(keypad)) + goto function_pointer_exception; + if (!bmLoadFunction(curs_set)) + goto function_pointer_exception; + if (!bmLoadFunction(flushinp)) + goto function_pointer_exception; + if (!bmLoadFunction(noecho)) + goto function_pointer_exception; + if (!bmLoadFunction(raw)) + goto function_pointer_exception; + if (!bmLoadFunction(ESCDELAY)) + goto function_pointer_exception; + +#ifndef NCURSES_OPAQUE + curses.getmaxx = wrap_getmaxx; + curses.getmaxy = wrap_getmaxy; +#else + if (!bmLoadFunction(getmaxx)) + goto function_pointer_exception; + if (!bmLoadFunction(getmaxy)) + goto function_pointer_exception; +#endif + +#undef bmLoadFunction + + api->displayedCount = _bmDrawCursesDisplayedCount; + api->getKey = _bmDrawCursesGetKey; + api->render = _bmDrawCursesRender; + api->free = _bmDrawCursesFree; + + struct sigaction action; + memset(&action, 0, sizeof(struct sigaction)); + action.sa_handler = _bmDrawCursesCrashHandler; + sigaction(SIGABRT, &action, &curses.abrtAction); + sigaction(SIGSEGV, &action, &curses.segvAction); + + action.sa_handler = _bmDrawCursesResizeHandler; + sigaction(SIGWINCH, &action, &curses.winchAction); + return 1; + +function_pointer_exception: + fprintf(stderr, "-!- Could not load function '%s' from '%s'\n", func, lib); + _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..04733c5 --- /dev/null +++ b/lib/filter.c @@ -0,0 +1,188 @@ +#include "internal.h" +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +/** + * 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. + */ +static bmItem** _bmFilterShrinkList(bmItem ***inOutList, size_t osize, size_t nsize) +{ + assert(inOutList); + + if (nsize == 0) { + free(*inOutList); + return (*inOutList = NULL); + } + + if (nsize >= osize) + return *inOutList; + + void *tmp = malloc(sizeof(bmItem*) * nsize); + if (!tmp) + return *inOutList; + + memcpy(tmp, *inOutList, sizeof(bmItem*) * nsize); + free(*inOutList); + return (*inOutList = tmp); +} + +/** + * Text filter tokenizer helper. + * + * @param menu bmMenu instance which filter to tokenize. + * @param outTokv char pointer reference to list of tokens, this should be freed after use. + * @param outTokc unsigned int reference to number of tokens. + * @return Pointer to buffer that contains tokenized string, this should be freed after use. + */ +static char* _bmFilterTokenize(bmMenu *menu, char ***outTokv, unsigned int *outTokc) +{ + assert(menu); + assert(outTokv); + assert(outTokc); + *outTokv = NULL; + *outTokc = 0; + + char **tokv = NULL, *buffer = NULL; + if (!(buffer = _bmStrdup(menu->filter))) + goto fail; + + char *s; + for (s = buffer; *s && *s == ' '; ++s); + + char **tmp = NULL; + size_t pos = 0, next; + unsigned int tokc = 0, tokn = 0; + for (; (pos = _bmStripToken(s, " ", &next)) > 0; tokv = tmp) { + if (++tokc > tokn && !(tmp = realloc(tokv, ++tokn * sizeof(char*)))) + goto fail; + + tmp[tokc - 1] = s; + s += next; + } + + *outTokv = tmp; + *outTokc = tokc; + return buffer; + +fail: + if (buffer) + free(buffer); + if (tokv) + free(tokv); + return NULL; +} + +/** + * Dmenu filterer that accepts substring function. + * + * @param menu bmMenu instance to filter. + * @param addition This will be 1, if filter is same as previous filter with something appended. + * @param fstrstr Substring function used to match items. + * @param outNmemb unsigned int reference to filtered items outNmemb. + * @return Pointer to array of bmItem pointers. + */ +bmItem** _bmFilterDmenuFun(bmMenu *menu, char addition, char* (*fstrstr)(const char *a, const char *b), int (*fstrncmp)(const char *a, const char *b, size_t len), unsigned int *outNmemb) +{ + assert(menu); + assert(fstrstr); + assert(fstrncmp); + assert(outNmemb); + *outNmemb = 0; + + unsigned int itemsCount; + bmItem **items; + + if (addition) { + items = bmMenuGetFilteredItems(menu, &itemsCount); + } else { + items = bmMenuGetItems(menu, &itemsCount); + } + + char *buffer = NULL; + bmItem **filtered = calloc(itemsCount, sizeof(bmItem*)); + if (!filtered) + goto fail; + + char **tokv; + unsigned int tokc; + if (!(buffer = _bmFilterTokenize(menu, &tokv, &tokc))) + goto fail; + + size_t len = (tokc ? strlen(tokv[0]) : 0); + unsigned int i, f, e; + for (e = f = i = 0; i < itemsCount; ++i) { + bmItem *item = items[i]; + if (!item->text && tokc != 0) + continue; + + if (tokc && item->text) { + unsigned int t; + for (t = 0; t < tokc && fstrstr(item->text, tokv[t]); ++t); + if (t < tokc) + continue; + } + + if (tokc && item->text && !fstrncmp(tokv[0], item->text, len + 1)) { /* exact matches */ + memmove(&filtered[1], filtered, f * sizeof(bmItem*)); + filtered[0] = item; + e++; /* where do exact matches end */ + } else if (tokc && item->text && !fstrncmp(tokv[0], item->text, len)) { /* prefixes */ + memmove(&filtered[e + 1], &filtered[e], (f - e) * sizeof(bmItem*)); + filtered[e] = item; + e++; /* where do exact matches end */ + } else { + filtered[f] = item; + } + f++; /* where do all matches end */ + } + + if (buffer) + free(buffer); + if (tokv) + free(tokv); + + return _bmFilterShrinkList(&filtered, menu->items.count, (*outNmemb = f)); + +fail: + if (filtered) + free(filtered); + if (buffer) + free(buffer); + return NULL; +} + +/** + * Filter that mimics the vanilla dmenu filtering. + * + * @param menu bmMenu instance to filter. + * @param addition This will be 1, if filter is same as previous filter with something appended. + * @param outNmemb unsigned int reference to filtered items outNmemb. + * @return Pointer to array of bmItem pointers. + */ +bmItem** _bmFilterDmenu(bmMenu *menu, char addition, unsigned int *outNmemb) +{ + return _bmFilterDmenuFun(menu, addition, strstr, strncmp, outNmemb); +} + +/** + * Filter that mimics the vanilla case-insensitive dmenu filtering. + * + * @param menu bmMenu instance to filter. + * @param addition This will be 1, if filter is same as previous filter with something appended. + * @param outNmemb unsigned int reference to filtered items outNmemb. + * @return Pointer to array of bmItem pointers. + */ +bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, char addition, unsigned int *outNmemb) +{ + return _bmFilterDmenuFun(menu, addition, _bmStrupstr, _bmStrnupcmp, outNmemb); +} + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/internal.h b/lib/internal.h new file mode 100644 index 0000000..2fc9393 --- /dev/null +++ b/lib/internal.h @@ -0,0 +1,188 @@ +#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. + */ +struct _bmItem { + /** + * Userdata pointer. + * This pointer will be passed around with the item untouched. + */ + void *userdata; + + /** + * Primary text shown on item as null terminated C "string". + * Matching will be done against this text as well. + */ + char *text; +}; + +/** + * Internal bmRenderApi struct. + * Renderers should be able to fill this one as they see fit. + */ +struct _bmRenderApi { + /** + * Get count of displayed items by the underlying renderer. + */ + unsigned int (*displayedCount)(const bmMenu *menu); + + /** + * If the underlying renderer is a UI toolkit. (curses, etc...) + * There might be possibility to get user input, and this should be thus implemented. + */ + bmKey (*getKey)(unsigned int *unicode); + + /** + * Tells underlying renderer to draw the menu. + */ + void (*render)(const bmMenu *menu); + + /** + * Release underlying renderer. + */ + 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. + */ +struct _bmMenu { + /** + * Userdata pointer. + * This pointer will be passed around with the menu untouched. + */ + void *userdata; + + /** + * Underlying renderer access. + */ + struct _bmRenderApi renderApi; + + /** + * Items contained in menu instance. + */ + struct _bmItemList items; + + /** + * Filtered/displayed items contained in menu instance. + */ + struct _bmItemList filtered; + + /** + * Selected items. + */ + struct _bmItemList selection; + + /** + * Menu instance title. + */ + char *title; + + /** + * Text used to filter matches. + */ + char *filter; + + /** + * Used as optimization. + */ + char *oldFilter; + + /** + * Size of filter buffer + */ + size_t filterSize; + + /** + * Current byte offset on filter text. + */ + unsigned int cursor; + + /** + * Current column/cursor position on filter text. + */ + unsigned int cursesCursor; + + /** + * Current filtered/highlighted item index in menu instance. + * This index is valid for the list returned by bmMenuGetFilteredItems. + */ + unsigned int index; + + /** + * Current filtering method in menu instance. + */ + bmFilterMode filterMode; + + /** + * Drawing mode used in menu instance. + */ + bmDrawMode drawMode; + + /** + * Should selection be wrapped? + */ + char wrap; +}; + +/* draw/curses.c */ +int _bmDrawCursesInit(struct _bmRenderApi *api); + +/* menu.c */ +int _bmMenuItemIsSelected(const bmMenu *menu, const bmItem *item); + +/* filter.c */ +bmItem** _bmFilterDmenu(bmMenu *menu, char addition, unsigned int *outNmemb); +bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, char addition, unsigned int *outNmemb); + +/* 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); +size_t _bmStripToken(char *string, const char *token, size_t *outNext); +int _bmStrupcmp(const char *hay, const char *needle); +int _bmStrnupcmp(const char *hay, const char *needle, size_t len); +char* _bmStrupstr(const char *hay, const char *needle); +bmItem** _bmShrinkItemList(bmItem ***inOutList, 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 *outRuneWidth); +size_t _bmUtf8RuneInsert(char **string, size_t *bufSize, size_t start, const char *rune, unsigned int u8len, size_t *outRuneWidth); +size_t _bmUnicodeInsert(char **string, size_t *bufSize, size_t start, unsigned int unicode, size_t *outRuneWidth); + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/item.c b/lib/item.c new file mode 100644 index 0000000..f2a879a --- /dev/null +++ b/lib/item.c @@ -0,0 +1,96 @@ +#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 for new item instance, **NULL** if creation failed. + */ +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); + + if (item->text) + free(item->text); + + free(item); +} + +/** + * Set userdata pointer to bmItem instance. + * Userdata will be carried unmodified by the instance. + * + * @param item bmItem instance where to set userdata pointer. + * @param userdata Pointer to userdata. + */ +void bmItemSetUserdata(bmItem *item, void *userdata) +{ + assert(item); + item->userdata = userdata; +} + +/** + * Get userdata pointer from bmItem instance. + * + * @param item bmItem instance which userdata pointer to get. + * @return Pointer for unmodified userdata. + */ +void* bmItemGetUserdata(bmItem *item) +{ + assert(item); + return item->userdata; +} + +/** + * 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); + + 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); + return item->text; +} + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/library.c b/lib/library.c new file mode 100644 index 0000000..f432eda --- /dev/null +++ b/lib/library.c @@ -0,0 +1,15 @@ +#include "version.h" + +/** + * Get version of the library in 'major.minor.patch' format. + * + * @see @link http://semver.org/ Semantic Versioning @endlink + * + * @return Null terminated C "string" to version string. + */ +const char *bmVersion(void) +{ + return BM_VERSION; +} + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/list.c b/lib/list.c new file mode 100644 index 0000000..203adf2 --- /dev/null +++ b/lib/list.c @@ -0,0 +1,139 @@ +#include "internal.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +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; +} + +/** !!! Frees the old list, not items !!! */ +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; +} + +/** !!! Frees the old items and list !!! */ +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); + free(list->list); + } + } + + 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 new file mode 100644 index 0000000..b7ab021 --- /dev/null +++ b/lib/menu.c @@ -0,0 +1,727 @@ +#include "internal.h" +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> + +/** + * Filter function map. + */ +static bmItem** (*filterFunc[BM_FILTER_MODE_LAST])(bmMenu *menu, char addition, unsigned int *outNmemb) = { + _bmFilterDmenu, /* BM_FILTER_DMENU */ + _bmFilterDmenuCaseInsensitive /* BM_FILTER_DMENU_CASE_INSENSITIVE */ +}; + +int _bmMenuItemIsSelected(const bmMenu *menu, const bmItem *item) +{ + assert(menu); + assert(item); + + unsigned int i, count; + bmItem **items = bmMenuGetSelectedItems(menu, &count); + for (i = 0; i < count && items[i] != item; ++i); + return (i < count); +} + +/** + * 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); + + if (menu->renderApi.free) + menu->renderApi.free(); + + if (menu->title) + free(menu->title); + + if (menu->filter) + free(menu->filter); + + if (menu->oldFilter) + free(menu->oldFilter); + + 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); + _bmItemListFreeList(&menu->selection); + _bmItemListFreeList(&menu->filtered); + _bmItemListFreeItems(&menu->items); +} + +/** + * Set userdata pointer to bmMenu instance. + * Userdata will be carried unmodified by the instance. + * + * @param menu bmMenu instance where to set userdata pointer. + * @param userdata Pointer to userdata. + */ +void bmMenuSetUserdata(bmMenu *menu, void *userdata) +{ + assert(menu); + menu->userdata = userdata; +} + +/** + * Get userdata pointer from bmMenu instance. + * + * @param menu bmMenu instance which userdata pointer to get. + * @return Pointer for unmodified userdata. + */ +void* bmMenuGetUserdata(bmMenu *menu) +{ + assert(menu); + return menu->userdata; +} + +/** + * Set filter text to bmMenu instance. + * + * @param menu bmMenu instance where to set filter. + * @param filter Null terminated C "string" to act as filter. + */ +void bmMenuSetFilter(bmMenu *menu, const char *filter) +{ + assert(menu); + + if (menu->filter) + free(menu->filter); + + menu->filter = (filter ? _bmStrdup(filter) : NULL); + menu->filterSize = (filter ? strlen(filter) : 0); +} + +/** + * Get filter text from bmMenu instance. + * + * @param menu bmMenu instance where to get filter. + * @return Const pointer to current filter text, may be **NULL** if empty. + */ +const char* bmMenuGetFilter(bmMenu *menu) +{ + assert(menu); + return menu->filter; +} + +/** + * 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); + menu->filterMode = (mode >= BM_FILTER_MODE_LAST ? BM_FILTER_MODE_DMENU : 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) +{ + assert(menu); + return menu->filterMode; +} + +/** + * Set selection wrapping on/off. + * + * @param menu bmMenu instance where to toggle selection wrapping. + * @param int 1 == on, 0 == off. + */ +void bmMenuSetWrap(bmMenu *menu, int wrap) +{ + assert(menu); + menu->wrap = (wrap ? 1 : 0); +} + +/** + * Get selection wrapping state. + * + * @param menu bmMenu instance where to get selection wrapping state. + * @return int for wrap state. + */ +int bmMenuGetWrap(const bmMenu *menu) +{ + assert(menu); + return menu->wrap; +} + +/** + * 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); + + 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); + 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); + return _bmItemListAddItemAt(&menu->items, item, 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) +{ + return _bmItemListAddItem(&menu->items, item); +} + +/** + * Remove item from bmMenu instance at specific index. + * + * @warning The item won't be freed, use bmItemFree to do that. + * + * @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); + + if (!menu->items.list || menu->items.count <= index) + return 0; + + bmItem *item = menu->items.list[index]; + int ret = _bmItemListRemoveItemAt(&menu->items, index); + + if (ret) { + _bmItemListRemoveItem(&menu->selection, item); + _bmItemListRemoveItem(&menu->filtered, item); + } + + return ret; +} + +/** + * Remove item from bmMenu instance. + * + * @warning 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) +{ + assert(menu); + + int ret = _bmItemListRemoveItem(&menu->items, item); + + if (ret) { + _bmItemListRemoveItem(&menu->selection, item); + _bmItemListRemoveItem(&menu->filtered, item); + } + + return ret; +} + +/** + * Highlight item in menu by index. + * + * @param menu bmMenu instance from where to highlight item. + * @param index Index of item to highlight. + * @return 1 on successful highlight, 0 on failure. + */ +int bmMenuSetHighlightedIndex(bmMenu *menu, unsigned int index) +{ + assert(menu); + + unsigned int itemsCount; + bmMenuGetFilteredItems(menu, &itemsCount); + + if (itemsCount <= index) + return 0; + + return (menu->index = index); +} + +/** + * Highlight item in menu. + * + * @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 highlighted item from bmMenu instance. + * + * @warning The pointer returned by this function may be invalid after items change. + * + * @param menu bmMenu instance from where to get highlighted item. + * @return Selected bmItem instance, **NULL** if none highlighted. + */ +bmItem* bmMenuGetHighlightedItem(const bmMenu *menu) +{ + assert(menu); + + unsigned int count; + bmItem **items = bmMenuGetFilteredItems(menu, &count); + + if (!items || count <= menu->index) + return NULL; + + return items[menu->index]; +} + +/** + * Set selected items to 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 bmMenuSetSelectedItems(bmMenu *menu, bmItem **items, unsigned int nmemb) +{ + assert(menu); + + bmItem **newItems; + if (!(newItems = calloc(sizeof(bmItem*), nmemb))) + return 0; + + memcpy(newItems, items, sizeof(bmItem*) * nmemb); + return _bmItemListSetItemsNoCopy(&menu->selection, newItems, nmemb); +} + +/** + * Get selected items from bmMenu instance. + * + * @warning The pointer returned by this function may be invalid after selection or items change. + * + * @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** bmMenuGetSelectedItems(const bmMenu *menu, unsigned int *outNmemb) +{ + assert(menu); + return _bmItemListGetItems(&menu->selection, outNmemb); +} + +/** + * 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) +{ + assert(menu); + + int ret = _bmItemListSetItems(&menu->items, items, nmemb); + + if (ret) { + _bmItemListFreeList(&menu->selection); + _bmItemListFreeList(&menu->filtered); + } + + return ret; +} + +/** + * 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); +} + +/** + * 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 outNmemb 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 *outNmemb) +{ + assert(menu); + + if (menu->filter && strlen(menu->filter)) + return _bmItemListGetItems(&menu->filtered, outNmemb); + + return _bmItemListGetItems(&menu->items, outNmemb); +} + +/** + * Render bmMenu instance using chosen draw method. + * + * @param menu bmMenu instance to be rendered. + */ +void bmMenuRender(const bmMenu *menu) +{ + assert(menu); + + if (menu->renderApi.render) + menu->renderApi.render(menu); +} + +/** + * Trigger filtering of menu manually. + * This is useful when adding new items and want to dynamically see them filtered. + * + * Do note that filtering might be heavy, so you should only call it after batch manipulation of items. + * Not after manipulation of each single item. + * + * @param menu bmMenu instance which to filter. + */ +void bmMenuFilter(bmMenu *menu) +{ + assert(menu); + + char addition = 0; + size_t len = (menu->filter ? strlen(menu->filter) : 0); + + if (!len || !menu->items.list || menu->items.count <= 0) { + _bmItemListFreeList(&menu->filtered); + + if (menu->oldFilter) + free(menu->oldFilter); + + menu->oldFilter = NULL; + return; + } + + if (menu->oldFilter) { + size_t oldLen = strlen(menu->oldFilter); + addition = (oldLen < len && !memcmp(menu->oldFilter, menu->filter, oldLen)); + } + + if (menu->oldFilter && addition && menu->filtered.count <= 0) + return; + + if (menu->oldFilter && !strcmp(menu->filter, menu->oldFilter)) + return; + + unsigned int count; + bmItem **filtered = filterFunc[menu->filterMode](menu, addition, &count); + + _bmItemListSetItemsNoCopy(&menu->filtered, filtered, count); + menu->index = 0; + + if (menu->oldFilter) + free(menu->oldFilter); + + menu->oldFilter = _bmStrdup(menu->filter); +} + +/** + * Poll key and unicode from underlying UI toolkit. + * + * This function will block on @link ::bmDrawMode BM_DRAW_MODE_CURSES @endlink draw mode. + * + * @param menu bmMenu instance from which to poll. + * @param outUnicode Reference to unsigned int. + * @return bmKey for polled key. + */ +bmKey bmMenuGetKey(bmMenu *menu, unsigned int *outUnicode) +{ + assert(menu); + assert(outUnicode); + + *outUnicode = 0; + bmKey key = BM_KEY_NONE; + + if (menu->renderApi.getKey) + key = menu->renderApi.getKey(outUnicode); + + return key; +} + +/** + * Advances menu logic with key and unicode as input. + * + * @param menu bmMenu instance to be advanced. + * @param key Key input that will advance menu logic. + * @param unicode Unicode input that will advance menu logic. + * @return bmRunResult for menu state. + */ +bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode) +{ + assert(menu); + + unsigned int itemsCount; + bmMenuGetFilteredItems(menu, &itemsCount); + + unsigned int displayed = 0; + if (menu->renderApi.displayedCount) + displayed = menu->renderApi.displayedCount(menu); + + if (!displayed) + displayed = itemsCount; + + switch (key) { + case BM_KEY_LEFT: + if (menu->filter) { + 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: + if (menu->filter) { + 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 = (menu->filter ? strlen(menu->filter) : 0); + menu->cursesCursor = (menu->filter ? _bmUtf8StringScreenWidth(menu->filter) : 0); + break; + + case BM_KEY_UP: + if (menu->index > 0) { + menu->index--; + } else if (menu->wrap) { + menu->index = itemsCount - 1; + } + break; + + case BM_KEY_DOWN: + if (menu->index < itemsCount - 1) { + menu->index++; + } else if (menu->wrap) { + menu->index = 0; + } + break; + + case BM_KEY_PAGE_UP: + menu->index = (menu->index < displayed ? 0 : menu->index - (displayed - 1)); + break; + + case BM_KEY_PAGE_DOWN: + menu->index = (menu->index + displayed >= itemsCount ? itemsCount - 1 : menu->index + (displayed - 1)); + break; + + case BM_KEY_SHIFT_PAGE_UP: + menu->index = 0; + break; + + case BM_KEY_SHIFT_PAGE_DOWN: + menu->index = itemsCount - 1; + break; + + case BM_KEY_BACKSPACE: + if (menu->filter) { + size_t width; + menu->cursor -= _bmUtf8RuneRemove(menu->filter, menu->cursor, &width); + menu->cursesCursor -= width; + } + break; + + case BM_KEY_DELETE: + if (menu->filter) + _bmUtf8RuneRemove(menu->filter, menu->cursor + 1, NULL); + break; + + case BM_KEY_LINE_DELETE_LEFT: + if (menu->filter) { + while (menu->cursor > 0) { + size_t width; + menu->cursor -= _bmUtf8RuneRemove(menu->filter, menu->cursor, &width); + menu->cursesCursor -= width; + } + } + break; + + case BM_KEY_LINE_DELETE_RIGHT: + if (menu->filter) + menu->filter[menu->cursor] = 0; + break; + + case BM_KEY_WORD_DELETE: + if (menu->filter) { + while (menu->cursor < strlen(menu->filter) && !isspace(menu->filter[menu->cursor])) { + 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, &menu->filterSize, menu->cursor, unicode, &width); + menu->cursesCursor += width; + } + break; + + case BM_KEY_TAB: + { + const char *text; + bmItem *highlighted = bmMenuGetHighlightedItem(menu); + if (highlighted && (text = bmItemGetText(highlighted))) { + bmMenuSetFilter(menu, text); + menu->cursor = (menu->filter ? strlen(menu->filter) : 0); + menu->cursesCursor = (menu->filter ? _bmUtf8StringScreenWidth(menu->filter) : 0); + } + } + break; + + case BM_KEY_CONTROL_RETURN: + case BM_KEY_RETURN: + { + bmItem *highlighted = bmMenuGetHighlightedItem(menu); + if (highlighted && !_bmMenuItemIsSelected(menu, highlighted)) + _bmItemListAddItem(&menu->selection, highlighted); + } + break; + + case BM_KEY_SHIFT_RETURN: + case BM_KEY_ESCAPE: + _bmItemListFreeList(&menu->selection); + break; + + default: break; + } + + bmMenuFilter(menu); + + switch (key) { + case BM_KEY_SHIFT_RETURN: + case BM_KEY_RETURN: return BM_RUN_RESULT_SELECTED; + case BM_KEY_ESCAPE: return BM_RUN_RESULT_CANCEL; + default: break; + } + + return BM_RUN_RESULT_RUNNING; +} + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/util.c b/lib/util.c new file mode 100644 index 0000000..a47ff8d --- /dev/null +++ b/lib/util.c @@ -0,0 +1,314 @@ +#include "internal.h" + +#define _XOPEN_SOURCE +#include <wchar.h> /* wcswidth */ +#undef _XOPEN_SOURCE + +#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) +{ + assert(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); +} + +/** + * Replaces next token in string with '\0' and returns position for the replaced token. + * + * @param string C "string" where token will be replaced. + * @param outNext Reference to position of next delimiter, or 0 if none. + * @return Position of the replaced token. + */ +size_t _bmStripToken(char *string, const char *token, size_t *outNext) +{ + size_t len = strcspn(string, token); + + if (outNext) + *outNext = len + (string[len] != 0); + + string[len] = 0; + return len; +} + +/** + * Portable case-insensitive strcmp. + * + * @param hay C "string" to match against. + * @param needle C "string" to match. + * @return Less than, equal to or greater than zero if hay is lexicographically less than, equal to or greater than needle. + */ +int _bmStrupcmp(const char *hay, const char *needle) +{ + return _bmStrnupcmp(hay, needle, strlen(hay)); +} + +/** + * Portable case-insensitive strncmp. + * + * @param hay C "string" to match against. + * @param needle C "string" to match. + * @return Less than, equal to or greater than zero if hay is lexicographically less than, equal to or greater than needle. + */ +int _bmStrnupcmp(const char *hay, const char *needle, size_t len) +{ + size_t i = 0; + unsigned char a = 0, b = 0; + + const unsigned char *p1 = (const unsigned char*)hay; + const unsigned char *p2 = (const unsigned char*)needle; + + for (i = 0; len > 0; --len, ++i) + if ((a = toupper(*p1++)) != (b = toupper(*p2++))) + return a - b; + + return a - b; +} + +/** + * Portable case-insensitive strstr. + * + * @param hay C "string" to substring against. + * @param needle C "string" to substring. + */ +char* _bmStrupstr(const char *hay, const char *needle) +{ + size_t i, r = 0, p = 0, len, len2; + + if ((len = strlen(hay)) < (len2 = strlen(needle))) + return NULL; + + if (!_bmStrnupcmp(hay, needle, len2)) + return (char*)hay; + + for (i = 0; i < len; ++i) { + if (p == len2) + return (char*)hay + r; + + if (toupper(hay[i]) == toupper(needle[p++])) { + if (!r) + r = i; + } else { + if (r) + i = r; + r = p = 0; + } + } + + return (p == len2 ? (char*)hay + r : NULL); +} + +/** + * 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) +{ + assert(string); + + char *mstr = _bmStrdup(string); + if (!mstr) + return strlen(string); + + char *s; + for (s = mstr; *s; ++s) if (*s == '\t') *s = ' '; + + int num_char = mbstowcs(NULL, mstr, 0) + 1; + wchar_t *wstring = malloc((num_char + 1) * sizeof (wstring[0])); + + if (mbstowcs(wstring, mstr, num_char) == (size_t)(-1)) { + free(wstring); + int len = strlen(mstr); + free(mstr); + return len; + } + + int length = wcswidth(wstring, num_char); + free(wstring); + free(mstr); + 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); + + 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); + + 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); + 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 outRuneWidth 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 *outRuneWidth) +{ + assert(string); + + if (outRuneWidth) + *outRuneWidth = 0; + + size_t len = strlen(string), oldStart = start; + if (len == 0 || len < start || !*string) + return 0; + + start -= _bmUtf8RunePrev(string, start); + + if (outRuneWidth) + *outRuneWidth = _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 inOutString Reference to buffer. + * @param inOutBufSize Reference to 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 outRuneWidth 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 **inOutString, size_t *inOutBufSize, size_t start, const char *rune, unsigned int u8len, size_t *outRuneWidth) +{ + assert(inOutString); + assert(inOutBufSize); + + if (outRuneWidth) + *outRuneWidth = 0; + + if (u8len == 1 && !isprint(*rune)) + return 0; + + size_t len = (*inOutString ? strlen(*inOutString) : 0); + if (!*inOutString && !(*inOutString = calloc(1, (*inOutBufSize = u8len + 1)))) + return 0; + + if (len + u8len >= *inOutBufSize) { + void *tmp; + if (!(tmp = realloc(*inOutString, (*inOutBufSize * 2)))) { + if (!(tmp = malloc((*inOutBufSize * 2)))) + return 0; + + memcpy(tmp, *inOutString, *inOutBufSize); + free(*inOutString); + } + + memset(tmp + *inOutBufSize, 0, *inOutBufSize); + *inOutString = tmp; + *inOutBufSize *= 2; + } + + char *str = *inOutString + start; + memmove(str + u8len, str, len - start); + memcpy(str, rune, u8len); + (*inOutString)[len + u8len] = 0; + + if (outRuneWidth) + *outRuneWidth = _bmUtf8RuneWidth(rune, u8len); + return u8len; +} + +/** + * Insert unicode character to UTF8 buffer. + * + * @param inOutString Reference to buffer. + * @param inOutBufSize Reference to size of the buffer. + * @param start Start offset where to insert to. (cursor) + * @param unicode Unicode character to insert. + * @param outRuneWidth 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 **inOutString, size_t *inOutBufSize, size_t start, unsigned int unicode, size_t *outRuneWidth) +{ + assert(inOutString); + assert(inOutBufSize); + + 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(inOutString, inOutBufSize, start, mb, u8len, outRuneWidth); +} + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/lib/version.h.in b/lib/version.h.in new file mode 100644 index 0000000..6be8b5f --- /dev/null +++ b/lib/version.h.in @@ -0,0 +1,3 @@ +static const char *BM_VERSION = "@BEMENU_VERSION@"; + +/* vim: set ts=8 sw=4 tw=0 :*/ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8da6d4a..f9d13a5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,19 @@ -INCLUDE(CTest) +SET(TESTS + "bmMenuNew" + ) -# TODO: write test CMakeLists +INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/../lib") +FOREACH (test ${TESTS}) + ADD_EXECUTABLE(${test}_test ${test}.c) + TARGET_LINK_LIBRARIES(${test}_test bemenu) + + IF (WIN32) + ADD_TEST(${test}_test ${test}_test.exe) + ELSE () + ADD_TEST(${test}_test ${test}_test) + ENDIF () +ENDFOREACH (test) + +MESSAGE("-!- Use 'make test' to run tests") # vim: set ts=8 sw=4 tw=0 : diff --git a/test/bmMenuNew.c b/test/bmMenuNew.c new file mode 100644 index 0000000..ebab314 --- /dev/null +++ b/test/bmMenuNew.c @@ -0,0 +1,29 @@ +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <assert.h> +#include <bemenu.h> + +int main(int argc, char **argv) +{ + (void)argc, (void)argv; + + // TEST: Instance bmMenu with all possible draw modes. + { + bmDrawMode i; + for (i = 0; i < BM_DRAW_MODE_LAST; ++i) { + if (i == BM_DRAW_MODE_CURSES && !isatty(STDIN_FILENO)) { + printf("Skipping test for mode BM_DRAW_MODE_CURSES, as not running on terminal.\n"); + continue; + } + bmMenu *menu = bmMenuNew(i); + assert(menu); + bmMenuRender(menu); + bmMenuFree(menu); + } + } + + return EXIT_SUCCESS; +} + +/* vim: set ts=8 sw=4 tw=0 :*/ |