easydoneitCTui

Log

Files

Refs

LICENSE

edt.c (55502B)

     1 #! /usr/bin/env sheepy
     2 
     3 #include <stdio.h>
     4 #include <stdlib.h>
     5 #include <ncurses.h>
     6 #include <unistd.h>
     7 #include <signal.h>
     8 #include "shpPackages/md/md.h"
     9 #include "shpPackages/ini/src/ini.h"
    10 #include "tui/tui.h"
    11 #include "libsheepyObject.h"
    12 #include "edCore.h"
    13 
    14 // TODO add n 'add many tasks' and m 'add many one line tasks'
    15 // TODO sort group list
    16 // TODO handle path in statusBar when it exceeds screen size
    17 
    18 /** path element
    19  */
    20 typedef struct {
    21         /** group id */
    22         char group[16+1];
    23         /** position in list */
    24         u16 pos;
    25         /** index in list of first line in the window */
    26         u16 firstLineOnScreen;
    27 } pathInTreeElement;
    28 
    29 /** maximum path length stored, when MAX_PATH_DEPTH is reached, the oldest path is removed */
    30 // TODO test with low MAX
    31 #define MAX_PATH_DEPTH 1000
    32 /** path in tree circular buffer */
    33 staticArrayT(pathInTreeT, pathInTreeElement, MAX_PATH_DEPTH);
    34 /** pathInTree has the path browsed without the group in WGROUP */
    35 pathInTreeT pathInTree;
    36 /** titles for group in pathInTree and currentGroup for statusBar */
    37 smallArrayt *pathTitles = NULL;
    38 
    39 /** main window */
    40 WINDOW *mainwin;
    41 /** sub windows (colummns) */
    42 void **subwins = NULL;
    43 enum { WLEFT, WGROUP, WVIEW, WSEARCH, WHELP};
    44 
    45 /** window attributes */
    46 typedef struct {
    47         /** this windows index in subwins array */
    48         u8 subwin;
    49         /** coordinate */
    50         i16 row;
    51         /** coordinate */
    52         i16 col;
    53         /** size */
    54         i16 szrow;
    55         /** size */
    56         i16 szcol;
    57         /** index in list of first line on screen */
    58         i32 indexFirstLine;
    59 } awindow;
    60 
    61 /** window array for attributes */
    62 awindow windows[5];
    63 
    64 /** screen size */
    65 int row,col;
    66 
    67 enum {SHOWING_DATABASE, SHOWING_BOOKMARKS, SEARCH, SHOWING_SEARCH, HELP};
    68 /** ui state for user input */
    69 i8 state = SHOWING_DATABASE;
    70 i8 previousState;
    71 /** state for Info (F9) function - toggle between info and text/group */
    72 bool stateInfo = false;
    73 
    74 /** group in the WGROUP window */
    75 char currentGroup[ID_LENGTH+1];
    76 /** cursor in the WGROUP window */
    77 char selectedGroup[ID_LENGTH+1];
    78 /** index of cursor in WLEFT window */
    79 u16 leftCursor   = 0;
    80 /** index if cursor in WGROUP */
    81 u16 selectedLine = 0;
    82 
    83 /** list of select items (tids) */
    84 smallArrayt *selectedList = NULL;
    85 smallArrayt *selectedListAttributes = NULL;
    86 
    87 /** list in WLEFT window */
    88 smallArrayt *lsLeft   = NULL;
    89 /** list in WGROUP window */
    90 smallArrayt *lsGroup  = NULL;
    91 /** list in WVIEW window */
    92 smallArrayt *viewText = NULL;
    93 bool viewTextIsGroup  = false;
    94 
    95 const int cursorColor       = 8;
    96 const int regularListColor  = 0;
    97 const int groupListColor    = 2;
    98 const int selectedListColor = 3;
    99 
   100 char statusSymbols[] = "AD>^# ";
   101 i8   statusColors[]  = {0, 6, 3, 14, 4, 5};
   102 typedef enum { HIDE_STATUS, SHOW_STATUS} displayStatusT;
   103 
   104 char *searchString = NULL;
   105 
   106 typedef enum { NO_PAPER, COPIED, CUT} paperfunctionT;
   107 paperfunctionT paperfunction = NO_PAPER;
   108 
   109 /* PROTOTYPES */
   110 void statusBar(void);
   111 
   112 /**
   113  * add group to pathInTree with cursor position in group list
   114  * @param
   115  *   groupid group id to add
   116  * @param
   117  *   pos     cursor position in group list
   118  */
   119 void addGroupToPath(const char *groupid, u16 pos) {
   120         if (staticArrayIsFull(pathInTree)) {
   121                 // remove oldest path when full
   122                 staticArrayDequeue(pathInTree);
   123         }
   124 
   125         // allocate element
   126         staticArrayPush(pathInTree);
   127         // store groupid
   128         strcpy(staticArrayLast(pathInTree).group, groupid);
   129         // store cursor position
   130         staticArrayLast(pathInTree).pos               = pos;
   131         // store window state
   132         staticArrayLast(pathInTree).firstLineOnScreen = windows[WGROUP].indexFirstLine;
   133         // copy window state WLEFT to keep same view as WGROUP
   134         windows[WLEFT].indexFirstLine                 = windows[WGROUP].indexFirstLine;
   135 }
   136 
   137 /**
   138  * go back in path history
   139  */
   140 void moveToParentGroup(void) {
   141         if (not staticArrayIsEmpty(pathInTree)) {
   142                 // there is a parent
   143                 // set last path to currentGroup
   144                 strcpy(currentGroup, staticArrayLast(pathInTree).group);
   145                 // set cursor in WGROUP
   146                 selectedLine                   = staticArrayLast(pathInTree).pos;
   147                 // set first line index in window
   148                 windows[WGROUP].indexFirstLine = staticArrayLast(pathInTree).firstLineOnScreen;
   149                 // pop path
   150                 staticArrayPop(pathInTree);
   151                 if (not staticArrayIsEmpty(pathInTree)) {
   152                         // set first line in window for new parent path
   153                         windows[WLEFT].indexFirstLine = staticArrayLast(pathInTree).firstLineOnScreen;
   154                 }
   155         }
   156         // TODO handle situation when pathInTree is empty and currentGroup is not root (lost history in the circular buffer)
   157 }
   158 
   159 /** get first group id in pathInTree */
   160 const char *getParentGroupInPath(void) {
   161         if (not staticArrayIsEmpty(pathInTree)) {
   162                 // return last element in pathInTree
   163                 return staticArrayLast(pathInTree).group;
   164         }
   165         else {
   166                 // database top or pathInTree empty
   167                 return "";
   168         }
   169 }
   170 
   171 /** create string with status
   172  * @param
   173  *   d dictionary containing the information about the group
   174  * @return
   175  *   statusIndex index in TASK_STATUS
   176  * @return
   177  *   string to be freed in the format 'STATUS TITLE'
   178  */
   179 char *statusAndTitle(smallDictt *d, i8 *statusIndex) {
   180         // status is one char
   181         char statusSymbol[3] = "  ";
   182         *statusIndex         = 0;
   183 
   184         // convert status string to status index in TASK_STATUS array
   185         for (; *statusIndex < TASK_STATUS_VOID+1 ; (*statusIndex)++) {
   186                 if (eqG(getG(d, rtChar, "status"), TASK_STATUS[*statusIndex])) {
   187                         break;
   188                 }
   189         }
   190         statusSymbol[0]      = statusSymbols[*statusIndex];
   191         char *s              = catS(statusSymbol, getG(d, rtChar, "title"));
   192         return s;
   193 }
   194 
   195 /** display list in window w with cursor and showing/hiding each task/group status */
   196 void displayGroupList(smallArrayt *list, u16 w, u16 cursor, displayStatusT status) {
   197         if (lenG(list) <= windows[w].szrow) {
   198                 // the list is smaller than the window
   199                 // the list top is the first line in the window
   200                 windows[w].indexFirstLine = 0;
   201         }
   202         // when the list is longer than the window, check that the list end is at window bottom
   203         #define checkEndListInWindow(list, w) ((lenG(list) > windows[w].szrow) and ((windows[w].indexFirstLine + windows[w].szrow) > lenG(list)))
   204         if checkEndListInWindow(list, w) {
   205                 // keep list end at window bottom
   206                 windows[w].indexFirstLine = lenG(list) - windows[w].szrow -1;
   207         }
   208         // print list in window
   209         enumerateSmallArray(list, L, line) {
   210                 cast(smallDictt*, d, L)
   211                 if (line < windows[w].indexFirstLine) {
   212                         // skip elements before first line on screen
   213                         goto end;
   214                 }
   215                 i8 i;
   216                 char *s;
   217                 if (status == HIDE_STATUS) {
   218                         // hide status for database list, use regularListColor
   219                         i = regularListColor;
   220                 }
   221                 else {
   222                         // color according to status
   223                         s = statusAndTitle(d, &i);
   224                 }
   225                 if (line == cursor) {
   226                         // show cursor in cursorColor
   227                         wcolor_set(subwins[w], cursorColor, NULL);
   228                         if (w == WGROUP) {
   229                                 // get tid at cursor position in list
   230                                 // only for list in WGROUP window
   231                                 strcpy(selectedGroup, getG(d, rtChar, "tid"));
   232                         }
   233                 }
   234                 else {
   235                         // set color
   236                         if (hasG(selectedList, getG(d, rtChar, "tid"))) {
   237                                 wcolor_set(subwins[w], selectedListColor, NULL);
   238                         }
   239                         else {
   240                                 if (eqG(getG(d, rtChar, "group"), "GROUP")) {
   241                                         wcolor_set(subwins[w], groupListColor, NULL);
   242                                 }
   243                                 else {
   244                                         wcolor_set(subwins[w], statusColors[i], NULL);
   245                                 }
   246                         }
   247                 }
   248                 if (status == HIDE_STATUS) {
   249                         // print title without status
   250                         mvwaddnstr(subwins[w], line - windows[w].indexFirstLine, 0, getG(d, rtChar, "title"), windows[w].szcol-1);
   251                 }
   252                 else {
   253                         // print title with status
   254                         mvwaddnstr(subwins[w], line - windows[w].indexFirstLine, 0, s, windows[w].szcol-1);
   255                         free(s);
   256                 }
   257                 if (line == cursor) {
   258                         // fill the cursor line with cursorColor
   259                         wchgat(subwins[w],-1, 0, cursorColor , NULL);
   260                 }
   261 end:
   262                 finishG(d);
   263         }
   264 }
   265 
   266 /** show list in WLEFT window */
   267 void showLeft(void) {
   268         if (not currentGroup[0]) {
   269                 // path is database list, show nothing
   270                 return;
   271         }
   272         if (getParentGroupInPath()[0]) {
   273                 // a group is displayed on the left
   274                 displayGroupList(lsLeft, WLEFT, leftCursor, SHOW_STATUS);
   275         }
   276         else {
   277                 // display database list without status
   278                 displayGroupList(lsLeft, WLEFT, leftCursor, HIDE_STATUS);
   279         }
   280 }
   281 
   282 /**
   283  * generate database list
   284  * @return
   285  *   ls array of dictionaries, same format as list_group
   286  */
   287 void listDatabases(smallArrayt *ls) {
   288         // list selected databases (from .easydoneit.ini)
   289         enumerateSmallArray(selected, S, line) {
   290                 castS(s, S);
   291                 createAllocateSmallDict(task_d);
   292                 setG(task_d, "head", "element");
   293                 setG(task_d, "tid", "");
   294                 setG(task_d, "position", line);
   295                 setG(task_d, "group", "GROUP");
   296                 setG(task_d, "title", ssGet(s));
   297                 setG(task_d, "status", TASK_STATUS[TASK_STATUS_VOID]);
   298                 setG(task_d, "ctime", 0);
   299                 pushNFreeG(ls, task_d);
   300                 finishG(s);
   301         }
   302         createAllocateSmallDict(task_d);
   303         setG(task_d, "head", "empty line");
   304         setG(task_d, "tid", "");
   305         setG(task_d, "position", 0);
   306         setG(task_d, "group", "");
   307         setG(task_d, "title", "");
   308         setG(task_d, "status", "");
   309         setG(task_d, "ctime", 0);
   310         pushNFreeG(ls, task_d);
   311 }
   312 
   313 /**
   314  * generate and display list in WLEFT window
   315  * @param
   316  *   parent group id or "" for database root
   317  */
   318 void viewLeftParent(const char *parent) {
   319         if (parent[0] == 0) {
   320                 // database root
   321                 if (not currentGroup[0]) {
   322                         // path is database list, show nothing
   323                         // the database list is in WGROUP, the user is selecting a database
   324                         return;
   325                 }
   326                 // show database list
   327                 leftCursor = staticArrayGet(pathInTree, pathInTree.last-1).pos;
   328                 // create list
   329                 if (lsLeft) {
   330                         terminateG(lsLeft);
   331                 }
   332                 initiateAllocateSmallArray(&lsLeft);
   333                 listDatabases(lsLeft);
   334         }
   335         else {
   336                 // show parent group
   337                 if (lsLeft) {
   338                         terminateG(lsLeft);
   339                 }
   340                 lsLeft = list_group(parent);
   341                 // find currentGroup in list
   342                 // TODO add a pos parameter to viewLeftParent and remove this loop
   343                 enumerateSmallArray(lsLeft, L, pI) {
   344                         cast(smallDictt*, d, L)
   345                         if (eqG(getG(d, rtChar, "tid"), currentGroup)) {
   346                                 break;
   347                         }
   348                         finishG(d);
   349                 }
   350                 leftCursor = pI;
   351         }
   352         showLeft();
   353 }
   354 
   355 /** show list in WGROUP window */
   356 void showGroup(void) {
   357         if (lenG(lsGroup) == 1) {
   358                 // list is empty, set invalid selectedGroup tid, no cursor in list
   359                 // (lsGroup always has an empty item at the end of the list)
   360                 selectedGroup[0] = 0;
   361         }
   362         displayGroupList(lsGroup, WGROUP, selectedLine, SHOW_STATUS);
   363 }
   364 
   365 /**
   366  * generate and display list in WGROUP window
   367  * @param
   368  *   currentGroup group id
   369  */
   370 void viewGroup(const char *currentGroup) {
   371         if (state == SHOWING_SEARCH) {
   372                 goto show;
   373         }
   374         if (lsGroup) {
   375                 terminateG(lsGroup);
   376         }
   377 ifState:
   378         if (not currentGroup[0]) {
   379                 // path is database list
   380                 // show database list to select another database
   381                 initiateAllocateSmallArray(&lsGroup);
   382                 listDatabases(lsGroup);
   383         }
   384         else if (state == SHOWING_BOOKMARKS) {
   385                 lsGroup = listBookmarks();
   386         }
   387         else if (state == SEARCH) {
   388                 if (isBlankG(searchString)) {
   389                         // the search string is empty
   390                         // leave search results
   391                         state = previousState;
   392                         // show previous state: bookmark or current group
   393                         goto ifState;
   394                 }
   395                 state = SHOWING_SEARCH;
   396                 // SEARCH
   397                 smallArrayt *searchR = search_string_in_tree(currentGroup, trimG(&searchString));
   398                 uniqG(searchR, unusedV);
   399                 createAllocateSmallArray(hit_tids);
   400                 forEachSmallArray(searchR, S) {
   401                         castS(s, S);
   402                         char *id = ssGet(s);
   403                         *(id+ID_LENGTH) = 0;
   404                         pushG(hit_tids, id);
   405                         finishG(s);
   406                 }
   407                 smashG(searchR);
   408                 uniqG(hit_tids, unusedV);
   409                 initiateAllocateSmallArray(&lsGroup);
   410                 enumerateSmallArray(hit_tids, H, count) {
   411                         castS(h, H);
   412                         smallDictt *d = get_task_in_list_group_format(ssGet(h));
   413                         setG(d, "position", count);
   414                         pushNFreeG(lsGroup, d);
   415                         finishG(h);
   416                 }
   417                 terminateG(hit_tids);
   418                 createAllocateSmallDict(task_d);
   419                 setG(task_d, "head", "empty line");
   420                 setG(task_d, "tid", "");
   421                 setG(task_d, "position", 0);
   422                 setG(task_d, "group", "");
   423                 setG(task_d, "title", "");
   424                 setG(task_d, "status", "");
   425                 setG(task_d, "ctime", 0);
   426                 pushNFreeG(lsGroup, task_d);
   427         }
   428         else {
   429                 // show group
   430                 lsGroup = list_group(currentGroup);
   431         }
   432         // check the list length and adjust selectedLine when there is only 1 line
   433         if (selectedLine+2 > lenG(lsGroup)) {
   434                 selectedLine = lenG(lsGroup)-2;
   435         }
   436 show:
   437         showGroup();
   438 }
   439 
   440 /** show text in WVIEW window */
   441 void showSelected(const char *selected) {
   442         if (not currentGroup[0]) {
   443                 // path is database list, show nothing
   444                 return;
   445         }
   446         if (windows[WVIEW].indexFirstLine < 0) {
   447                 // keep first line index in list
   448                 windows[WVIEW].indexFirstLine = 0;
   449         }
   450         if (lenG(viewText) <= windows[WVIEW].szrow) {
   451                 // the list is smaller than the window
   452                 // the list top is the first line in the window
   453                 windows[WVIEW].indexFirstLine = 0;
   454         }
   455         //if ((lenG(viewText) > windows[WVIEW].szrow) and ((windows[WVIEW].indexFirstLine + windows[WVIEW].szrow) > lenG(viewText))) {
   456         if checkEndListInWindow(viewText, WVIEW) {
   457                 // keep list end at window bottom
   458                 windows[WVIEW].indexFirstLine = lenG(viewText) - windows[WVIEW].szrow -1;
   459         }
   460         // in root show all groups
   461         // in groups, show text for task at pos 0 (group title task)
   462         if (viewTextIsGroup and ((selectedLine != 0) or eqG(currentGroup, "root"))) {
   463                 // show group at cursor in WGROUP window
   464                 enumerateSmallArray(viewText, L, line) {
   465                         cast(smallDictt*, d, L)
   466                         if (line < windows[WVIEW].indexFirstLine) {
   467                                 // skip elements before first line on screen
   468                                 goto endGroup;
   469                         }
   470                         // print status and title
   471                         i8 i;
   472                         char *s = statusAndTitle(d, &i);
   473                         // set color
   474                         if (eqG(getG(d, rtChar, "group"), "GROUP")) {
   475                                 wcolor_set(subwins[WVIEW], groupListColor, NULL);
   476                         }
   477                         else {
   478                                 wcolor_set(subwins[WVIEW], statusColors[i], NULL);
   479                         }
   480                         mvwaddnstr(subwins[WVIEW], line - windows[WVIEW].indexFirstLine, 0, s, windows[WVIEW].szcol-1);
   481                         free(s);
   482 endGroup:
   483                         finishG(d);
   484                 }
   485         }
   486         else {
   487                 // show task text
   488                 enumerateSmallArray(viewText, L, line) {
   489                         castS(l, L);
   490                         if (line < windows[WVIEW].indexFirstLine) {
   491                                 // skip elements before first line on screen
   492                                 goto endText;
   493                         }
   494                         mvwaddnstr(subwins[WVIEW], line - windows[WVIEW].indexFirstLine, 0, ssGet(l), windows[WVIEW].szcol-1);
   495 endText:
   496                         finishG(l);
   497                 }
   498         }
   499 }
   500 
   501 /**
   502  * generate and display list in WVIEW window
   503  * @param
   504  *   selected tid at cursor in WGROUP
   505  */
   506 void viewSelected(const char *selected) {
   507         if (viewText) {
   508                 terminateG(viewText);
   509         }
   510         if (not currentGroup[0]) {
   511                 // path is database list, show nothing
   512                 return;
   513         }
   514         if (state == SHOWING_BOOKMARKS) {
   515                 // setup database, bookmarks can be in any database
   516                 save_edi_core_data_location();
   517                 setup_data_location_for_tid(selected);
   518         }
   519         // in root show all groups
   520         // in groups, show text for task at pos 0 (group title task)
   521         if (is_this_task_a_group(selected) and ((selectedLine != 0) or eqG(currentGroup, "root"))) {
   522                 // show group
   523                 viewText        = list_group(selected);
   524                 viewTextIsGroup = true;
   525         }
   526         else {
   527                 // show task text
   528                 viewText        = display_task(selected, passThroughTitle);
   529                 viewTextIsGroup = false;
   530         }
   531         if (state == SHOWING_BOOKMARKS) {
   532                 restore_edi_core_data_location();
   533         }
   534         showSelected(selected);
   535 }
   536 
   537 void showHelp(void) {
   538         windows[WHELP].subwin = listLength(subwins);
   539 #define margin 4
   540         windows[WHELP].row    = margin;
   541         windows[WHELP].col    = margin;
   542         windows[WHELP].szrow  = row - windows[WHELP].row - margin;
   543         windows[WHELP].szcol  = col - windows[WHELP].col - margin;
   544         void *w;
   545         listPush(&subwins, EVA(w, newwin(windows[WHELP].szrow, windows[WHELP].szcol, windows[WHELP].row, windows[WHELP].col)) );
   546         setSubwindows(subwins);
   547         wcolor_set(subwins[windows[WHELP].subwin], 1, NULL);
   548         // fill background with color
   549         char *b = malloc(windows[WHELP].szrow * windows[WHELP].szcol +1);
   550         range(i, windows[WHELP].szrow * windows[WHELP].szcol) {
   551                 b[i] = ' ';
   552         }
   553         b[windows[WHELP].szrow * windows[WHELP].szcol] = 0;
   554         waddstr(subwins[windows[WHELP].subwin], b);
   555         free(b);
   556         box(w, 0 , 0);
   557         mvwaddnstr(subwins[windows[WHELP].subwin], 0, 2, " HELP ", windows[WHELP].szcol-2);
   558         smallArrayt *help = createSA(
   559                         "q              quit                                 g              set active status",
   560                         "INSERT/i       new task                             h              set done status",
   561                         "DELETE/p       delete selected items                j              set ongoing status",
   562                         "z              select/deselect all items            k              set pending status",
   563                         "x              cut selected items                   l              set inactive status",
   564                         "c              copy selected items                  o              set unknown status",
   565                         "v              paste selected items                 a              attach files to cursor item",
   566                         "b              link selected items                  d              convert task to group or group to task",
   567                         "DOWN           move cursor down                     t              toggle top/bottom",
   568                         "END            page down list",
   569                         "UP             move cursor up",
   570                         "HOME           page up list",
   571                         "RIGHT/ENTER    enter group/database (or edit task)",
   572                         "LEFT/BACKSPACE browse back in history",
   573                         "PAGE DOWN      page down description",
   574                         "PAGE UP        page up description",
   575                         "SPACE          select multiple items",
   576                         "F2             deselect items",
   577                         "F3             toggle active filter",
   578                         "F4             toggle done filter",
   579                         "F5             toggle ongoing filter",
   580                         "F6             toggle pending filter",
   581                         "F7             toggle inactive filter",
   582                         "F8             view all group tasks",
   583                         "F9             show task information",
   584                         "F10            search",
   585                         "F11            add selected items to bookmarks",
   586                         "F12            show bookmarks",
   587                         "",
   588                         "List symbols: 'A' Active, 'D' Done, '>' Ongoing, '^' Pending, '#' inactive, ' ' Unknown"
   589         );
   590         enumerateSmallArray(help, H, line) {
   591                 castS(h, H);
   592                 mvwaddnstr(subwins[windows[WHELP].subwin], 2+line, 3, ssGet(h), windows[WHELP].szcol-2);
   593                 if (2+line == windows[WHELP].szrow-2) {
   594                         // prevent print outside the window
   595                         break;
   596                 }
   597                 finishG(h);
   598         }
   599         terminateG(help);
   600 }
   601 
   602 /**
   603  * first line in main window
   604  * keyboard commands
   605  */
   606 void topMenu(void) {
   607         // print menu, inverted colors to the end of the screen
   608         int colorPair = 1;
   609         move(0,0);
   610         color_set(colorPair, NULL);
   611         attron(A_REVERSE);
   612         addnstr("F1 Help F2 Deselect ", col-1);
   613 
   614         i32 *flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_ACTIVE);
   615         if (*flt == DISABLE) {
   616                 color_set(cursorColor, NULL);
   617         }
   618         else {
   619                 color_set(colorPair, NULL);
   620         }
   621 
   622         addnstr("F3 Active", col-1);
   623         color_set(colorPair, NULL);
   624         addnstr(" ", col-1);
   625 
   626         flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_DONE);
   627         if (*flt == DISABLE) {
   628                 color_set(cursorColor, NULL);
   629         }
   630         else {
   631                 color_set(colorPair, NULL);
   632         }
   633 
   634         addnstr("F4 Done", col-1);
   635         color_set(colorPair, NULL);
   636         addnstr(" ", col-1);
   637 
   638         flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_ONGOING);
   639         if (*flt == DISABLE) {
   640                 color_set(cursorColor, NULL);
   641         }
   642         else {
   643                 color_set(colorPair, NULL);
   644         }
   645 
   646         addnstr("F5 Ongoing", col-1);
   647         color_set(colorPair, NULL);
   648         addnstr(" ", col-1);
   649 
   650         flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_PENDING);
   651         if (*flt == DISABLE) {
   652                 color_set(cursorColor, NULL);
   653         }
   654         else {
   655                 color_set(colorPair, NULL);
   656         }
   657 
   658         addnstr("F6 Pending", col-1);
   659         color_set(colorPair, NULL);
   660         addnstr(" ", col-1);
   661 
   662         flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_INACTIVE);
   663         if (*flt == DISABLE) {
   664                 color_set(cursorColor, NULL);
   665         }
   666         else {
   667                 color_set(colorPair, NULL);
   668         }
   669 
   670         addnstr("F7 Inactive", col-1);
   671         color_set(colorPair, NULL);
   672         addnstr(" F8 View Group F9 Info F10 Search F11 Bookmark F12 Bookmarks", col-1);
   673         attroff(A_REVERSE);
   674         chgat(-1, A_REVERSE, colorPair , NULL);
   675 }
   676 
   677 /**
   678  * show pathInTree at the bottom of main window
   679  */
   680 void statusBar(void) {
   681         // print path at bottom
   682         move(row-1,0);
   683         color_set(2, NULL);
   684         smallStringt *p = joinG(pathTitles, "/");
   685         addnstr(ssGet(p), col-1);
   686         clrtoeol();
   687 }
   688 
   689 /**
   690  * place WLEFT, WGROUP and WVIEW on screen
   691  * at start and the terminal is resized
   692  */
   693 void placeCols(void) {
   694         // create columns 15 35 50
   695         int szWin1 = (col * 3) / (2 * 10);
   696         int szWin2 = (col * 7) / (2 * 10);
   697         int szWin3 = col - szWin1 -szWin2;
   698 
   699         windows[WLEFT].row    = 1;
   700         windows[WLEFT].col    = 0;
   701         windows[WLEFT].szrow  = row-2;
   702         windows[WLEFT].szcol  = szWin1-1;
   703         windows[WGROUP].row   = 1;
   704         windows[WGROUP].col   = szWin1;
   705         windows[WGROUP].szrow = row-2;
   706         windows[WGROUP].szcol = szWin2-1;
   707         windows[WVIEW].row    = 1;
   708         windows[WVIEW].col    = szWin1+szWin2;
   709         windows[WVIEW].szrow  = row-2;
   710         windows[WVIEW].szcol  = szWin3;
   711 }
   712 
   713 /**
   714  * create WLEFT, WGROUP and WVIEW
   715  */
   716 void createCols(void) {
   717         free(subwins);
   718         subwins = NULL;
   719 
   720         placeCols();
   721 
   722         // initialize firstLineOnScreen index
   723         windows[WLEFT].indexFirstLine  = 0;
   724         windows[WGROUP].indexFirstLine = 0;
   725         windows[WVIEW].indexFirstLine  = 0;
   726 
   727         // create 3 window colunms WLEFT, WGROUP, WVIEW
   728         WINDOW *w;
   729 
   730         // refresh here, otherwise the new window is invisible
   731         refresh();
   732         // create left window
   733         w = newwin(windows[WLEFT].szrow, windows[WLEFT].szcol, windows[WLEFT].row, windows[WLEFT].col);
   734         listPush(&subwins, w);
   735 
   736         // create group window
   737         w = newwin(windows[WGROUP].szrow, windows[WGROUP].szcol, windows[WGROUP].row, windows[WGROUP].col);
   738         listPush(&subwins, w);
   739 
   740         // create view window
   741         w = newwin(windows[WVIEW].szrow, windows[WVIEW].szcol, windows[WVIEW].row, windows[WVIEW].col);
   742         listPush(&subwins, w);
   743 
   744         setSubwindows(subwins);
   745 }
   746 
   747 /**
   748  * resize the windows when the terminal is resized
   749  */
   750 void resizeCols(void) {
   751 
   752         placeCols();
   753 
   754         // erase previous content to remove all artifacts
   755         eraseSubwindows();
   756         // refresh to show the windows again
   757         refresh();
   758         enumerateType(void, subwins, w, i) {
   759                 mvwin(*w, windows[i].row, windows[i].col);
   760                 wresize(*w, windows[i].szrow, windows[i].szcol);
   761         }
   762 }
   763 
   764 /**
   765  * fill WLEFT, WGROUP, WVIEW
   766  */
   767 void fillCols(void) {
   768         viewLeftParent(getParentGroupInPath());
   769         viewGroup(currentGroup);
   770         viewSelected(selectedGroup);
   771 }
   772 
   773 /**
   774  * display again the lists in WLEFT, WGROUP and WVIEW without updates
   775  * when the terminal is resized
   776  */
   777 void showCols(void) {
   778         showLeft();
   779         showGroup();
   780         showSelected(selectedGroup);
   781 }
   782 
   783 /**
   784  * detect when the terminal is resized
   785  */
   786 void winch_sigaction(int sig, siginfo_t *si, void *arg) {
   787         endwin();
   788         initscr();
   789         refresh();
   790         clear();
   791         row = LINES;
   792         col = COLS;
   793         topMenu();
   794         statusBar();
   795         resizeCols();
   796         showCols();
   797         refreshAll();
   798 }
   799 
   800 void registerSigWinch(void) {
   801         struct sigaction sa;
   802         memset(&sa, 0, sizeof(struct sigaction));
   803         sigemptyset(&sa.sa_mask);
   804         sa.sa_sigaction = winch_sigaction;
   805         sa.sa_flags   = SA_SIGINFO;
   806         sigaction(SIGWINCH, &sa, NULL);
   807 }
   808 
   809 void pasteClipboard(const char *pasteLinkOrCopy) {
   810         // handle multiple databases
   811         save_edi_core_data_location();
   812         // saved_data_location is destination database
   813         setup_data_location_for_tid(getG(selectedList, rtChar, 0));
   814         // data_location is source database
   815         bool sameSrcDstDatabase = true;
   816         const char *destination_database_name;
   817         if (not eqG(data_location, saved_data_location)) {
   818                 sameSrcDstDatabase        = false;
   819                 destination_database_name = getDatabaseNameFromPath(saved_data_location);
   820         }
   821         if (paperfunction == COPIED) {
   822                 forEachSmallArray(selectedList, T) {
   823                         castS(t, T);
   824                         if (sameSrcDstDatabase) {
   825                                 if (eqG(pasteLinkOrCopy, "paste")) {
   826                                         copy_task_to_a_group(ssGet(t), currentGroup);
   827                                 }
   828                                 if (eqG(pasteLinkOrCopy, "link")) {
   829                                         smallStringt *c;
   830                                         add_task_reference_to_a_group(ssGet(t), EVA(c, allocG(currentGroup)));
   831                                         terminateG(c);
   832                                 }
   833                         }
   834                         else {
   835                                 // always copy to different source/destination databases
   836                                 copy_task_to_database(ssGet(t), destination_database_name, currentGroup);
   837                         }
   838                         finishG(t);
   839                 }
   840         }
   841         if (paperfunction == CUT) {
   842                 enumerateSmallArray(selectedList, T, i) {
   843                         castS(t, T);
   844                         if (sameSrcDstDatabase) {
   845                                 smallArrayt *a = getG(selectedListAttributes, rtSmallArrayt, i);
   846                                 if (getG(a, rtI32, 1) == SHOWING_DATABASE) {
   847                                         move_task_to_a_group(getG(a, rtChar, 0), ssGet(t), currentGroup);
   848                                 }
   849                                 else {
   850                                         // group in selectedListAttributes is invalid
   851                                         move_task_to_a_group(find_group_containing_task(ssGet(t)), ssGet(t), currentGroup);
   852                                 }
   853                                 finishG(a);
   854                         }
   855                         else {
   856                                 const char *new_tid = NULL;
   857                                 smallArrayt *a = getG(selectedListAttributes, rtSmallArrayt, i);
   858                                 if (getG(a, rtI32, 1) == SHOWING_DATABASE) {
   859                                         // move to other database
   860                                         new_tid = move_task_to_a_group_to_database(getG(a, rtChar, 0), ssGet(t), destination_database_name, currentGroup);
   861                                 }
   862                                 else {
   863                                         // group in selectedListAttributes is invalid
   864                                         new_tid = move_task_to_a_group_to_database(find_group_containing_task(ssGet(t)), ssGet(t), destination_database_name, currentGroup);
   865                                 }
   866                                 finishG(a);
   867 
   868                                 if (new_tid){
   869                                         ssize_t index = findG(bookmarks, ssGet(t));
   870                                         if (index != -1) {
   871                                                         // update bookmark
   872                                                         setG(bookmarks, index, new_tid);
   873                                         }
   874                                 }
   875                         }
   876                         finishG(t);
   877                 }
   878         }
   879         restore_edi_core_data_location();
   880         // deselect all items
   881         emptyG(selectedList);
   882         emptyG(selectedListAttributes);
   883         paperfunction = NO_PAPER;
   884 }
   885 
   886 void saveEdiState(void) {
   887         char *path = expandHomeG("~/.easydoneit.bin");
   888         createAllocateSmallJson(ediState);
   889 
   890         createAllocateSmallArray(a);
   891 
   892         range(i, staticArrayCount(pathInTree)) {
   893                 createAllocateSmallArray(a2);
   894                 pushG(a2, staticArrayGet(pathInTree, i).group);
   895                 pushG(a2, (u32)staticArrayGet(pathInTree, i).pos);
   896                 pushG(a2, (u32)staticArrayGet(pathInTree, i).firstLineOnScreen);
   897                 pushNFreeG(a, a2);
   898         }
   899 
   900         setNFreeG(ediState, "pathInTree", a);
   901 
   902         setNFreeG(ediState, "pathTitles", pathTitles);
   903 
   904         setG(ediState, "state", (int32_t)state);
   905         setG(ediState, "previousState", (int32_t)previousState);
   906         setG(ediState, "stateInfo", stateInfo);
   907         setG(ediState, "currentGroup", currentGroup);
   908         setG(ediState, "selectedGroup", selectedGroup);
   909         setG(ediState, "selectedLine", (u32)selectedLine);
   910         setNFreeG(ediState, "selectedList", selectedList);
   911         setNFreeG(ediState, "selectedListAttributes", selectedListAttributes);
   912         setNFreeG(ediState, "lsGroup", lsGroup); // for search results, avoid crash
   913         // TODO save view text to restore view group
   914         //setNFreeG(ediState, "searchString", searchString);
   915         setG(ediState, "paperfunction", paperfunction);
   916 
   917         //logVarG(ediState);
   918         smallBytest *B = serialG(ediState);
   919         writeFileG(B, path);
   920         terminateManyG(ediState, B);
   921         free(path);
   922 }
   923 
   924 /** true when state is loaded */
   925 bool loadEdiState(void) {
   926         bool r = false;
   927         char *path = expandHomeG("~/.easydoneit.bin");
   928 
   929         // use default when edi state is not found
   930         if (fileExists(path)) {
   931                 createAllocateSmallBytes(B);
   932                 readFileG(B, path);
   933 
   934                 createAllocateSmallJson(ediState);
   935 
   936                 deserialG(ediState, B);
   937                 //logVarG(ediState);
   938 
   939                 smallArrayt *a = getG(ediState, rtSmallArrayt, "pathInTree");
   940 
   941                 enumerateSmallArray(a, P, i) {
   942                         cast(smallArrayt *, p, P);
   943                         staticArrayPush(pathInTree);
   944                         strcpy(staticArrayLast(pathInTree).group, getG(p, rtChar, 0));
   945                         staticArrayLast(pathInTree).pos               = getG(p, rtI32, 1);
   946                         staticArrayLast(pathInTree).firstLineOnScreen = getG(p, rtI32, 2);
   947                         finishG(p);
   948                 }
   949 
   950                 finishG(a);
   951 
   952                 pathTitles             = getNDupG(ediState, rtSmallArrayt, "pathTitles");
   953                 state                  = getG(ediState, rtI32, "state");
   954                 previousState          = getG(ediState, rtI32, "previousState");
   955             stateInfo              = getG(ediState, rtBool, "stateInfo");
   956                 strcpy(currentGroup,     getG(ediState, rtChar, "currentGroup"));
   957                 strcpy(selectedGroup,    getG(ediState, rtChar, "selectedGroup"));
   958                 selectedLine           = getG(ediState, rtI32, "selectedLine");
   959                 selectedList           = getNDupG(ediState, rtSmallArrayt, "selectedList");
   960                 selectedListAttributes = getNDupG(ediState, rtSmallArrayt,"selectedListAttributes");
   961                 lsGroup                = getNDupG(ediState, rtSmallArrayt, "lsGroup");
   962                 //getG(ediState, rtChar, "searchString");
   963                 paperfunction          = getG(ediState, rtI32, "paperfunction");
   964 
   965                 terminateManyG(ediState, B);
   966 
   967                 r = true;
   968         }
   969         free(path);
   970         return r;
   971 }
   972 
   973 
   974 int main(int argc, char** argv) {
   975 
   976         //char *c = readFileG(c, argv[1]);
   977         //logG(md_highlight(c));
   978 
   979         initLibsheepy(argv[0]);
   980 
   981         //#if 0
   982         start("tui");
   983 
   984         mainwin = initScreen(&row, &col);
   985 
   986         staticArrayInit(pathInTree);
   987 
   988         // database top
   989         // path top title is database name
   990         // groupid parameter to addGroupToPath is "", pos is the cursor in the database list
   991         windows[WGROUP].indexFirstLine = 0;
   992 
   993         if (not loadEdiState()) {
   994                 initiateAllocateSmallArray(&selectedList);
   995                 initiateAllocateSmallArray(&selectedListAttributes);
   996 
   997                 addGroupToPath("", 0);
   998                 initiateAllocateSmallArray(&pathTitles);
   999                 pushG(pathTitles, getG(selected, rtChar, 0));
  1000                 strcpy(currentGroup, "root");
  1001         }
  1002 
  1003         topMenu();
  1004         statusBar();
  1005         createCols();
  1006         registerSigWinch();
  1007         fillCols();
  1008 
  1009         refreshAll();
  1010 
  1011         int c;
  1012         while (((c = wgetch(mainwin)) != 'q') || (state == SEARCH)) {
  1013                 if (state == HELP) {
  1014                         state   = previousState;
  1015                         void *w = listPop(&subwins);
  1016                         setSubwindows(subwins);
  1017                         werase(mainwin);
  1018                         //refresh();
  1019                         wnoutrefresh(mainwin);
  1020                         delwin(w);
  1021                         topMenu();
  1022                         statusBar();
  1023                         showCols();
  1024                 }
  1025                 else if (state == SEARCH) {
  1026                         switch(c){
  1027                                 case KEY_F(1):
  1028                                         state = previousState;
  1029                                         noecho();
  1030                                         statusBar();
  1031                                         break;
  1032                                 case '\n': {
  1033                                         // state is SEARCH, viewGroup will perform the search
  1034 
  1035                                         move(row-1,0);
  1036                                         color_set(cursorColor, NULL);
  1037                                         clrtoeol();
  1038                                         addnstr("Searching...", col-1);
  1039                                         chgat(-1, 0, cursorColor, NULL);
  1040                                         refresh();
  1041 
  1042                                         noecho();
  1043                                         statusBar();
  1044                                         // scroll WVIEW window to top
  1045                                         windows[WVIEW].indexFirstLine = 0;
  1046                                         eraseSubwindows();
  1047                                         fillCols();
  1048                                         break;
  1049                                 }
  1050                                 case KEY_BACKSPACE:
  1051                                         delG(&searchString, -1, 0);
  1052                         }
  1053                         if (c < KEY_MIN) {
  1054                                 iInjectS(&searchString, -1, c);
  1055                         }
  1056                         if (state == SEARCH) {
  1057                                 move(row-1,0);
  1058                                 color_set(1, NULL);
  1059                                 clrtoeol();
  1060                                 char *s = appendS("SEARCH (F1 to cancel): ", searchString);
  1061                                 addnstr(s, col-1);
  1062                                 free(s);
  1063                                 chgat(-1, 0, 1, NULL);
  1064                         }
  1065                 }
  1066                 else {
  1067                         if ((c != KEY_F(9)) and (stateInfo == true)) {
  1068                                 // exit state info when any key is pressed
  1069                                 stateInfo = false;
  1070                         }
  1071                         switch(c){
  1072                                 case 'a':
  1073                                         // TODO add attachments
  1074                                         break;
  1075                                 case 't':
  1076                                         if (eqG(add_top_or_bottom, "top")) {
  1077                                                 free(add_top_or_bottom);
  1078                                                 add_top_or_bottom = strdup("bottom");
  1079                                         }
  1080                                         else {
  1081                                                 free(add_top_or_bottom);
  1082                                                 add_top_or_bottom = strdup("top");
  1083                                         }
  1084                                         break;
  1085                                 case 'd':
  1086                                         // convert selected items
  1087                                         if (not lenG(selectedList)) {
  1088                                                 if (not is_this_task_a_group(selectedGroup)) {
  1089                                                         create_group(selectedGroup);
  1090                                                 }
  1091                                                 else {
  1092                                                         convert_group_to_task(selectedGroup);
  1093                                                 }
  1094                                         }
  1095                                         else {
  1096                                                 // selected items in selectedList
  1097                                                 forEachSmallArray(selectedList, T) {
  1098                                                         castS(t, T);
  1099                                                         if (not is_this_task_a_group(ssGet(t))) {
  1100                                                                 create_group(ssGet(t));
  1101                                                         }
  1102                                                         else {
  1103                                                                 convert_group_to_task(ssGet(t));
  1104                                                         }
  1105                                                         finishG(t);
  1106                                                 }
  1107                                                 emptyG(selectedList);
  1108                                                 emptyG(selectedListAttributes);
  1109                                         }
  1110                                         // scroll WVIEW window to top
  1111                                         windows[WVIEW].indexFirstLine = 0;
  1112                                         eraseSubwindows();
  1113                                         fillCols();
  1114                                         break;
  1115                                 case 'i':
  1116                                 case KEY_IC:
  1117                                         // INSERT
  1118                                         if (state == SHOWING_SEARCH) {
  1119                                                 // leave search results
  1120                                                 state = previousState;
  1121                                         }
  1122                                         create_task(currentGroup);
  1123                                         // refresh screen
  1124                                         system("reset");
  1125                                         reInitScreen(mainwin);
  1126                                         topMenu();
  1127                                         statusBar();
  1128                                         eraseSubwindows();
  1129                                         // scroll WVIEW window to top
  1130                                         windows[WVIEW].indexFirstLine = 0;
  1131                                         fillCols();
  1132                                         redrawwin(mainwin);
  1133                                         refresh();
  1134                                         showCols();
  1135                                         forEachType(void, subwins, wd) {
  1136                                                 redrawwin((WINDOW *)(*wd));
  1137                                         }
  1138                                         break;
  1139                                 case 'p':
  1140                                 case KEY_DC:
  1141                                         // DELETE
  1142                                         if (state == SHOWING_SEARCH) {
  1143                                                 // leave search results
  1144                                                 state = previousState;
  1145                                         }
  1146                                         if (not lenG(selectedList)) {
  1147                                                 delete_linked_task(currentGroup, selectedGroup);
  1148                                                 save_edi_core_data_location();
  1149                                                 if (not tid_exists(selectedGroup)) {
  1150                                                                 ssize_t index = findG(bookmarks, selectedGroup);
  1151                                                                 if (index != -1) {
  1152                                                                                 // delete bookmark
  1153                                                                                 delG(bookmarks, index, index+1);
  1154                                                                 }
  1155                                                 }
  1156                                                 restore_edi_core_data_location();
  1157                                         }
  1158                                         else {
  1159                                                 // selected items in selectedList
  1160                                                 enumerateSmallArray(selectedList, T, i) {
  1161                                                         castS(t, T);
  1162                                                         smallArrayt *a = getG(selectedListAttributes, rtSmallArrayt, i);
  1163                                                         if (getG(a, rtI32, 1) == SHOWING_DATABASE) {
  1164                                                                 delete_linked_task(getG(a, rtChar, 0), ssGet(t));
  1165                                                         }
  1166                                                         else {
  1167                                                                 // group in selectedListAttributes is invalid
  1168                                                                 delete_task(ssGet(t));
  1169                                                         }
  1170                                                                 save_edi_core_data_location();
  1171                                                                 if (not tid_exists(ssGet(t))) {
  1172                                                                                 ssize_t index = findG(bookmarks, ssGet(t));
  1173                                                                                 if (index != -1) {
  1174                                                                                                 // delete bookmark
  1175                                                                                                 delG(bookmarks, index, index+1);
  1176                                                                                 }
  1177                                                                 }
  1178                                                                 restore_edi_core_data_location();
  1179                                                         finishG(a);
  1180                                                         finishG(t);
  1181                                                 }
  1182                                                 emptyG(selectedList);
  1183                                                 emptyG(selectedListAttributes);
  1184                                         }
  1185                                         // scroll WVIEW window to top
  1186                                         windows[WVIEW].indexFirstLine = 0;
  1187                                         eraseSubwindows();
  1188                                         fillCols();
  1189                                         break;
  1190                                 case ' ':
  1191                                         // select/deselect item under cursor in the group list
  1192                                         if (currentGroup[0]) {
  1193                                                 // select only in group, not database list
  1194                                                 ssize_t index = findG(selectedList, selectedGroup);
  1195                                                 if (index == -1) {
  1196                                                         // add new selection
  1197                                                         pushG(selectedList, selectedGroup);
  1198                                                         createAllocateSmallArray(a);
  1199                                                         pushG(a, currentGroup);
  1200                                                         pushG(a, (int32_t)state);
  1201                                                         pushNFreeG(selectedListAttributes, a);
  1202                                                 }
  1203                                                 else {
  1204                                                         // remove selection
  1205                                                         delG(selectedList, index, index+1);
  1206                                                         delG(selectedListAttributes, index, index+1);
  1207                                                 }
  1208                                         }
  1209                                 case KEY_DOWN:
  1210                                         // move cursor down in WGROUP window
  1211                                         // when the cursor reaches the list bottom
  1212                                         // it goes back to the top
  1213                                         if (selectedLine < lenG(lsGroup)-2) {
  1214                                                 // move down the list and stay inside
  1215                                                 selectedLine++;
  1216                                         }
  1217                                         else {
  1218                                                 selectedLine                   = 0;
  1219                                                 windows[WGROUP].indexFirstLine = 0;
  1220                                         }
  1221                                         if ((selectedLine - windows[WGROUP].indexFirstLine +1) > windows[WGROUP].szrow) {
  1222                                                 // scroll to keep cursor on screen
  1223                                                 windows[WGROUP].indexFirstLine++;
  1224                                         }
  1225                                         // scroll WVIEW window to top
  1226                                         windows[WVIEW].indexFirstLine = 0;
  1227                                         eraseSubwindows();
  1228                                         fillCols();
  1229                                         break;
  1230                                 case KEY_END:
  1231                                         // page down WGROUP window
  1232                                         // when the last page is reached, the cursor is moved to the window bottom
  1233                                         // on next page down, the cursor is moved to the list top
  1234                                         if ((windows[WGROUP].indexFirstLine + windows[WGROUP].szrow) < lenG(lsGroup)-2) {
  1235                                                 // list middle is on window bottom
  1236                                                 windows[WGROUP].indexFirstLine += windows[WGROUP].szrow;
  1237                                                 // keep cursor on window top
  1238                                                 if checkEndListInWindow(lsGroup, WGROUP) {
  1239                                                         selectedLine                    = lenG(lsGroup) - windows[WGROUP].szrow -1;
  1240                                                 }
  1241                                                 else {
  1242                                                         selectedLine                    = windows[WGROUP].indexFirstLine;
  1243                                                 }
  1244                                         }
  1245                                         else {
  1246                                                 // list end is on window bottom
  1247                                                 if (selectedLine != (lenG(lsGroup)-2)) {
  1248                                                         // cursor not at window bottom, move cursor to bottom
  1249                                                         selectedLine = lenG(lsGroup)-2;
  1250                                                 }
  1251                                                 else {
  1252                                                         // cursor is at bottom, move to list top
  1253                                                         selectedLine                   = 0;
  1254                                                         windows[WGROUP].indexFirstLine = 0;
  1255                                                 }
  1256                                         }
  1257                                         // scroll WVIEW window to top
  1258                                         windows[WVIEW].indexFirstLine = 0;
  1259                                         eraseSubwindows();
  1260                                         fillCols();
  1261                                         break;
  1262                                 case KEY_UP:
  1263                                         // move cursor up in WGROUP window
  1264                                         // when cursor reaches the list top
  1265                                         // it goes back to the bottom
  1266                                         if (selectedLine > 0) {
  1267                                                 // keep cursor in list
  1268                                                 selectedLine--;
  1269                                                 if (selectedLine < windows[WGROUP].indexFirstLine) {
  1270                                                         // scroll to keep cursor on screen
  1271                                                         windows[WGROUP].indexFirstLine--;
  1272                                                 }
  1273                                         }
  1274                                         else {
  1275                                                 // cursor is at top, move to list bottom
  1276                                                 selectedLine = lenG(lsGroup)-2;
  1277                                                 windows[WGROUP].indexFirstLine = lenG(lsGroup) - windows[WGROUP].szrow -1;
  1278                                         }
  1279                                         // scroll WVIEW window to top
  1280                                         windows[WVIEW].indexFirstLine = 0;
  1281                                         eraseSubwindows();
  1282                                         fillCols();
  1283                                         break;
  1284                                 case KEY_HOME:
  1285                                         // page up WGROUP window
  1286                                         // when the first page is reached, the cursor is moved to the window top
  1287                                         // on next page up, the cursor is moved to the list bottom
  1288                                         if (((windows[WGROUP].indexFirstLine - windows[WGROUP].szrow)) > 0) {
  1289                                                 // list middle is on window top
  1290                                                 windows[WGROUP].indexFirstLine -= windows[WGROUP].szrow;
  1291                                                 // keep cursor on window bottom
  1292                                                 selectedLine                    = windows[WGROUP].indexFirstLine + windows[WGROUP].szrow -1;
  1293                                         }
  1294                                         else {
  1295                                                 // list top is on window top
  1296                                                 i32 newIndex = 0;
  1297                                                 if (windows[WGROUP].indexFirstLine) {
  1298                                                         // currently list top is not in window
  1299                                                         if (selectedLine >= windows[WGROUP].szrow) {
  1300                                                                 // cursor is outside window after page down
  1301                                                                 // keep cursor at window bottom
  1302                                                                 selectedLine = windows[WGROUP].szrow -1;
  1303                                                         }
  1304                                                 }
  1305                                                 else {
  1306                                                         if (selectedLine != 0) {
  1307                                                                 // cursor not at window top, move cursor to top
  1308                                                                 selectedLine = 0;
  1309                                                         }
  1310                                                         else {
  1311                                                                 // cursor is at top, move to list bottom
  1312                                                                 selectedLine = lenG(lsGroup)-2;
  1313                                                                 newIndex     = lenG(lsGroup) - windows[WGROUP].szrow -1;
  1314                                                         }
  1315                                                 }
  1316                                                 // show list top at WGROUP window top
  1317                                                 windows[WGROUP].indexFirstLine = newIndex;
  1318                                         }
  1319                                         // scroll WVIEW window to top
  1320                                         windows[WVIEW].indexFirstLine = 0;
  1321                                         eraseSubwindows();
  1322                                         fillCols();
  1323                                         break;
  1324                                 case '\n':
  1325                                 case KEY_RIGHT:
  1326                                         // move to selected item (group, database)
  1327                                         if (state == SHOWING_SEARCH) {
  1328                                                 // leave search results
  1329                                                 state = previousState;
  1330                                         }
  1331                                         // scroll WVIEW window to top
  1332                                         windows[WVIEW].indexFirstLine = 0;
  1333                                         if (state == SHOWING_BOOKMARKS) {
  1334                                                 // currently showing bookmarks
  1335                                                 // stop showing bookmarks
  1336                                                 state = SHOWING_DATABASE;
  1337                                         }
  1338                                         if (!currentGroup[0]) {
  1339                                                 // a database is selected
  1340                                                 // add empty group id for root in pathInTree
  1341                                                 addGroupToPath("", selectedLine);
  1342                                                 smallDictt *d = getG(lsGroup, rtSmallDictt, selectedLine);
  1343                                                 char *dName = getG(d, rtChar, "title");
  1344                                                 select_database(dName);
  1345 
  1346                                                 // update database name in status bar
  1347                                                 free(popG(pathTitles, rtChar));
  1348                                                 pushG(pathTitles, dName);
  1349 
  1350                                                 // set current group root, database top
  1351                                                 strcpy(currentGroup, "root");
  1352                                                 selectedLine = 0;
  1353                                                 statusBar();
  1354                                                 eraseSubwindows();
  1355                                                 fillCols();
  1356                                                 break;
  1357                                         }
  1358                                         // in root enter all groups
  1359                                         // in groups, edit text for task at pos 0 (group title task)
  1360                                         if (is_this_task_a_group(selectedGroup) and ((selectedLine != 0) or eqG(currentGroup, "root"))) {
  1361                                                 // a group is selected
  1362                                                 // add currentGroup to pathInTree
  1363                                                 addGroupToPath(currentGroup, selectedLine);
  1364                                                 // set selected group as current group
  1365                                                 strcpy(currentGroup, selectedGroup);
  1366                                                 // line 0 is group title, select first task in group instead
  1367                                                 // viewGroup keeps the cursor in the list
  1368                                                 // so when the group is empty, it is moved to line 0
  1369                                                 selectedLine = 1;
  1370 
  1371                                                 // add new current group title to pathTitles for status bar
  1372                                                 pushNFreeG(pathTitles, trimG(get_task_title(currentGroup)));
  1373 
  1374                                                 statusBar();
  1375                                                 eraseSubwindows();
  1376                                                 fillCols();
  1377                                         }
  1378                                         else {
  1379                                                 // edit task, open editor
  1380                                                 edit_task(selectedGroup);
  1381                                                 // refresh screen
  1382                                                 system("reset");
  1383                                                 reInitScreen(mainwin);
  1384                                                 topMenu();
  1385                                                 statusBar();
  1386                                                 eraseSubwindows();
  1387                                                 // scroll WVIEW window to top
  1388                                                 windows[WVIEW].indexFirstLine = 0;
  1389                                                 fillCols();
  1390                                                 redrawwin(mainwin);
  1391                                                 refresh();
  1392                                                 showCols();
  1393                                                 forEachType(void, subwins, wd) {
  1394                                                         redrawwin((WINDOW *)(*wd));
  1395                                                 }
  1396                                         }
  1397                                         break;
  1398                                 case KEY_BACKSPACE:
  1399                                 case KEY_LEFT: {
  1400                                         // move back in browsing history
  1401                                         // scroll WVIEW window to top
  1402                                         windows[WVIEW].indexFirstLine = 0;
  1403                                         if (state == SHOWING_SEARCH) {
  1404                                                 // leave search results
  1405                                                 state = previousState;
  1406                                                 eraseSubwindows();
  1407                                                 fillCols();
  1408                                                 break;
  1409                                         }
  1410                                         if (state == SHOWING_BOOKMARKS) {
  1411                                                 // currently showing bookmarks
  1412                                                 // stop showing bookmarks and stay in same group
  1413                                                 state = SHOWING_DATABASE;
  1414                                                 eraseSubwindows();
  1415                                                 fillCols();
  1416                                                 break;
  1417                                         }
  1418                                         if ((staticArrayCount(pathInTree) == 1) and (currentGroup[0] not_eq 0)) {
  1419                                                 // path is root and currentGroup is not database list
  1420                                                 // show database list in WGROUP window
  1421                                                 currentGroup[0] = 0;
  1422                                         }
  1423 
  1424                                         moveToParentGroup();
  1425                                         if (lenG(pathTitles) > 1) {
  1426                                                 // always keep database name in path title
  1427                                                 // to show current database in status bar
  1428                                                 free(popG(pathTitles, rtChar));
  1429                                         }
  1430 
  1431                                         statusBar();
  1432                                         eraseSubwindows();
  1433                                         fillCols();
  1434                                         break;
  1435                                         }
  1436                                 case KEY_NPAGE:
  1437                                         // page down WVIEW window
  1438                                         // showSelected keeps the text in the window and adjusts indexFirstLine
  1439                                         windows[WVIEW].indexFirstLine += windows[WVIEW].szrow;
  1440                                         eraseSubwindows();
  1441                                         /* fillCols(); */
  1442                                         viewLeftParent(getParentGroupInPath());
  1443                                         viewGroup(currentGroup);
  1444                                         showSelected("");
  1445                                         break;
  1446                                 case KEY_PPAGE:
  1447                                         // page up WVIEW window
  1448                                         // showSelected keeps the text in the window and adjusts indexFirstLine
  1449                                         windows[WVIEW].indexFirstLine -= windows[WVIEW].szrow;
  1450                                         eraseSubwindows();
  1451                                         /* fillCols(); */
  1452                                         viewLeftParent(getParentGroupInPath());
  1453                                         viewGroup(currentGroup);
  1454                                         showSelected("");
  1455                                         break;
  1456                                 case KEY_F(1):
  1457                                         if (state != HELP) {
  1458                                                 previousState = state;
  1459                                                 state         = HELP;
  1460                                                 showHelp();
  1461                                         }
  1462                                         break;
  1463                                 case KEY_F(3): {
  1464                                         // toggle active filter
  1465                                         // deselect all items
  1466                                         emptyG(selectedList);
  1467                                         emptyG(selectedListAttributes);
  1468                                         if (state == SHOWING_SEARCH) {
  1469                                                 // leave search results
  1470                                                 state = previousState;
  1471                                         }
  1472                                         i32 *flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_ACTIVE);
  1473                                         if (*flt == DISABLE) {
  1474                                                 *flt = ENABLE;
  1475                                         }
  1476                                         else {
  1477                                                 *flt = DISABLE;
  1478                                         }
  1479                                         // scroll WVIEW window to top
  1480                                         windows[WVIEW].indexFirstLine = 0;
  1481                                         eraseSubwindows();
  1482                                         topMenu();
  1483                                         fillCols();
  1484                                         break;
  1485                                 }
  1486                                 case KEY_F(4): {
  1487                                         // toggle done filter
  1488                                         // deselect all items
  1489                                         emptyG(selectedList);
  1490                                         emptyG(selectedListAttributes);
  1491                                         if (state == SHOWING_SEARCH) {
  1492                                                 // leave search results
  1493                                                 state = previousState;
  1494                                         }
  1495                                         i32 *flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_DONE);
  1496                                         if (*flt == DISABLE) {
  1497                                                 *flt = ENABLE;
  1498                                         }
  1499                                         else {
  1500                                                 *flt = DISABLE;
  1501                                         }
  1502                                         // scroll WVIEW window to top
  1503                                         windows[WVIEW].indexFirstLine = 0;
  1504                                         eraseSubwindows();
  1505                                         topMenu();
  1506                                         fillCols();
  1507                                         break;
  1508                                 }
  1509                                 case KEY_F(5): {
  1510                                         // toggle ongoing filter
  1511                                         // deselect all items
  1512                                         emptyG(selectedList);
  1513                                         emptyG(selectedListAttributes);
  1514                                         if (state == SHOWING_SEARCH) {
  1515                                                 // leave search results
  1516                                                 state = previousState;
  1517                                         }
  1518                                         i32 *flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_ONGOING);
  1519                                         if (*flt == DISABLE) {
  1520                                                 *flt = ENABLE;
  1521                                         }
  1522                                         else {
  1523                                                 *flt = DISABLE;
  1524                                         }
  1525                                         // scroll WVIEW window to top
  1526                                         windows[WVIEW].indexFirstLine = 0;
  1527                                         eraseSubwindows();
  1528                                         topMenu();
  1529                                         fillCols();
  1530                                         break;
  1531                                 }
  1532                                 case KEY_F(6): {
  1533                                         // toggle pending filter
  1534                                         // deselect all items
  1535                                         emptyG(selectedList);
  1536                                         emptyG(selectedListAttributes);
  1537                                         if (state == SHOWING_SEARCH) {
  1538                                                 // leave search results
  1539                                                 state = previousState;
  1540                                         }
  1541                                         i32 *flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_PENDING);
  1542                                         if (*flt == DISABLE) {
  1543                                                 *flt = ENABLE;
  1544                                         }
  1545                                         else {
  1546                                                 *flt = DISABLE;
  1547                                         }
  1548                                         // scroll WVIEW window to top
  1549                                         windows[WVIEW].indexFirstLine = 0;
  1550                                         eraseSubwindows();
  1551                                         topMenu();
  1552                                         fillCols();
  1553                                         break;
  1554                                 }
  1555                                 case KEY_F(7): {
  1556                                         // toggle inactive filter
  1557                                         // deselect all items
  1558                                         emptyG(selectedList);
  1559                                         emptyG(selectedListAttributes);
  1560                                         if (state == SHOWING_SEARCH) {
  1561                                                 // leave search results
  1562                                                 state = previousState;
  1563                                         }
  1564                                         i32 *flt = (i32 *) getG(status_filters, rtI32P, TASK_STATUS_INACTIVE);
  1565                                         if (*flt == DISABLE) {
  1566                                                 *flt = ENABLE;
  1567                                         }
  1568                                         else {
  1569                                                 *flt = DISABLE;
  1570                                         }
  1571                                         // scroll WVIEW window to top
  1572                                         windows[WVIEW].indexFirstLine = 0;
  1573                                         eraseSubwindows();
  1574                                         topMenu();
  1575                                         fillCols();
  1576                                         break;
  1577                                 }
  1578                                 case KEY_F(8): {
  1579                                         // show all descriptions for tasks in list
  1580                                         if (viewText) {
  1581                                                 terminateG(viewText);
  1582                                         }
  1583                                         if (state == SHOWING_SEARCH) {
  1584                                                 // show tasks from search results
  1585                                                 viewText = listTasksInList(lsGroup);
  1586                                                 viewTextIsGroup = true;
  1587                                         }
  1588                                         if (state == SHOWING_DATABASE) {
  1589                                                 // show tasks in current group
  1590                                                 viewText = listTasksInGroup(currentGroup);
  1591                                                 viewTextIsGroup = true;
  1592                                         }
  1593                                         if (state == SHOWING_BOOKMARKS) {
  1594                                                 // show tasks from bookmarks
  1595                                                 viewText = listTasksInBookmarks();
  1596                                                 viewTextIsGroup = true;
  1597                                         }
  1598                                         // scroll WVIEW window to top
  1599                                         windows[WVIEW].indexFirstLine = 0;
  1600                                         eraseSubwindows();
  1601                                         /* fillCols(); */
  1602                                         viewLeftParent(getParentGroupInPath());
  1603                                         viewGroup(currentGroup);
  1604                                         showSelected("");
  1605                                         break;
  1606                                 }
  1607                                 case KEY_F(9):
  1608                                         // toggle info
  1609                                         if (not stateInfo) {
  1610                                                 stateInfo = true;
  1611                                                 if (viewText) {
  1612                                                         terminateG(viewText);
  1613                                                 }
  1614                                                 //viewText = createSA("fwefw", "wfwef");
  1615                                                 viewText = show_group_for_task(selectedGroup);
  1616                                                 viewTextIsGroup = false;
  1617                                                 // scroll WVIEW window to top
  1618                                                 windows[WVIEW].indexFirstLine = 0;
  1619                                                 eraseSubwindows();
  1620                                                 /* fillCols(); */
  1621                                                 viewLeftParent(getParentGroupInPath());
  1622                                                 viewGroup(currentGroup);
  1623                                                 showSelected("");
  1624                                         }
  1625                                         else {
  1626                                                 stateInfo = false;
  1627                                                 // scroll WVIEW window to top
  1628                                                 windows[WVIEW].indexFirstLine = 0;
  1629                                                 eraseSubwindows();
  1630                                                 fillCols();
  1631                                         }
  1632                                         break;
  1633                                 case '/':
  1634                                 case KEY_F(10): {
  1635                                         // search string in group
  1636                                         if (state == SHOWING_SEARCH) {
  1637                                                 // leave search results
  1638                                                 state = previousState;
  1639                                                 break;
  1640                                         }
  1641                                         iEmptySF(&searchString);
  1642                                         previousState = state;
  1643                                         state = SEARCH;
  1644                                         echo();
  1645                                         move(row-1,0);
  1646                                         color_set(1, NULL);
  1647                                         clrtoeol();
  1648                                         addnstr("SEARCH (F1 to cancel): ", col-1);
  1649                                         chgat(-1, 0, 1, NULL);
  1650                                         break;
  1651                                 }
  1652                                 case KEY_F(11): {
  1653                                         // bookmark items
  1654                                         // remove from bookmark if already bookmarked
  1655                                         if (not lenG(selectedList)) {
  1656                                                 ssize_t index = findG(bookmarks, selectedGroup);
  1657                                                 if (index == -1) {
  1658                                                         // no duplicated bookmarks
  1659                                                         // remove empty strings in bookmarks
  1660                                                         compactG(bookmarks);
  1661                                                         pushG(bookmarks, selectedGroup);
  1662                                                 }
  1663                                                 else {
  1664                                                         // remove bookmark
  1665                                                         delG(bookmarks, index, index+1);
  1666                                                 }
  1667                                         }
  1668                                         else {
  1669                                                 // selected items in selectedList
  1670                                                 forEachSmallArray(selectedList, T) {
  1671                                                         castS(t, T);
  1672                                                         ssize_t index = findG(bookmarks, ssGet(t));
  1673                                                         if (index == -1) {
  1674                                                                 // no duplicated bookmarks
  1675                                                                 pushG(bookmarks, ssGet(t));
  1676                                                         }
  1677                                                         else {
  1678                                                                 // remove bookmark
  1679                                                                 delG(bookmarks, index, index+1);
  1680                                                         }
  1681                                                         finishG(t);
  1682                                                 }
  1683                                                 emptyG(selectedList);
  1684                                                 emptyG(selectedListAttributes);
  1685                                         }
  1686                                         break;
  1687                                 }
  1688                                 case KEY_F(12): {
  1689                                         // show bookmarks
  1690                                         if (state == SHOWING_SEARCH) {
  1691                                                 // leave search results
  1692                                                 state = previousState;
  1693                                         }
  1694                                         if ((state != SHOWING_BOOKMARKS) and (not eqG(getG(bookmarks, rtChar, 0), ""))) {
  1695                                                 state = SHOWING_BOOKMARKS;
  1696                                         }
  1697                                         else if (state == SHOWING_BOOKMARKS) {
  1698                                                 state = SHOWING_DATABASE;
  1699                                         }
  1700                                         // scroll WVIEW window to top
  1701                                         windows[WVIEW].indexFirstLine = 0;
  1702                                         eraseSubwindows();
  1703                                         fillCols();
  1704                                         break;
  1705                                 }
  1706                                 case 'g':
  1707                                         // set selected item active
  1708                                         if (currentGroup[0]) {
  1709                                                 // change status for all selected tasks
  1710                                                 if (not lenG(selectedList)) {
  1711                                                         set_status(selectedGroup, TASK_STATUS_ACTIVE);
  1712                                                 }
  1713                                                 else {
  1714                                                         // selected items in selectedList
  1715                                                         forEachSmallArray(selectedList, T) {
  1716                                                                 castS(t, T);
  1717                                                                 set_status(ssGet(t), TASK_STATUS_ACTIVE);
  1718                                                                 finishG(t);
  1719                                                         }
  1720                                                         emptyG(selectedList);
  1721                                                         emptyG(selectedListAttributes);
  1722                                                 }
  1723 
  1724                                                 // scroll WVIEW window to top
  1725                                                 windows[WVIEW].indexFirstLine = 0;
  1726                                                 eraseSubwindows();
  1727                                                 fillCols();
  1728                                         }
  1729                                         break;
  1730                                 case 'h':
  1731                                         // set selected item active
  1732                                         if (currentGroup[0]) {
  1733                                                 // change status for all selected tasks
  1734                                                 if (not lenG(selectedList)) {
  1735                                                         set_status(selectedGroup, TASK_STATUS_DONE);
  1736                                                 }
  1737                                                 else {
  1738                                                         // selected items in selectedList
  1739                                                         forEachSmallArray(selectedList, T) {
  1740                                                                 castS(t, T);
  1741                                                                 set_status(ssGet(t), TASK_STATUS_DONE);
  1742                                                                 finishG(t);
  1743                                                         }
  1744                                                         emptyG(selectedList);
  1745                                                         emptyG(selectedListAttributes);
  1746                                                 }
  1747 
  1748                                                 // scroll WVIEW window to top
  1749                                                 windows[WVIEW].indexFirstLine = 0;
  1750                                                 eraseSubwindows();
  1751                                                 fillCols();
  1752                                         }
  1753                                         break;
  1754                                 case 'j':
  1755                                         // set selected item active
  1756                                         if (currentGroup[0]) {
  1757                                                 // change status for all selected tasks
  1758                                                 if (not lenG(selectedList)) {
  1759                                                         set_status(selectedGroup, TASK_STATUS_ONGOING);
  1760                                                 }
  1761                                                 else {
  1762                                                         // selected items in selectedList
  1763                                                         forEachSmallArray(selectedList, T) {
  1764                                                                 castS(t, T);
  1765                                                                 set_status(ssGet(t), TASK_STATUS_ONGOING);
  1766                                                                 finishG(t);
  1767                                                         }
  1768                                                         emptyG(selectedList);
  1769                                                         emptyG(selectedListAttributes);
  1770                                                 }
  1771 
  1772                                                 // scroll WVIEW window to top
  1773                                                 windows[WVIEW].indexFirstLine = 0;
  1774                                                 eraseSubwindows();
  1775                                                 fillCols();
  1776                                         }
  1777                                         break;
  1778                                 case 'k':
  1779                                         // set selected item active
  1780                                         if (currentGroup[0]) {
  1781                                                 // change status for all selected tasks
  1782                                                 if (not lenG(selectedList)) {
  1783                                                         set_status(selectedGroup, TASK_STATUS_PENDING);
  1784                                                 }
  1785                                                 else {
  1786                                                         // selected items in selectedList
  1787                                                         forEachSmallArray(selectedList, T) {
  1788                                                                 castS(t, T);
  1789                                                                 set_status(ssGet(t), TASK_STATUS_PENDING);
  1790                                                                 finishG(t);
  1791                                                         }
  1792                                                         emptyG(selectedList);
  1793                                                         emptyG(selectedListAttributes);
  1794                                                 }
  1795 
  1796                                                 // scroll WVIEW window to top
  1797                                                 windows[WVIEW].indexFirstLine = 0;
  1798                                                 eraseSubwindows();
  1799                                                 fillCols();
  1800                                         }
  1801                                         break;
  1802                                 case 'l':
  1803                                         // set selected item active
  1804                                         if (currentGroup[0]) {
  1805                                                 // change status for all selected tasks
  1806                                                 if (not lenG(selectedList)) {
  1807                                                         set_status(selectedGroup, TASK_STATUS_INACTIVE);
  1808                                                 }
  1809                                                 else {
  1810                                                         // selected items in selectedList
  1811                                                         forEachSmallArray(selectedList, T) {
  1812                                                                 castS(t, T);
  1813                                                                 set_status(ssGet(t), TASK_STATUS_INACTIVE);
  1814                                                                 finishG(t);
  1815                                                         }
  1816                                                         emptyG(selectedList);
  1817                                                         emptyG(selectedListAttributes);
  1818                                                 }
  1819 
  1820                                                 // scroll WVIEW window to top
  1821                                                 windows[WVIEW].indexFirstLine = 0;
  1822                                                 eraseSubwindows();
  1823                                                 fillCols();
  1824                                         }
  1825                                         break;
  1826                                 case 'o':
  1827                                         // set selected item active
  1828                                         if (currentGroup[0]) {
  1829                                                 // change status for all selected tasks
  1830                                                 if (not lenG(selectedList)) {
  1831                                                         set_status(selectedGroup, TASK_STATUS_VOID);
  1832                                                 }
  1833                                                 else {
  1834                                                         // selected items in selectedList
  1835                                                         forEachSmallArray(selectedList, T) {
  1836                                                                 castS(t, T);
  1837                                                                 set_status(ssGet(t), TASK_STATUS_VOID);
  1838                                                                 finishG(t);
  1839                                                         }
  1840                                                         emptyG(selectedList);
  1841                                                         emptyG(selectedListAttributes);
  1842                                                 }
  1843 
  1844                                                 // scroll WVIEW window to top
  1845                                                 windows[WVIEW].indexFirstLine = 0;
  1846                                                 eraseSubwindows();
  1847                                                 fillCols();
  1848                                         }
  1849                                         break;
  1850                                 case 'z':
  1851                                         // select all in list
  1852                                         if (currentGroup[0]) {
  1853                                                 // select only in group, not database list
  1854                                                 if (lenG(selectedList)) {
  1855                                                         // deselect all items
  1856                                                         emptyG(selectedList);
  1857                                                         emptyG(selectedListAttributes);
  1858                                                 }
  1859                                                 else {
  1860                                                         // nothing is selected, select all
  1861                                                         // except group title
  1862                                                         enumerateSmallArray(lsGroup, D, i) {
  1863                                                                 cast(smallDictt*, d, D);
  1864                                                                 // in groups don't select group title
  1865                                                                 if ((i not_eq 0) or eqG(currentGroup, "root") or (state != SHOWING_DATABASE)) {
  1866                                                                                 char *s = getG(d, rtChar, "tid");
  1867                                                                                 if (not isBlankG(s)) {
  1868                                                                                         pushG(selectedList, s);
  1869                                                                                         createAllocateSmallArray(a);
  1870                                                                                         pushG(a, currentGroup);
  1871                                                                                         pushG(a, (int32_t)state);
  1872                                                                                         pushNFreeG(selectedListAttributes, a);
  1873                                                                                 }
  1874                                                                 }
  1875                                                                 finishG(d);
  1876                                                         }
  1877                                                 }
  1878                                                 showCols();
  1879                                         }
  1880                                         break;
  1881                                 case 'x':
  1882                                         // cut selected items
  1883                                         if (currentGroup[0]) {
  1884                                                 // select only in group, not database list
  1885                                                 if (not lenG(selectedList)) {
  1886                                                         pushG(selectedList, selectedGroup);
  1887                                                         createAllocateSmallArray(a);
  1888                                                         pushG(a, currentGroup);
  1889                                                         pushG(a, (int32_t)state);
  1890                                                         pushNFreeG(selectedListAttributes, a);
  1891                                                 }
  1892                                                 paperfunction = CUT;
  1893 
  1894                                                 move(row-1,0);
  1895                                                 color_set(cursorColor, NULL);
  1896                                                 clrtoeol();
  1897                                                 addnstr("Cut selected items, move to destination and paste (v)", col-1);
  1898                                                 chgat(-1, 0, cursorColor, NULL);
  1899                                         }
  1900                                         break;
  1901                                 case 'c':
  1902                                         // copy selected items
  1903                                         if (currentGroup[0]) {
  1904                                                 // select only in group, not database list
  1905                                                 if (not lenG(selectedList)) {
  1906                                                         pushG(selectedList, selectedGroup);
  1907                                                         createAllocateSmallArray(a);
  1908                                                         pushG(a, currentGroup);
  1909                                                         pushG(a, (int32_t)state);
  1910                                                         pushNFreeG(selectedListAttributes, a);
  1911                                                 }
  1912                                                 paperfunction = COPIED;
  1913 
  1914                                                 move(row-1,0);
  1915                                                 color_set(cursorColor, NULL);
  1916                                                 clrtoeol();
  1917                                                 addnstr("Copied selected items, move to destination and paste (v) or link (b)", col-1);
  1918                                                 chgat(-1, 0, cursorColor, NULL);
  1919                                         }
  1920                                         break;
  1921                                 case 'v':
  1922                                         if ((currentGroup[0]) and (state == SHOWING_DATABASE) and (paperfunction != NO_PAPER)) {
  1923                                                 pasteClipboard("paste");
  1924                                                 // scroll WVIEW window to top
  1925                                                 windows[WVIEW].indexFirstLine = 0;
  1926                                                 eraseSubwindows();
  1927                                                 fillCols();
  1928                                         }
  1929                                         break;
  1930                                 case 'b':
  1931                                         // link selected tasks
  1932                                         if ((currentGroup[0]) and (state == SHOWING_DATABASE) and (paperfunction != NO_PAPER)) {
  1933                                                 pasteClipboard("link");
  1934                                                 // scroll WVIEW window to top
  1935                                                 windows[WVIEW].indexFirstLine = 0;
  1936                                                 eraseSubwindows();
  1937                                                 fillCols();
  1938                                         }
  1939                                         break;
  1940                                 /* case KEY_MOUSE: */
  1941                                 /*         mvaddstr(row-4, 0, "mouse event"); */
  1942                                 /*         if(getmouse(&event) == OK) { */
  1943                                 /*                 mvprintw(row-3,0,"x %d y %d z %d bstate 0x%x", event.x, event.y, event.z, event.bstate); */
  1944                                 /*         } */
  1945                                 /*         break; */
  1946                         }
  1947                 }
  1948                 refreshAll();
  1949         }
  1950 exit:
  1951         finalizeScreen(mainwin);
  1952 
  1953         //test();
  1954 
  1955         // save bookmarks in ini file
  1956         smallDictt *data      = getG(ini, rtSmallDictt, "data");
  1957         smallDictt *locations = getG(ini, rtSmallDictt, "locations");
  1958         smallDictt *filters   = getG(ini, rtSmallDictt, "filters");
  1959         smallDictt *desktop = getG(ini, rtSmallDictt, "desktop");
  1960 
  1961         setNFreeG(data, "location", data_location);
  1962 
  1963         setNFreeG(locations, "add_top_or_bottom", add_top_or_bottom);
  1964 
  1965         enumerateS(TASK_STATUS_TRIM, name, i) {
  1966                 i32 flt = getG(status_filters, rtI32, i);
  1967                 setG(filters, name, STATUS_FILTER_STATES[flt]);
  1968         }
  1969         if (desktop) {
  1970                 smallStringt *b     = joinG(bookmarks, "|");
  1971                 setNFreeG(desktop, "bookmarks", b);
  1972         }
  1973         else {
  1974                 // no desktop section in ini file, empty bookmarks
  1975                 if (lenG(bookmarks)) {
  1976                         // create a desktop section
  1977                         desktop = allocG(rtSmallDictt);
  1978                         smallStringt *b     = joinG(bookmarks, "|");
  1979                         setNFreeG(desktop, "bookmarks", b);
  1980                         setG(ini, "desktop", desktop);
  1981                 }
  1982         }
  1983 
  1984         char *p = expandHomeG("~/.easydoneit.ini");
  1985         saveIni(ini, p);
  1986         free(p);
  1987         //logVarG(ini);
  1988 
  1989         saveEdiState();
  1990 
  1991         terminateG(ini);
  1992 
  1993         finalizeLibsheepy();
  1994 
  1995         XSUCCESS
  1996 }