💾 Archived View for gmi.noulin.net › gitRepositories › easydoneitCTui › file › edt.c.gmi captured on 2023-01-29 at 13:15:52. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
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 }