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