void tasklist_check_curs_pos() /* {{{ */ { /* check if the cursor is in a valid position */ const int onscreentasks = getmaxy(tasklist); /* log starting cursor position */ tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "cursor_check(init) - selline:%d offset:%d taskcount:%d perscreen:%d", selline, pageoffset, taskcount, rows-3); /* check 0<=selline<taskcount */ if (selline<0) selline = 0; else if (selline>=taskcount) selline = taskcount-1; /* check if page offset is necessary */ if (taskcount<=onscreentasks) pageoffset = 0; /* offset up if necessary */ else if (selline<pageoffset) pageoffset = selline; /* offset down if necessary */ else if (taskcount>onscreentasks && pageoffset+onscreentasks-1<selline) pageoffset = selline-onscreentasks+1; /* dont allow blank lines if there is an offset */ else if (taskcount>onscreentasks && taskcount-pageoffset<onscreentasks) pageoffset = taskcount-onscreentasks; /* log cursor position */ tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "cursor_check - selline:%d offset:%d taskcount:%d perscreen:%d", selline, pageoffset, taskcount, rows-3); } /* }}} */
void ncurses_init() /* {{{ */ { /* initialize ncurses */ int ret; /* register signals */ signal(SIGINT, ncurses_end); signal(SIGKILL, ncurses_end); signal(SIGSEGV, ncurses_end); /* initialize screen */ tnc_fprintf(stdout, LOG_DEBUG, "starting ncurses..."); if ((stdscr = initscr()) == NULL ) { fprintf(stderr, "Error initialising ncurses.\n"); exit(EXIT_FAILURE); } /* start colors */ ret = init_colors(); if (ret) { fprintf(stderr, "Error initializing colors.\n"); tnc_fprintf(logfp, LOG_ERROR, "error initializing colors (%d)", ret); } } /* }}} */
void run_command_source_cmd(const char *cmdstr) /* {{{ */ { /* run commands generated by a command */ FILE *cmd = popen(cmdstr, "r"); tnc_fprintf(logfp, LOG_DEBUG, "source: command \"%s\"", cmdstr); /* check for a valid fd */ if (cmdstr == NULL) { tnc_fprintf(logfp, LOG_ERROR, "source: file \"%s\" could not be opened", cmdstr); statusbar_message(cfg.statusbar_timeout, "source: command \"%s\" could not be opened", cmdstr); return; } /* exit window */ def_prog_mode(); endwin(); /* read command file */ source_fp(cmd); /* force redraw */ reset_prog_mode(); redraw = true; /* close config file */ pclose(cmd); tnc_fprintf(logfp, LOG_DEBUG, "source complete: \"%s\"", cmdstr); statusbar_message(cfg.statusbar_timeout, "source complete: \"%s\"", cmdstr); } /* }}} */
void ncurses_end(int sig) /* {{{ */ { /* terminate ncurses * sig - the signal which is terminating the program */ bool print_check_log = true; char *logpath; delwin(header); delwin(tasklist); delwin(statusbar); delwin(stdscr); endwin(); switch (sig) { case SIGINT: tnc_fprintf(stdout, LOG_DEFAULT, "aborted"); tnc_fprintf(logfp, LOG_DEBUG, "received SIGINT, exiting"); break; case SIGSEGV: tnc_fprintf(logfp, LOG_ERROR, "SEGFAULT"); tnc_fprintf(logfp, LOG_ERROR, "segmentation fault, exiting"); break; case SIGKILL: tnc_fprintf(stdout, LOG_ERROR, "killed"); tnc_fprintf(logfp, LOG_ERROR, "received SIGKILL, exiting"); break; case -1: tnc_fprintf(stdout, LOG_ERROR, "an error has occurred, exiting"); break; default: tnc_fprintf(stdout, LOG_DEFAULT, "done"); tnc_fprintf(logfp, LOG_DEBUG, "exiting with code %d", sig); print_check_log = false; break; } /* tell user to check logs */ if (print_check_log) { asprintf(&logpath, LOGFILE, getenv("USER")); tnc_fprintf(stdout, LOG_ERROR, "tasknc ended abnormally. please check '%s' for details", logpath); free(logpath); fflush(stdout); } /* free memory */ cleanup(); exit(0); } /* }}} */
void run_command_color(char *args) /* {{{ */ { /** * create/modify a color rule * syntax: object foreground background [rule] */ char *object = NULL, *fg = NULL, *bg = NULL, *rule = NULL; color_object obj; int ret = 0, fgc, bgc; if (args != NULL) ret = sscanf(args, "%ms %m[a-z0-9-] %m[a-z0-9-] %m[^\n]", &object, &fg, &bg, &rule); if (ret < 3) { statusbar_message(cfg.statusbar_timeout, "syntax: color <object> <foreground> <background> <rule>"); tnc_fprintf(logfp, LOG_ERROR, "syntax: color <object> <foreground> <background> <rule> [%d](%s)", ret, args); goto cleanup; } /* parse object */ obj = parse_object(object); if (obj == OBJECT_NONE) { statusbar_message(cfg.statusbar_timeout, "color: invalid object \"%s\"", object); tnc_fprintf(logfp, LOG_ERROR, "color: invalid object \"%s\"", object); goto cleanup; } /* parse colors */ fgc = parse_color(fg); bgc = parse_color(bg); if (bgc < -2 || fgc < -2) { statusbar_message(cfg.statusbar_timeout, "color: invalid colors \"%s\" \"%s\"", fg, bg); tnc_fprintf(logfp, LOG_ERROR, "color: invalid colors %d:\"%s\" %d:\"%s\"", fgc, fg, bgc, bg); goto cleanup; } /* create color rule */ if (add_color_rule(obj, rule, fgc, bgc)>=0) statusbar_message(cfg.statusbar_timeout, "applied color rule"); else statusbar_message(cfg.statusbar_timeout, "applying color rule failed"); goto cleanup; cleanup: check_free(object); check_free(fg); check_free(bg); check_free(rule); } /* }}} */
short add_color_pair(short askpair, short fg, short bg) /* {{{ */ { /* initialize a color pair and return its pair number */ short pair = 1; /* pick a color number if none is specified */ if (askpair<=0) { while (pair<COLOR_PAIRS && pairs_used[pair]) pair++; if (pair == COLOR_PAIRS) return -1; } /* check if pair requested is being used */ else { if (pairs_used[askpair]) return -1; pair = askpair; } /* initialize pair */ if (init_pair(pair, fg, bg) == ERR) return -1; /* mark pair as used and exit */ pairs_used[pair] = true; tnc_fprintf(logfp, LOG_DEBUG, "assigned color pair %hd to (%hd, %hd)", pair, fg, bg); return pair; } /* }}} */
void run_command_source(const char *filepath) /* {{{ */ { /* run the commands contained in a config file */ FILE *config = NULL; config = fopen(filepath, "r"); tnc_fprintf(logfp, LOG_DEBUG, "source: file \"%s\"", filepath); /* check for a valid fd */ if (config == NULL) { tnc_fprintf(logfp, LOG_ERROR, "source: file \"%s\" could not be opened", filepath); statusbar_message(cfg.statusbar_timeout, "source: file \"%s\" could not be opened", filepath); return; } /* read config file */ source_fp(config); /* close config file */ fclose(config); tnc_fprintf(logfp, LOG_DEBUG, "source complete: \"%s\"", filepath); statusbar_message(cfg.statusbar_timeout, "source complete: \"%s\"", filepath); } /* }}} */
int umvaddstr(WINDOW *win, const int y, const int x, const char *format, ...) /* {{{ */ { /* convert a string to a wchar string and mvaddwstr * win - the window to print the string in * y - the y coordinates to print the string at * x - the x coordinates to print the string at * format - the format string to print * additional args are accepted to use with the format string * (similar to printf) * return is the return of mvwaddnwstr */ int len, r; wchar_t *wstr; char *str; va_list args; /* build str */ va_start(args, format); const int slen = sizeof(wchar_t)*(cols-x+1)/sizeof(char); str = calloc(slen, sizeof(char)); vsnprintf(str, slen-1, format, args); va_end(args); /* allocate wchar_t string */ len = strlen(str)+1; wstr = calloc(len, sizeof(wchar_t)); /* check for valid allocation */ if (wstr==NULL) { tnc_fprintf(logfp, LOG_ERROR, "critical: umvaddstr failed to malloc"); return -1; } /* perform conversion and write to screen */ mbstowcs(wstr, str, len); len = wcslen(wstr); if (len>cols-x) len = cols-x; r = mvwaddnwstr(win, y, x, wstr, len); /* free memory allocated */ free(wstr); free(str); return r; } /* }}} */
void run_command_show(const char *arg) /* {{{ */ { /** * display a variable in the statusbar * syntax: variable */ var *this_var; char *message = NULL; int ret = 0; /* parse arg */ if (arg != NULL) ret = sscanf(arg, "%m[^\n]", &message); if (ret != 1) { statusbar_message(cfg.statusbar_timeout, "syntax: show <variable>"); tnc_fprintf(logfp, LOG_ERROR, "syntax: show <variable> [%d](%s)", ret, arg); goto cleanup; } /* check for a variable */ if (arg == NULL) { statusbar_message(cfg.statusbar_timeout, "no variable specified!"); goto cleanup; } /* find the variable */ this_var = (var *)find_var(arg); if (this_var==NULL) { statusbar_message(cfg.statusbar_timeout, "variable not found: %s", arg); goto cleanup; } /* acquire the value string and print it */ message = var_value_message(this_var, 1); statusbar_message(cfg.statusbar_timeout, message); cleanup: free(message); return; } /* }}} */
void run_command_unbind(char *argstr) /* {{{ */ { /** * unbind a key * syntax - mode key */ char *modestr = NULL, *keystr = NULL, *keyname = NULL; prog_mode mode; int ret = 0; /* parse args */ if (argstr != NULL) ret = sscanf(argstr, "%ms %m[^\n]", &modestr, &keystr); if (ret != 2) { statusbar_message(cfg.statusbar_timeout, "syntax: unbind <mode> <key>"); tnc_fprintf(logfp, LOG_ERROR, "syntax: unbind <mode> <key> [%d](%s)", ret, argstr); goto cleanup; } /* parse mode */ if (str_eq(modestr, "pager")) mode = MODE_PAGER; else if (str_eq(modestr, "tasklist")) mode = MODE_TASKLIST; else mode = MODE_ANY; int key = parse_key(keystr); remove_keybinds(key, mode); keyname = name_key(key); statusbar_message(cfg.statusbar_timeout, "key unbound: %s (%d)", keyname, key); goto cleanup; cleanup: free(keyname); free(modestr); free(keystr); return; } /* }}} */
void key_tasklist_sort(const char* arg) { /* {{{ */ /* handle a keyboard direction to sort * arg - the mode to sort by (pass NULL to prompt user) * see the manual page for how sort strings are parsed */ char* uuid = NULL; /* store selected task */ struct task* cur = get_task_by_position(selline); if (cur != NULL) { uuid = strdup(cur->uuid); } tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "sort: initial task uuid=%s", uuid); check_free(cfg.sortmode); if (arg == NULL) { /* store sort string */ cfg.sortmode = calloc(cols, sizeof(char)); statusbar_getstr(&(cfg.sortmode), "sort by: "); sb_timeout = time(NULL) + 3; } else { cfg.sortmode = strdup(arg); } /* run sort */ sort_wrapper(head); /* follow original task */ if (cfg.follow_task) { set_position_by_uuid(uuid); tasklist_check_curs_pos(); } check_free(uuid); /* force redraw */ redraw = true; } /* }}} */
void find_next_search_result(task *head, task *pos) /* {{{ */ { /* find the next search result in the list of tasks * head - the first task in the task list * pos - the position in the task list to start searching from */ task *cur; cur = pos; while (1) { /* move to next item */ cur = cur->next; /* move to head if end of list is reached */ if (cur == NULL) { cur = head; selline = 0; tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "search wrapped"); statusbar_message(cfg.statusbar_timeout, "search wrapped to top"); } else selline++; /* check for match */ if (task_match(cur, searchstring)) return; /* stop if full loop was made */ if (cur==pos) break; } statusbar_message(cfg.statusbar_timeout, "no matches: %s", searchstring); return; } /* }}} */
void handle_resize() /* {{{ */ { /* handle a change in screen size */ int pagerheight; /* make sure rows and cols are set correctly */ rows = getmaxy(stdscr); cols = getmaxx(stdscr); /* resize windows */ wresize(header, 1, cols); wresize(tasklist, rows-2, cols); wresize(statusbar, 1, cols); /* move to proper positions */ mvwin(header, 0, 0); mvwin(tasklist, 1, 0); mvwin(statusbar, rows-1, 0); /* handle pager */ if (pager != NULL) { pagerheight = getmaxy(pager); if (pagerheight > rows-2) pagerheight = rows-2; wresize(pager, pagerheight, cols); mvwin(pager, rows-pagerheight-1, 0); } /* redraw windows */ tasklist_print_task_list(); print_header(); /* message about resize */ tnc_fprintf(logfp, LOG_DEBUG, "window resized to y=%d x=%d", rows, cols); statusbar_message(cfg.statusbar_timeout, "window resized to y=%d x=%d", rows, cols); } /* }}} */
void key_tasklist_scroll(const int direction) /* {{{ */ { /* handle a keyboard direction to scroll * direction - the direction to scroll in * u = up one * d = down one * h = to first element in list * e = to last element in list */ const char oldsel = selline; const char oldoffset = pageoffset; switch (direction) { case 'u': /* scroll one up */ if (selline>0) { selline--; if (selline<pageoffset) pageoffset--; } else statusbar_message(cfg.statusbar_timeout, "already at top"); break; case 'd': /* scroll one down */ if (selline<taskcount-1) { selline++; if (selline>=pageoffset+rows-2) pageoffset++; } else statusbar_message(cfg.statusbar_timeout, "already at bottom"); break; case 'h': /* go to first entry */ pageoffset = 0; selline = 0; break; case 'e': /* go to last entry */ if (taskcount>rows-2) pageoffset = taskcount-rows+2; selline = taskcount-1; break; default: statusbar_message(cfg.statusbar_timeout, "invalid scroll direction"); break; } if (pageoffset!=oldoffset) redraw = true; else { if (oldsel-selline == 1) tasklist_print_task(selline, NULL, 2); else if (selline-oldsel == 1) tasklist_print_task(oldsel, NULL, 2); else { tasklist_print_task(oldsel, NULL, 1); tasklist_print_task(selline, NULL, 1); } } print_header(); tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "selline:%d offset:%d tasks:%d", selline, pageoffset, taskcount); } /* }}} */
void configure(void) /* {{{ */ { /* parse config file to get runtime options */ FILE *cmd; char *filepath, *xdg_config_home, *home; int ret = 0; /* set default settings */ cfg.nc_timeout = NCURSES_WAIT; /* time getch will wait */ cfg.statusbar_timeout = STATUSBAR_TIMEOUT_DEFAULT; /* default time before resetting statusbar */ cfg.sortmode = strdup("drpu"); /* determine sort order */ cfg.follow_task = true; /* follow task after it is moved */ cfg.history_max = 50; /* set default formats */ cfg.formats.title = strdup(" $program_name ($selected_line/$task_count) $> $date"); cfg.formats.task = strdup(" $project $description $> ?$due?$due?$-6priority?"); cfg.formats.view = strdup(" task info"); /* set initial filter */ active_filter = strdup("status:pending"); /* get task version */ cmd = popen("task --version", "r"); while (ret != 1) ret = fscanf(cmd, "%m[0-9.-] ", &(cfg.version)); tnc_fprintf(logfp, LOG_DEBUG, "task version: %s", cfg.version); pclose(cmd); /* default keybinds */ add_keybind(ERR, NULL, NULL, MODE_TASKLIST); add_keybind(ERR, NULL, NULL, MODE_PAGER); add_keybind(KEY_RESIZE, handle_resize, NULL, MODE_ANY); add_keybind('k', key_tasklist_scroll_up, NULL, MODE_TASKLIST); add_keybind('k', key_pager_scroll_up, NULL, MODE_PAGER); add_keybind(KEY_UP, key_tasklist_scroll_up, NULL, MODE_TASKLIST); add_keybind(KEY_UP, key_pager_scroll_up, NULL, MODE_PAGER); add_keybind('j', key_tasklist_scroll_down, NULL, MODE_TASKLIST); add_keybind('j', key_pager_scroll_down, NULL, MODE_PAGER); add_keybind(KEY_DOWN, key_tasklist_scroll_down, NULL, MODE_TASKLIST); add_keybind(KEY_DOWN, key_pager_scroll_down, NULL, MODE_PAGER); add_keybind('g', key_tasklist_scroll_home, NULL, MODE_TASKLIST); add_keybind(KEY_HOME, key_tasklist_scroll_home, NULL, MODE_TASKLIST); add_keybind('g', key_pager_scroll_home, NULL, MODE_PAGER); add_keybind(KEY_HOME, key_pager_scroll_home, NULL, MODE_PAGER); add_keybind('G', key_pager_scroll_end, NULL, MODE_PAGER); add_keybind(KEY_END, key_pager_scroll_end, NULL, MODE_PAGER); add_keybind('G', key_tasklist_scroll_end, NULL, MODE_TASKLIST); add_keybind(KEY_END, key_tasklist_scroll_end, NULL, MODE_TASKLIST); add_keybind('e', key_tasklist_edit, NULL, MODE_ANY); add_keybind('r', key_tasklist_reload, NULL, MODE_TASKLIST); add_keybind('u', key_tasklist_undo, NULL, MODE_TASKLIST); add_keybind('d', key_tasklist_delete, NULL, MODE_ANY); add_keybind('c', key_tasklist_complete, NULL, MODE_ANY); add_keybind('a', key_tasklist_add, NULL, MODE_TASKLIST); add_keybind('v', key_tasklist_view, NULL, MODE_TASKLIST); add_keybind(13, key_tasklist_view, NULL, MODE_TASKLIST); add_keybind(KEY_ENTER, key_tasklist_view, NULL, MODE_TASKLIST); add_keybind('s', key_tasklist_sort, NULL, MODE_TASKLIST); add_keybind('/', key_tasklist_search, NULL, MODE_TASKLIST); add_keybind('n', key_tasklist_search_next, NULL, MODE_TASKLIST); add_keybind('f', key_tasklist_filter, NULL, MODE_TASKLIST); add_keybind('y', key_tasklist_sync, NULL, MODE_TASKLIST); add_keybind('q', key_done, NULL, MODE_TASKLIST); add_keybind('q', key_pager_close, NULL, MODE_PAGER); add_keybind(';', key_command, NULL, MODE_TASKLIST); add_keybind(':', key_command, NULL, MODE_ANY); add_keybind('h', help_window, NULL, MODE_ANY); add_keybind(12, force_redraw, NULL, MODE_TASKLIST); add_keybind(12, force_redraw, NULL, MODE_PAGER); /* determine config path */ xdg_config_home = getenv("XDG_CONFIG_HOME"); if (xdg_config_home == NULL) { home = getenv("HOME"); filepath = malloc((strlen(home)+25)*sizeof(char)); sprintf(filepath, "%s/.config/tasknc/config", home); } else { filepath = malloc((strlen(xdg_config_home)+16)*sizeof(char)); sprintf(filepath, "%s/tasknc/config", xdg_config_home); } run_command_source(filepath); free(filepath); /* compile format strings */ compile_formats(); } /* }}} */
int main(int argc, char **argv) /* {{{ */ { /* declare variables */ int c; bool debug = false; char *debugopts = NULL; /* open log */ logfp = fopen(LOGFILE, "a"); tnc_fprintf(logfp, LOG_DEBUG, "%s started", PROGNAME); /* set defaults */ cfg.loglvl = LOGLVL_DEFAULT; setlocale(LC_ALL, ""); /* handle arguments */ static struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"debug", required_argument, 0, 'd'}, {"version", no_argument, 0, 'v'}, {"loglevel", required_argument, 0, 'l'}, {"filter", required_argument, 0, 'f'}, {0, 0, 0, 0} }; int option_index = 0; while ((c = getopt_long(argc, argv, "l:hvd:f:", long_options, &option_index)) != -1) { switch (c) { case 'l': cfg.loglvl = (char) atoi(optarg); printf("loglevel: %d\n", (int)cfg.loglvl); break; case 'v': print_version(); return 0; break; case 'd': debug = true; debugopts = strdup(optarg); break; case 'f': active_filter = strdup(optarg); break; case 'h': case '?': help(); return 0; break; default: return 1; } } /* run ncurses */ if (!debug) { tnc_fprintf(logfp, LOG_DEBUG, "running gui"); ncurses_init(); mvwprintw(stdscr, 0, 0, "%s %s", PROGNAME, PROGVERSION); mvwprintw(stdscr, 1, 0, "configuring..."); wrefresh(stdscr); tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "configuring..."); configure(); tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "configuration complete"); mvwprintw(stdscr, 1, 0, "loading tasks..."); wrefresh(stdscr); tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "loading tasks..."); head = get_tasks(NULL); tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "%d tasks loaded", taskcount); mvwhline(stdscr, 0, 0, ' ', COLS); mvwhline(stdscr, 1, 0, ' ', COLS); tasklist_window(); ncurses_end(0); } /* debug mode */ else { configure(); head = get_tasks(NULL); test(debugopts); free(debugopts); } /* done */ tnc_fprintf(logfp, LOG_DEBUG, "exiting"); return 0; } /* }}} */
void run_command_set(char *args) /* {{{ */ { /** * set a variable in the statusbar * syntax: variable value */ var *this_var; char *message = NULL, *varname = NULL, *value = NULL; int ret = 0; /* parse args */ if (args != NULL) ret = sscanf(args, "%ms %m[^\n]", &varname, &value); if (ret != 2) { statusbar_message(cfg.statusbar_timeout, "syntax: set <variable> <value>"); tnc_fprintf(logfp, LOG_ERROR, "syntax: set <variable> <value> [%d](%s)", ret, args); goto cleanup; } /* find the variable */ this_var = (var *)find_var(varname); if (this_var==NULL) { statusbar_message(cfg.statusbar_timeout, "variable not found: %s", varname); goto cleanup; } /* check for permission */ if (this_var->perms == VAR_RO) { statusbar_message(cfg.statusbar_timeout, "variable is read only: %s", varname); goto cleanup; } if (this_var->perms == VAR_RC && tasklist != NULL) { statusbar_message(cfg.statusbar_timeout, "variable must be set in config: %s", varname); goto cleanup; } /* set the value */ switch (this_var->type) { case VAR_INT: ret = sscanf(value, "%d", (int *)this_var->ptr); break; case VAR_CHAR: ret = sscanf(value, "%c", (char *)this_var->ptr); break; case VAR_STR: if (*(char **)(this_var->ptr)!=NULL) free(*(char **)(this_var->ptr)); *(char **)(this_var->ptr) = calloc(strlen(value)+1, sizeof(char)); ret = NULL!=strcpy(*(char **)(this_var->ptr), value); if (ret) strip_quotes((char **)this_var->ptr, 1); break; default: ret = 0; break; } if (ret<=0) tnc_fprintf(logfp, LOG_ERROR, "failed to parse value from command: set %s %s", varname, value); /* acquire the value string and print it */ message = var_value_message(this_var, 1); statusbar_message(cfg.statusbar_timeout, message); cleanup: free(message); free(varname); free(value); return; } /* }}} */
void handle_command(char *cmdstr) /* {{{ */ { /* accept a command string, determine what action to take, and execute */ char *command, *args, *modestr, *pos; funcmap *fmap; prog_mode mode; int ret = 0; /* parse args */ pos = strchr(cmdstr, '\n'); if (pos != NULL) *pos = 0; tnc_fprintf(logfp, LOG_DEBUG, "command received: %s", cmdstr); if (cmdstr != NULL) ret = sscanf(cmdstr, "%ms %m[^\n]", &command, &args); if (ret < 1) { statusbar_message(cfg.statusbar_timeout, "failed to parse command"); tnc_fprintf(logfp, LOG_ERROR, "failed to parse command: [%d] (%s)", ret, cmdstr); return; } /* determine mode */ if (pager != NULL) { modestr = "pager"; mode = MODE_PAGER; } else if (tasklist != NULL) { modestr = "tasklist"; mode = MODE_TASKLIST; } else { modestr = "none"; mode = MODE_ANY; } /* log command */ tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "command: %s - %s (%s)", modestr, command, args); /* handle command & arguments */ /* try for exposed command */ fmap = find_function(command, mode); if (fmap!=NULL) { (fmap->function)(str_trim(args)); goto cleanup; } /* version: print version string */ if (str_eq(command, "version")) statusbar_message(cfg.statusbar_timeout, "%s %s by %s\n", PROGNAME, PROGVERSION, PROGAUTHOR); /* quit/exit: exit tasknc */ else if (str_eq(command, "quit") || str_eq(command, "exit")) done = true; /* reload: force reload of task list */ else if (str_eq(command, "reload")) { reload = true; statusbar_message(cfg.statusbar_timeout, "task list reloaded"); } /* redraw: force redraw of screen */ else if (str_eq(command, "redraw")) redraw = true; /* dump: write all displayed tasks to log file */ else if (str_eq(command, "dump")) { task *this = head; int counter = 0; while (this!=NULL) { tnc_fprintf(logfp, 0, "uuid: %s", this->uuid); tnc_fprintf(logfp, 0, "description: %s", this->description); tnc_fprintf(logfp, 0, "project: %s", this->project); tnc_fprintf(logfp, 0, "tags: %s", this->tags); this = this->next; counter++; } tnc_fprintf(logfp, 0, "dumped %d tasks", counter); } /* scrdump: do an ncurses scr_dump */ else if (str_eq(command, "scrdump")) { const char *dumppath = "nc_dump"; scr_dump(dumppath); tnc_fprintf(logfp, LOG_DEBUG, "ncurses dumped to '%s'", dumppath); } else { statusbar_message(cfg.statusbar_timeout, "error: command %s not found", command); tnc_fprintf(logfp, LOG_ERROR, "error: command %s not found", command); } goto cleanup; cleanup: /* clean up */ free(command); free(args); } /* }}} */
bool eval_rules(char *rule, const task *tsk, const bool selected) /* {{{ */ { /* evaluate a rule set for a task */ char *regex = NULL, pattern, *tmp; int ret, move; bool go = false, invert = false; /* success if rules are done */ if (rule == NULL || *rule == 0) return true; /* skip non-patterns */ if (*rule != '~') return eval_rules(rule+1, tsk, selected); /* regex match */ ret = sscanf(rule, "~%c '%m[^\']'", &pattern, ®ex); if (ret > 0 && pattern >= 'A' && pattern <= 'Z') { pattern += 32; invert = true; } if (ret == 1) { switch (pattern) { case 's': if (!XOR(invert, selected)) return false; else return eval_rules(rule+2, tsk, selected); break; case 't': if (!XOR(invert, tsk->start>0)) return false; else return eval_rules(rule+2, tsk, selected); break; default: break; } } if (ret == 2) { move = strlen(regex)+3; go = true; switch (pattern) { case 'p': if (!XOR(invert, match_string(tsk->project, regex))) return false; else tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "eval_rules: project match - '%s' '%s'", tsk->project, regex); break; case 'd': if (!XOR(invert, match_string(tsk->description, regex))) return false; else tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "eval_rules: description match - '%s' '%s'", tsk->description, regex); break; case 't': if (!XOR(invert, match_string(tsk->tags, regex))) return false; else tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "eval_rules: tag match - '%s' '%s'", tsk->tags, regex); break; case 'r': tmp = calloc(2, sizeof(char)); *tmp = tsk->priority; if (!XOR(invert, match_string(tmp, regex))) return false; else tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "eval_rules: priority match - '%s' '%s'", tmp, regex); free(tmp); break; default: go = false; break; } free(regex); if (go) return eval_rules(rule+move, tsk, selected); } /* should never get here */ tnc_fprintf(logfp, LOG_ERROR, "malformed rules - \"%s\"", rule); return false; } /* }}} */
void tasklist_window() /* {{{ */ { /* ncurses main function */ int c; task *cur; char *uuid = NULL; /* get field lengths */ cfg.fieldlengths.project = max_project_length(); cfg.fieldlengths.date = DATELENGTH; /* create windows */ rows = LINES; cols = COLS; tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "rows: %d, columns: %d", rows, cols); header = newwin(1, cols, 0, 0); tasklist = newwin(rows-2, cols, 1, 0); statusbar = newwin(1, cols, rows-1, 0); tnc_fprintf(logfp, LOG_DEBUG_VERBOSE, "ncurses windows: h:%p, t:%p, s:%p (%d,%d)", header, tasklist, statusbar, rows, cols); if (statusbar==NULL || tasklist==NULL || header==NULL) { tnc_fprintf(logfp, LOG_ERROR, "window creation failed (rows:%d, cols:%d)", rows, cols); ncurses_end(-1); } /* set curses settings */ set_curses_mode(NCURSES_MODE_STD); /* print task list */ check_screen_size(); cfg.fieldlengths.description = COLS-cfg.fieldlengths.project-1-cfg.fieldlengths.date; task_count(); print_header(); tasklist_print_task_list(); /* main loop */ while (1) { /* set variables for determining actions */ done = false; redraw = false; reload = false; /* check for an empty task list */ if (head == NULL) { if (strcmp(active_filter,"") == 0){ tnc_fprintf(logfp, LOG_ERROR, "it appears that your task list is empty. %s does not yet support empty task lists.", PROGNAME); ncurses_end(-1); } active_filter = strdup(""); reload = true; } /* get the screen size */ rows = LINES; cols = COLS; /* check for a screen thats too small */ check_screen_size(); /* check for size changes */ check_resize(); /* apply staged window updates */ doupdate(); /* get a character */ c = wgetch(statusbar); /* handle the character */ handle_keypress(c, MODE_TASKLIST); /* exit */ if (done) break; /* reload task list */ if (reload) { cur = get_task_by_position(selline); if (cur != NULL) uuid = strdup(cur->uuid); wipe_tasklist(); reload_tasks(); task_count(); redraw = true; if (cfg.follow_task) set_position_by_uuid(uuid); check_free(uuid); uuid = NULL; tasklist_check_curs_pos(); } /* redraw all windows */ if (redraw) { cfg.fieldlengths.project = max_project_length(); cfg.fieldlengths.description = cols-cfg.fieldlengths.project-1-cfg.fieldlengths.date; print_header(); tasklist_print_task_list(); tasklist_check_curs_pos(); touchwin(tasklist); touchwin(header); touchwin(statusbar); wnoutrefresh(tasklist); wnoutrefresh(header); wnoutrefresh(statusbar); doupdate(); } statusbar_timeout(); } } /* }}} */
void run_command_bind(char *args) /* {{{ */ { /** * create a new keybind * syntax - mode key function [funcarg] */ int key, ret = 0; char *function = NULL, *arg = NULL, *keystr = NULL, *modestr = NULL, *keyname = NULL; void (*func)(); funcmap *fmap; prog_mode mode; /* parse command */ if (args != NULL) ret = sscanf(args, "%ms %ms %ms %m[^\n]", &modestr, &keystr, &function, &arg); if (ret < 3) { statusbar_message(cfg.statusbar_timeout, "syntax: bind <mode> <key> <function> <args>"); tnc_fprintf(logfp, LOG_ERROR, "syntax: bind <mode> <key> <function> <args> [%d](%s)", ret, args); goto cleanup; } /* parse mode string */ if (str_eq(modestr, "tasklist")) mode = MODE_TASKLIST; else if (str_eq(modestr, "pager")) mode = MODE_PAGER; else { tnc_fprintf(logfp, LOG_ERROR, "bind: invalid mode (%s)", modestr); goto cleanup; } /* parse key */ key = parse_key(keystr); /* map function to function call */ fmap = find_function(function, mode); if (fmap==NULL) { tnc_fprintf(logfp, LOG_ERROR, "bind: invalid function specified (%s)", args); goto cleanup; } func = fmap->function; /* error out if there is no argument specified when required */ if (fmap->argn>0 && arg==NULL) { statusbar_message(cfg.statusbar_timeout, "bind: argument required for function %s", function); goto cleanup; } /* add keybind */ add_keybind(key, func, arg, mode); keyname = name_key(key); statusbar_message(cfg.statusbar_timeout, "key %s (%d) bound to %s - %s", keyname, key, modestr, name_function(func)); goto cleanup; cleanup: free(function); free(arg); free(keystr); free(modestr); free(keyname); return; } /* }}} */