summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJari Vetoniemi <mailroxas@gmail.com>2014-08-21 01:47:30 +0300
committerJari Vetoniemi <mailroxas@gmail.com>2014-08-21 01:47:30 +0300
commitf1bb87a808ff1383a2d27dc2c4a1f8812c470c31 (patch)
tree6669801544e51621c9269e5e89d3ae7a83c73ba8
parent5bd81e8d384e82926a7671527e62aebafed1183f (diff)
parent536eee6d0bf8eb99b6566f7f4fd646b3cd76f532 (diff)
downloadbemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.tar.gz
bemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.tar.bz2
bemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.zip
Merge branch 'develop'
-rw-r--r--CMakeLists.txt2
-rw-r--r--README.md1
-rw-r--r--client/CMakeLists.txt10
-rw-r--r--client/client.c281
-rw-r--r--doxygen/CMakeLists.txt2
-rw-r--r--doxygen/Doxyfile.in24
-rw-r--r--doxygen/Mainpage.dox21
-rw-r--r--doxygen/doxygen-qmi-style/README.md41
-rw-r--r--doxygen/doxygen-qmi-style/footer.html20
-rw-r--r--doxygen/doxygen-qmi-style/header.html55
-rw-r--r--doxygen/doxygen-qmi-style/navtree/ftv2mlastnode.pngbin0 -> 285 bytes
-rw-r--r--doxygen/doxygen-qmi-style/navtree/ftv2mnode.pngbin0 -> 285 bytes
-rw-r--r--doxygen/doxygen-qmi-style/navtree/ftv2plastnode.pngbin0 -> 277 bytes
-rw-r--r--doxygen/doxygen-qmi-style/navtree/ftv2pnode.pngbin0 -> 282 bytes
-rw-r--r--doxygen/doxygen-qmi-style/navtree/navtree.css117
-rw-r--r--doxygen/doxygen-qmi-style/qmi.css1033
-rw-r--r--doxygen/doxygen-qmi-style/search/search.css238
-rw-r--r--lib/CMakeLists.txt42
-rw-r--r--lib/bemenu.c9
-rw-r--r--lib/bemenu.h484
-rw-r--r--lib/draw/curses.c497
-rw-r--r--lib/filter.c188
-rw-r--r--lib/internal.h188
-rw-r--r--lib/item.c96
-rw-r--r--lib/library.c15
-rw-r--r--lib/list.c139
-rw-r--r--lib/menu.c727
-rw-r--r--lib/util.c314
-rw-r--r--lib/version.h.in3
-rw-r--r--test/CMakeLists.txt18
-rw-r--r--test/bmMenuNew.c29
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
diff --git a/README.md b/README.md
index 3c711ab..07d3034 100644
--- a/README.md
+++ b/README.md
@@ -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> |&nbsp; </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
new file mode 100644
index 0000000..6dfeb5d
--- /dev/null
+++ b/doxygen/doxygen-qmi-style/navtree/ftv2mlastnode.png
Binary files differ
diff --git a/doxygen/doxygen-qmi-style/navtree/ftv2mnode.png b/doxygen/doxygen-qmi-style/navtree/ftv2mnode.png
new file mode 100644
index 0000000..48e70f0
--- /dev/null
+++ b/doxygen/doxygen-qmi-style/navtree/ftv2mnode.png
Binary files differ
diff --git a/doxygen/doxygen-qmi-style/navtree/ftv2plastnode.png b/doxygen/doxygen-qmi-style/navtree/ftv2plastnode.png
new file mode 100644
index 0000000..2b99c65
--- /dev/null
+++ b/doxygen/doxygen-qmi-style/navtree/ftv2plastnode.png
Binary files differ
diff --git a/doxygen/doxygen-qmi-style/navtree/ftv2pnode.png b/doxygen/doxygen-qmi-style/navtree/ftv2pnode.png
new file mode 100644
index 0000000..02f42f7
--- /dev/null
+++ b/doxygen/doxygen-qmi-style/navtree/ftv2pnode.png
Binary files differ
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 :*/