From 332fdba0bddd7ec7e89f8dae867e26c054e344fe Mon Sep 17 00:00:00 2001 From: Felicity Tarnell Date: Fri, 7 Mar 2014 17:43:41 +0000 Subject: [PATCH] Source refactoring; no functional changes. --- Makefile.in | 6 +- bindings.c | 13 + bindings.h | 36 ++ commands.c | 133 +++++ commands.h | 32 ++ entry.c | 124 +++++ entry.h | 59 ++ functions.c | 800 +++++++++++++++++++++++++++ functions.h | 51 ++ str.c | 107 ++++ str.h | 21 + style.h | 50 ++ tts.c | 1482 ++------------------------------------------------ tts.h | 65 +++ tts_curses.h | 30 + ui.c | 65 +++ ui.h | 37 ++ variable.h | 28 + wide.c | 26 + wide.h | 81 +++ 20 files changed, 1806 insertions(+), 1440 deletions(-) create mode 100644 bindings.c create mode 100644 bindings.h create mode 100644 commands.c create mode 100644 commands.h create mode 100644 entry.c create mode 100644 entry.h create mode 100644 functions.c create mode 100644 functions.h create mode 100644 str.c create mode 100644 str.h create mode 100644 style.h create mode 100644 tts.h create mode 100644 tts_curses.h create mode 100644 ui.c create mode 100644 ui.h create mode 100644 variable.h create mode 100644 wide.c create mode 100644 wide.h diff --git a/Makefile.in b/Makefile.in index 8ee1584..dd1b3a1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,13 +1,15 @@ VPATH = @top_srcdir@ CC = @CC@ -CPPFLAGS = @CPPFLAGS@ +# _GNU_SOURCE is required for wcsdup() on older version of glibc that don't +# implement XPG7. +CPPFLAGS = @CPPFLAGS@ -D_XOPEN_SOURCE=700 -D_XOPEN_SOURCE_EXTENDED -D__EXTENSIONS__ CFLAGS = @CFLAGS@ -I@top_srcdir@ -I@top_builddir@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ INSTALL = @INSTALL@ -OBJS = tts.o +OBJS = tts.o wide.o entry.o ui.o functions.o commands.o bindings.o str.o prefix = @prefix@ exec_prefix = @exec_prefix@ diff --git a/bindings.c b/bindings.c new file mode 100644 index 0000000..0132f65 --- /dev/null +++ b/bindings.c @@ -0,0 +1,13 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#include "bindings.h" + +binding_list_t bindings = TAILQ_HEAD_INITIALIZER(bindings); diff --git a/bindings.h b/bindings.h new file mode 100644 index 0000000..8b7df24 --- /dev/null +++ b/bindings.h @@ -0,0 +1,36 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_BINDINGS_H +#define TTS_BINDINGS_H + +#include "wide.h" +#include "functions.h" +#include "queue.h" + +typedef struct tkey { + INT ky_code; + const WCHAR *ky_name; +} tkey_t; + +typedef struct binding { + INT bi_code; + tkey_t *bi_key; + function_t *bi_func; + TAILQ_ENTRY(binding) bi_entries; +} binding_t; + +typedef TAILQ_HEAD(bindlist, binding) binding_list_t; +extern binding_list_t bindings; + +tkey_t *find_key(const WCHAR *name); +void bind_key(const WCHAR *key, const WCHAR *func); + +#endif /* !TTS_BINDINGS_H */ diff --git a/commands.c b/commands.c new file mode 100644 index 0000000..906cabc --- /dev/null +++ b/commands.c @@ -0,0 +1,133 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#include "commands.h" +#include "style.h" +#include "bindings.h" +#include "variable.h" + +static command_t commands[] = { + { WIDE("bind"), c_bind }, + { WIDE("style"), c_style }, + { WIDE("set"), c_set }, + { } +}; + +command_t * +find_command(name) + const WCHAR *name; +{ +size_t i; + for (i = 0; i < sizeof(commands) / sizeof(*commands); i++) + if (STRCMP(name, commands[i].cm_name) == 0) + return &commands[i]; + return NULL; +} + +void +c_style(argc, argv) + size_t argc; + WCHAR **argv; +{ +style_t *sy; +WCHAR *last, *tok; + + if (argc < 3 || argc > 4) { + cmderr(WIDE("Usage: style [background]")); + return; + } + + if (STRCMP(argv[1], WIDE("header")) == 0) + sy = &sy_header; + else if (STRCMP(argv[1], WIDE("status")) == 0) + sy = &sy_status; + else if (STRCMP(argv[1], WIDE("entry")) == 0) + sy = &sy_entry; + else if (STRCMP(argv[1], WIDE("selected")) == 0) + sy = &sy_selected; + else if (STRCMP(argv[1], WIDE("running")) == 0) + sy = &sy_running; + else if (STRCMP(argv[1], WIDE("date")) == 0) + sy = &sy_date; + else { + cmderr(WIDE("Unknown style item.")); + return; + } + + style_clear(sy); + for (tok = STRTOK(argv[2], WIDE(","), &last); tok != NULL; + tok = STRTOK(NULL, WIDE(","), &last)) { + style_add(sy, tok, argv[3]); + } + + apply_styles(); +} + +void +c_bind(argc, argv) + size_t argc; + WCHAR **argv; +{ + if (argc != 3) { + cmderr(WIDE("Usage: bind ")); + return; + } + + bind_key(argv[1], argv[2]); +} + +void +c_set(argc, argv) + size_t argc; + WCHAR **argv; +{ +variable_t *var; +int val; + + if (argc != 3) { + cmderr(WIDE("Usage: set ")); + return; + } + + if ((var = find_variable(argv[1])) == NULL) { + cmderr(WIDE("Unknown variable \"%"FMT_L"s\"."), argv[1]); + return; + } + + switch (var->va_type) { + case VTYPE_BOOL: + if (STRCMP(argv[2], WIDE("true")) == 0 || + STRCMP(argv[2], WIDE("yes")) == 0 || + STRCMP(argv[2], WIDE("on")) == 0 || + STRCMP(argv[2], WIDE("1")) == 0) { + val = 1; + } else if (STRCMP(argv[2], WIDE("false")) == 0 || + STRCMP(argv[2], WIDE("no")) == 0 || + STRCMP(argv[2], WIDE("off")) == 0 || + STRCMP(argv[2], WIDE("0")) == 0) { + val = 0; + } else { + cmderr(WIDE("Invalid value for boolean: \"%"FMT_L"s\"."), argv[2]); + return; + } + + *(int *)var->va_addr = val; + break; + + case VTYPE_STRING: + *(WCHAR **)var->va_addr = STRDUP(argv[2]); + break; + + case VTYPE_INT: + *(int *)var->va_addr = STRTOL(argv[2], NULL, 0); + break; + } +} + diff --git a/commands.h b/commands.h new file mode 100644 index 0000000..cad450b --- /dev/null +++ b/commands.h @@ -0,0 +1,32 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_COMMANDS_H +#define TTS_COMMANDS_H + +#include + +#include "wide.h" + +typedef struct command { + const WCHAR *cm_name; + void (*cm_hdl) (size_t, WCHAR **); +} command_t; + +command_t *find_command(const WCHAR *); + +void c_bind (size_t, WCHAR **); +void c_style (size_t, WCHAR **); +void c_set (size_t, WCHAR **); + +void cmderr (const WCHAR *, ...); +void vcmderr (const WCHAR *, va_list); + +#endif /* !TTS_COMMANDS_H */ diff --git a/entry.c b/entry.c new file mode 100644 index 0000000..b6e4ce6 --- /dev/null +++ b/entry.c @@ -0,0 +1,124 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#include +#include +#include +#include + +#include "entry.h" +#include "wide.h" +#include "tts.h" + +entry_list entries = TAILQ_HEAD_INITIALIZER(entries); + +entry_t *running; + +entry_t * +entry_new(desc) + const WCHAR *desc; +{ +entry_t *en; + if ((en = calloc(1, sizeof(*en))) == NULL) + return NULL; + + if (auto_nonbillable && STRSTR(desc, auto_nonbillable)) + en->en_flags.efl_nonbillable = 1; + + TAILQ_INSERT_HEAD(&entries, en, en_entries); + + en->en_desc = STRDUP(desc); + time(&en->en_created); + + return en; +} + +void +entry_start(en) + entry_t *en; +{ + if (running) + entry_stop(running); + time(&en->en_started); + running = en; +} + +void +entry_stop(en) + entry_t *en; +{ + if (running == en) + running = NULL; + en->en_secs += time(NULL) - en->en_started; + en->en_started = 0; +} + +void +entry_free(en) + entry_t *en; +{ + if (en == running) + entry_stop(en); + free(en->en_desc); +} + +void +entry_account(en) + entry_t *en; +{ + if (!en->en_started) + return; + en->en_secs += time(NULL) - en->en_started; + time(&en->en_started); +} + +/* + * Return the amount of time for the day on which the timestamp .when falls. + * If .inv is 0, sum non-invoiced entries; if 1, sum invoiced entries; if + * 2, sum billable entries; if -1, sum all entries. If .incr is non-zero, + * individual entry time will be rounded up to intervals of that many minutes. + */ +time_t +entry_time_for_day(when, inv, incr) + time_t when; +{ +time_t day = time_day(when); +time_t sum = 0; +entry_t *en; +int rnd = incr * 60; + TAILQ_FOREACH(en, &entries, en_entries) { + time_t n; + + if (entry_day(en) > day) + continue; + if (entry_day(en) < day) + break; + + if (inv == 0 && en->en_flags.efl_invoiced) + continue; + if (inv == 1 && !en->en_flags.efl_invoiced) + continue; + if (inv == 2 && en->en_flags.efl_nonbillable) + continue; + + n = en->en_secs; + if (en->en_started) + n += time(NULL) - en->en_started; + + if (!n) + continue; + + if (rnd) + n = (1 + round((n - 1) / rnd)) * rnd; + sum += n; + } + return sum; +} + diff --git a/entry.h b/entry.h new file mode 100644 index 0000000..68b45a1 --- /dev/null +++ b/entry.h @@ -0,0 +1,59 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_ENTRY_H +#define TTS_ENTRY_H + +#include + +#include "queue.h" +#include "wide.h" + +typedef struct entry { + WCHAR *en_desc; + int en_secs; + time_t en_started; + time_t en_created; + + struct { + int efl_visible:1; + int efl_invoiced:1; + int efl_marked:1; + int efl_deleted:1; + int efl_nonbillable:1; + } en_flags; + TAILQ_ENTRY(entry) en_entries; +} entry_t; + +typedef TAILQ_HEAD(entrylist, entry) entry_list; +extern entry_list entries; + +extern entry_t *running; + +entry_t *entry_new (const WCHAR *); +void entry_start (entry_t *); +void entry_stop (entry_t *); +void entry_free (entry_t *); +void entry_account (entry_t *); +time_t entry_time_for_day (time_t, int, int); + +#define time_day(t) (((t) / (60 * 60 * 24)) * (60 * 60 * 24)) +#define entry_day(e) (time_day((e)->en_created)) + +#define time_to_hms(t, h, m, s) do { \ + time_t n = t; \ + h = n / (60 * 60); \ + n %= (60 * 60); \ + m = n / 60; \ + n %= 60; \ + s = n; \ + } while (0) + +#endif /* !TTS_ENTRY_H */ diff --git a/functions.c b/functions.c new file mode 100644 index 0000000..3b8631a --- /dev/null +++ b/functions.c @@ -0,0 +1,800 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#include + +#include "functions.h" +#include "tts.h" +#include "entry.h" +#include "ui.h" +#include "commands.h" +#include "tts_curses.h" +#include "bindings.h" +#include "str.h" + +function_t funcs[] = { + { WIDE("help"), khelp, WIDE("display help screen") }, + { WIDE("add"), kadd, WIDE("add a new entry and start the timer") }, + { WIDE("add-old"), kaddold, WIDE("add a new entry and specify its duration") }, + { WIDE("delete"), kmarkdel, WIDE("delete the current entry") }, + { WIDE("undelete"), kundel, WIDE("undelete the current entry") }, + { WIDE("quit"), kquit, WIDE("exit TTS") }, + { WIDE("invoice"), kinvoiced, WIDE("toggle current entry as invoiced") }, + { WIDE("billable"), kbillable, WIDE("toggle current entry as billable") }, + { WIDE("mark"), kmark, WIDE("mark the current entry") }, + { WIDE("unmarkall"), kunmarkall, WIDE("unmark all entries") }, + { WIDE("startstop"), ktoggle, WIDE("start or stop the timer") }, + { WIDE("edit-desc"), keddesc, WIDE("edit the current entry's description") }, + { WIDE("edit-time"), kedtime, WIDE("edit the current entry's duration") }, + { WIDE("showhide-inv"), ktoggleinv, WIDE("show or hide invoiced entries") }, + { WIDE("copy"), kcopy, WIDE("copy the current entry's description to a new entry") }, + { WIDE("add-time"), kaddtime, WIDE("add time to the current entry") }, + { WIDE("sub-time"), kdeltime, WIDE("subtract time from the current entry") }, + { WIDE("search"), ksearch, WIDE("search for an entry by name") }, + { WIDE("sync"), ksync, WIDE("purge all deleted entries") }, + { WIDE("prev"), kup, WIDE("move to the previous entry") }, + { WIDE("next"), kdown, WIDE("move to the next entry") }, + { WIDE("execute"), kexec, WIDE("execute a configuration command") }, + { WIDE("merge"), kmerge, WIDE("merge marked entries into current entry") }, + { WIDE("interrupt"), kint, WIDE("split current entry into new entry")}, + { } +}; + +void +kquit() +{ +entry_t *en; +int ndel = 0; + + TAILQ_FOREACH(en, &entries, en_entries) { + if (en->en_flags.efl_deleted) + ndel++; + } + + if (ndel) { + WCHAR s[128]; + SNPRINTF(s, WSIZEOF(s), WIDE("Purge %d deleted entries?"), ndel); + if (yesno(s)) { + ksync(); + } + } + + doexit = 1; +} + +void +kadd() +{ +WCHAR *name; +entry_t *en; + name = prompt(WIDE("Description:"), NULL, NULL); + if (!name || !*name) { + free(name); + return; + } + en = entry_new(name); + entry_start(en); + curent = en; + save(); +} + +void +kaddold() +{ +WCHAR *name; +entry_t *en; + name = prompt(WIDE("Description:"), NULL, NULL); + + if (!name || !*name) { + free(name); + return; + } + + en = entry_new(name); + curent = en; + kedtime(); + save(); +} + +void +ktoggle() +{ + itime = 0; + + if (!curent) + return; + + if (curent == running) { + entry_stop(curent); + save(); + return; + } + + if (running) + entry_stop(running); + entry_start(curent); + save(); +} + +void +kundel() +{ + if (!curent) + return; + + curent->en_flags.efl_deleted = 0; + if (delete_advance) + cursadvance(); +} + +void +kmarkdel() +{ +entry_t *en; +int nmarked = 0; + + TAILQ_FOREACH(en, &entries, en_entries) { + if (en->en_flags.efl_marked) { + nmarked++; + en->en_flags.efl_deleted = 1; + } + } + + if (nmarked) + return; + + if (!curent) { + drawstatus(WIDE("No entries to delete.")); + return; + } + + curent->en_flags.efl_deleted = 1; + + if (delete_advance) + cursadvance(); +} + +void +ksync() +{ +entry_t *en, *ten; + + TAILQ_FOREACH_SAFE(en, &entries, en_entries, ten) { + if (!en->en_flags.efl_deleted) + continue; + if (en == curent) + curent = NULL; + TAILQ_REMOVE(&entries, en, en_entries); + entry_free(en); + } + + if (curent == NULL) + curent = TAILQ_FIRST(&entries); + save(); +} + +void +kup() +{ +entry_t *prev = curent; + if (!curent) + return; + + do { + if ((prev = TAILQ_PREV(prev, entrylist, en_entries)) == NULL) + break; + } while (!showinv && prev->en_flags.efl_invoiced); + + if (prev == NULL) { + drawstatus(WIDE("Already at first entry.")); + return; + } + + curent = prev; + if (!curent->en_flags.efl_visible) + pagestart--; +} + +void +kdown() +{ +entry_t *next = curent; + if (!curent) + return; + + do { + if ((next = TAILQ_NEXT(next, en_entries)) == NULL) + break; + } while (!showinv && next->en_flags.efl_invoiced); + + if (next == NULL) { + drawstatus(WIDE("Already at last entry.")); + return; + } + + curent = next; + if (!curent->en_flags.efl_visible) + pagestart++; +} + +void +kinvoiced() +{ +entry_t *en; +int anymarked = 0; + + TAILQ_FOREACH(en, &entries, en_entries) { + if (!en->en_flags.efl_marked) + continue; + anymarked = 1; + en->en_flags.efl_invoiced = !en->en_flags.efl_invoiced; + en->en_flags.efl_marked = 0; + } + + if (anymarked) { + save(); + return; + } + + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + curent->en_flags.efl_invoiced = !curent->en_flags.efl_invoiced; + save(); + + en = curent; + + if (showinv) { + if (TAILQ_NEXT(curent, en_entries) != NULL) + curent = TAILQ_NEXT(curent, en_entries); + return; + } + + /* + * Try to find the next uninvoiced request to move the cursor to. + */ + for (;;) { + if ((curent = TAILQ_NEXT(curent, en_entries)) == NULL) + break; /* end of list */ + if (!curent->en_flags.efl_invoiced) + return; + } + + /* + * We didn't find any, so try searching backwards instead. + */ + for (curent = en;;) { + if ((curent = TAILQ_PREV(curent, entrylist, en_entries)) == NULL) + break; /* end of list */ + if (!curent->en_flags.efl_invoiced) + return; + } +} + +void +kbillable() +{ +entry_t *en; +int anymarked = 0; + + TAILQ_FOREACH(en, &entries, en_entries) { + if (!en->en_flags.efl_marked) + continue; + anymarked = 1; + en->en_flags.efl_nonbillable = !en->en_flags.efl_nonbillable; + en->en_flags.efl_marked = 0; + } + + if (anymarked) { + save(); + return; + } + + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + curent->en_flags.efl_nonbillable = !curent->en_flags.efl_nonbillable; + save(); + + if (bill_advance) + cursadvance(); +} + +void +keddesc() +{ +WCHAR *new; + + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + if ((new = prompt(WIDE("Description:"), curent->en_desc, NULL)) == NULL) + return; + + free(curent->en_desc); + curent->en_desc = new; + save(); +} + +void +kedtime() +{ +WCHAR *new, old[64]; +time_t n; +int h, m, s; + + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + n = curent->en_secs; + if (curent->en_started) + n += time(NULL) - curent->en_started; + h = n / (60 * 60); + n %= (60 * 60); + m = n / 60; + n %= 60; + s = n; + + SNPRINTF(old, WSIZEOF(old), WIDE("%02d:%02d:%02d"), h, m, s); + if ((new = prompt(WIDE("Duration [HH:MM:SS]:"), old, NULL)) == NULL) + return; + + if (!SSCANF(new, WIDE("%d:%d:%d"), &h, &m, &s)) { + free(new); + drawstatus(WIDE("Invalid duration.")); + } + + curent->en_secs = (h * 60 * 60) + (m * 60) + s; + if (curent->en_started) + time(&curent->en_started); + + save(); +} + +void +ktoggleinv() +{ +entry_t *en = curent; + showinv = !showinv; + drawstatus(WIDE("%"FMT_L"s invoiced entries."), + showinv ? L"Showing" : L"Hiding"); + + if (curent && !curent->en_flags.efl_invoiced) + return; + + if (!curent) { + curent = TAILQ_FIRST(&entries); + return; + } + + /* + * Try to find the next uninvoiced request to move the cursor to. + */ + for (;;) { + if ((curent = TAILQ_NEXT(curent, en_entries)) == NULL) + break; /* end of list */ + if (!curent->en_flags.efl_invoiced) + return; + } + + /* + * We didn't find any, so try searching backwards instead. + */ + for (curent = en;;) { + if ((curent = TAILQ_PREV(curent, entrylist, en_entries)) == NULL) + break; /* end of list */ + if (!curent->en_flags.efl_invoiced) + return; + } +} + +void +kcopy() +{ +entry_t *en; + + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + en = entry_new(curent->en_desc); + curent = en; + entry_start(en); + save(); +} + +void +kaddtime() +{ +WCHAR *tstr; +int h = 0, m = 0, s = 0, secs; + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + if ((tstr = prompt(WIDE("Time to add ([[HH:]MM:]SS):"), NULL, NULL)) == NULL) + return; + + if (!*tstr) { + drawstatus(WIDE("")); + free(tstr); + return; + } + + if (SSCANF(tstr, WIDE("%d:%d:%d"), &h, &m, &s) != 3) { + h = 0; + if (SSCANF(tstr, WIDE("%d:%d"), &m, &s) != 2) { + m = 0; + if (SSCANF(tstr, WIDE("%d"), &s) != 1) { + free(tstr); + drawstatus(WIDE("Invalid time format.")); + return; + } + } + } + + free(tstr); + + if (m >= 60) { + drawstatus(WIDE("Minutes cannot be more than 59.")); + return; + } + + if (s >= 60) { + drawstatus(WIDE("Seconds cannot be more than 59.")); + return; + } + + secs = s + m*60 + h*60*60; + curent->en_secs += secs; + save(); +} + +void +kdeltime() +{ +WCHAR *tstr; +int h = 0, m = 0, s = 0, secs; + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + if ((tstr = prompt(WIDE("Time to subtract, ([[HH:]MM:]SS):"), NULL, NULL)) == NULL) + return; + + if (!*tstr) { + drawstatus(WIDE("")); + free(tstr); + return; + } + + if (SSCANF(tstr, WIDE("%d:%d:%d"), &h, &m, &s) != 3) { + h = 0; + if (SSCANF(tstr, WIDE("%d:%d"), &m, &s) != 2) { + m = 0; + if (SSCANF(tstr, WIDE("%d"), &s) != 1) { + free(tstr); + drawstatus(WIDE("Invalid time format.")); + return; + } + } + } + + free(tstr); + if (m >= 60) { + drawstatus(WIDE("Minutes cannot be more than 59.")); + return; + } + + if (s >= 60) { + drawstatus(WIDE("Seconds cannot be more than 59.")); + return; + } + + entry_account(curent); + + secs = s + m*60 + h*60*60; + if (curent->en_secs - secs < 0) { + drawstatus(WIDE("Remaining time cannot be less than zero.")); + return; + } + + curent->en_secs -= secs; + save(); +} + +void +kmerge() +{ +entry_t *en, *ten; +int nmarked = 0; +WCHAR pr[128]; +int h, m, s = 0; + + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + /* + * Count number of marked entries and the summed time. + */ + TAILQ_FOREACH(en, &entries, en_entries) { + if (!en->en_flags.efl_marked || en == curent) + continue; + nmarked++; + s += en->en_secs; + if (en->en_started) + s += time(NULL) - en->en_started; + } + + if (nmarked == 0) { + drawstatus(WIDE("No marked entries.")); + return; + } + + h = s / (60 * 60); + s %= (60 * 60); + m = s / 60; + s %= 60; + + SNPRINTF(pr, WSIZEOF(pr), WIDE("Merge %d marked entries [%02d:%02d:%02d] into current entry?"), + nmarked, h, m, s); + if (!yesno(pr)) + return; + + TAILQ_FOREACH_SAFE(en, &entries, en_entries, ten) { + if (!en->en_flags.efl_marked || en == curent) + continue; + if (en->en_started) + entry_stop(en); + curent->en_secs += en->en_secs; + TAILQ_REMOVE(&entries, en, en_entries); + entry_free(en); + } + save(); +} + +void +khelp() +{ +WINDOW *hwin; +size_t nhelp = 0; +WCHAR **help; +#define HTITLE WIDE(" TTS keys ") +size_t width = 0; +size_t i; +INT c; +binding_t *bi; + + /* Count the number of bindings */ + TAILQ_FOREACH(bi, &bindings, bi_entries) + nhelp++; + help = calloc(nhelp, sizeof(const WCHAR *)); + + i = 0; + TAILQ_FOREACH(bi, &bindings, bi_entries) { + WCHAR s[128]; + if (bi->bi_key) + SNPRINTF(s, WSIZEOF(s), WIDE("%-10"FMT_L"s %"FMT_L"s (%"FMT_L"s)"), + bi->bi_key->ky_name, bi->bi_func->fn_desc, + bi->bi_func->fn_name); + else + SNPRINTF(s, WSIZEOF(s), WIDE("%"FMT_L"c %"FMT_L"s (%"FMT_L"s)"), + bi->bi_code, bi->bi_func->fn_desc, bi->bi_func->fn_name); + help[i] = STRDUP(s); + i++; + } + + if (nhelp > (LINES - 6)) + nhelp = LINES - 6; + + for (i = 0; i < nhelp; i++) + if (STRLEN(help[i]) > width) + width = STRLEN(help[i]); + + hwin = newwin(nhelp + 4, width + 4, + (LINES / 2) - ((nhelp + 2) / 2), + (COLS / 2) - ((width + 2) / 2)); + wborder(hwin, 0, 0, 0, 0, 0, 0, 0, 0); + + wattron(hwin, A_REVERSE | A_BOLD); + wmove(hwin, 0, (width / 2) - (WSIZEOF(HTITLE) - 1)/2); + WADDSTR(hwin, HTITLE); + wattroff(hwin, A_REVERSE | A_BOLD); + + for (i = 0; i < nhelp; i++) { + wmove(hwin, i + 2, 2); + WADDSTR(hwin, help[i]); + } + + wrefresh(hwin); + + while (WGETCH(hwin, &c) == ERR +#ifdef KEY_RESIZE + || (c == KEY_RESIZE) +#endif + ) + ; + + delwin(hwin); + + for (i = 0; i < nhelp; i++) + free(help[i]); + free(help); +} + +void +kmark() +{ + if (!curent) { + drawstatus(WIDE("No entry selected.")); + return; + } + + curent->en_flags.efl_marked = !curent->en_flags.efl_marked; + + if (mark_advance) + cursadvance(); +} + +void +kunmarkall() +{ +entry_t *en; + TAILQ_FOREACH(en, &entries, en_entries) + en->en_flags.efl_marked = 0; +} + +void +kint() +{ +time_t duration; +entry_t *en; +WCHAR *name; + + if (!itime) { + if (!running) { + drawstatus(WIDE("No running entry.")); + return; + } + + itime = time(NULL); + return; + } + + if (!running) { + drawstatus(WIDE("No running entry.")); + return; + } + + name = prompt(WIDE("Description:"), NULL, NULL); + + if (!name || !*name) { + itime = 0; + free(name); + return; + } + + if (itime) { + duration = time(NULL) - itime; + } else { + int h, m, s; + if (prduration(WIDE("Duration [HH:MM:SS]:"), &h, &m, &s) == -1) + return; + + duration = (h * 60 * 60) + (m * 60) + s; + } + + itime = 0; + running->en_secs += (time(NULL) - running->en_started); + running->en_started = time(NULL); + running->en_secs -= duration; + + en = entry_new(name); + en->en_created = time(NULL) - duration; + en->en_secs = duration; + save(); + + free(name); +} + +void +ksearch() +{ +static WCHAR *lastsearch; +WCHAR *term; +entry_t *start, *cur; + + if (!curent) { + drawstatus(WIDE("No entries.")); + return; + } + + if ((term = prompt(WIDE("Search:"), NULL, NULL)) == NULL) + return; + + if (!*term) { + free(term); + if (!lastsearch) + return; + + term = lastsearch; + } else { + free(lastsearch); + lastsearch = term; + } + + cur = start = curent; + + for (;;) { + cur = TAILQ_NEXT(cur, en_entries); + if (cur == NULL) { + drawstatus(WIDE("Search reached last entry, continuing from top.")); + cur = TAILQ_FIRST(&entries); + } + + if (cur == start) { + drawstatus(WIDE("No matches.")); + break; + } + + if (STRSTR(cur->en_desc, term)) { + curent = cur; + if (!showinv && cur->en_flags.efl_invoiced) + showinv = 1; + return; + } + + } +} + +void +kexec() +{ +WCHAR *cmd; +WCHAR **args; +command_t *cmds; +size_t nargs; + + if ((cmd = prompt(WIDE(":"), NULL, NULL)) == NULL || !*cmd) { + free(cmd); + return; + } + + nargs = tokenise(cmd, &args); + free(cmd); + + if (nargs == 0) { + tokfree(&args); + return; + } + + if ((cmds = find_command(args[0])) == NULL) { + drawstatus(WIDE("Unknown command.")); + tokfree(&args); + return; + } + + cmds->cm_hdl(nargs, args); + tokfree(&args); +} + diff --git a/functions.h b/functions.h new file mode 100644 index 0000000..1ef87b2 --- /dev/null +++ b/functions.h @@ -0,0 +1,51 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_FUNCTIONS_H +#define TTS_FUNCTIONS_H + +#include "wide.h" + +void kadd(void); +void kaddold(void); +void kquit(void); +void kup(void); +void kdown(void); +void ktoggle(void); +void kinvoiced(void); +void kbillable(void); +void keddesc(void); +void kedtime(void); +void ktoggleinv(void); +void kcopy(void); +void kaddtime(void); +void kdeltime(void); +void khelp(void); +void kmark(void); +void kunmarkall(void); +void ksearch(void); +void kmarkdel(void); +void kundel(void); +void ksync(void); +void kexec(void); +void kmerge(void); +void kint(void); + +typedef struct function { + const WCHAR *fn_name; + void (*fn_hdl) (void); + const WCHAR *fn_desc; +} function_t; + +extern function_t funcs[]; + +function_t *find_func(const WCHAR *name); + +#endif /* !TTS_FUNCTIONS_H */ diff --git a/str.c b/str.c new file mode 100644 index 0000000..4b89814 --- /dev/null +++ b/str.c @@ -0,0 +1,107 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#include "str.h" + +size_t +tokenise(str, res) + const WCHAR *str; + WCHAR ***res; +{ +int ntoks = 0; +const WCHAR *p, *q; + + *res = NULL; + p = str; + + for (;;) { + ptrdiff_t sz; + int qskip = 0; + + /* Skip leading whitespace */ + while (ISSPACE(*p)) + p++; + + /* End of string - no more arguments */ + if (!*p) + break; + + q = p; + + if (*q == '"') { + /* Quoted string - scan for end of string */ + int isbsl = 0; + p++; + + while (*++q) { + /* Handle escaping with backslash; currently works but the \ isn't + * removed from the string. + */ + if (*q == '\\') { + isbsl = 1; + continue; + } + + if (!isbsl && (*q == '"')) + break; + + isbsl = 0; + } + /* At this point, *q == '"'. If it's NUL instead, then the + * string was not terminated with a closing '"' before the end + * of the line. We could give an error here, but it seems + * more useful to just accept it. + */ + if (*q == '"') + qskip = 1; + } else { + /* Not quoted - just find the next whitespace */ + while (!ISSPACE(*q) && *q) + q++; + } + + /* Copy the argument (which is sz bytes long) into the result array */ + sz = (q - p); + *res = realloc(*res, sizeof(WCHAR *) * (ntoks + 1)); + (*res)[ntoks] = malloc(sizeof(WCHAR) * (sz + 1)); + MEMCPY((*res)[ntoks], p, sz); + (*res)[ntoks][sz] = 0; + ntoks++; + + if (qskip) + q += qskip; + + while (ISSPACE(*q)) + q++; + + /* + * q is the start of the next token (with leading whitespace); reset + * p to process the next argument. + */ + if (!*q) + break; + p = q; + } + + *res = realloc(*res, sizeof(WCHAR *) * (ntoks + 1)); + (*res)[ntoks] = NULL; + return ntoks; +} + +void +tokfree(vec) + WCHAR ***vec; +{ +WCHAR **p; + for (p = (*vec); *p; p++) + free(*p); + free(*vec); +} + diff --git a/str.h b/str.h new file mode 100644 index 0000000..c7b88e6 --- /dev/null +++ b/str.h @@ -0,0 +1,21 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_STR_H +#define TTS_STR_H + +#include + +#include "wide.h" + +size_t tokenise(const WCHAR *, WCHAR ***result); +void tokfree(WCHAR ***); + +#endif /* !TTS_STR_H */ diff --git a/style.h b/style.h new file mode 100644 index 0000000..9afac1b --- /dev/null +++ b/style.h @@ -0,0 +1,50 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_STYLE_H +#define TTS_STYLE_H + +#include "tts_curses.h" + +typedef struct style { + short sy_pair; + attr_t sy_attrs; +} style_t; + +#define style_fg(s) (COLOR_PAIR((s).sy_pair) | (s).sy_attrs) +#define style_bg(s) ((INT) ' ' | COLOR_PAIR((s).sy_pair) | ((s).sy_attrs & ~WA_UNDERLINE)) + +typedef struct attrname { + const WCHAR *an_name; + attr_t an_value; +} attrname_t; + +typedef struct colour { + const WCHAR *co_name; + short co_value; +} colour_t; + +extern style_t sy_header, + sy_status, + sy_entry, + sy_running, + sy_selected, + sy_date; + +int attr_find(const WCHAR *name, attr_t *result); +int colour_find(const WCHAR *name, short *result); + +void style_clear(style_t *); +int style_set(style_t *, const WCHAR *fg, const WCHAR *bg); +int style_add(style_t *, const WCHAR *fg, const WCHAR *bg); + +void apply_styles(void); + +#endif /* !TTS_STYLE_H */ diff --git a/tts.c b/tts.c index 495e385..b6da855 100644 --- a/tts.c +++ b/tts.c @@ -8,15 +8,6 @@ * warranty. */ -#define __EXTENSIONS__ -/* - * Older versions of glibc don't supporte _XOPEN_SOURCE==700 and require - * this for wcsdup() prototype. - */ -#define _GNU_SOURCE -#define _XOPEN_SOURCE 700 -#define _XOPEN_SOURCE_EXTENDED - #include #include @@ -52,244 +43,38 @@ # include #endif -#if defined HAVE_NCURSESW_CURSES_H -# include -#elif defined HAVE_NCURSESW_H -# include -#elif defined HAVE_NCURSES_CURSES_H -# include -#elif defined HAVE_NCURSES_H -# include -#elif defined HAVE_CURSES_H -# include -#else -# error "SVR4 or XSI compatible curses header file required" -#endif - #include "queue.h" - -#ifdef HAVE_CURSES_ENHANCED -# define WPFX(x) wcs##x -# define WIDE(x) L##x -# define ISX(x) isw##x -# define WCHAR wchar_t -# define FMT_L "l" -# define SNPRINTF swprintf -# define VSNPRINTF vswprintf -# define SSCANF swscanf -# define MEMCPY wmemcpy -# define MEMMOVE wmemmove -# define MBSTOWCS mbstowcs -# define WCSTOMBS wcstombs -# define FPRINTF fwprintf -# define STRTOK wcstok - -# define GETCH get_wch -# define WGETCH wget_wch -# define ADDSTR addwstr -# define WADDSTR waddwstr -# define INT wint_t -#else -# define WPFX(x) str##x -# define WIDE(x) x -# define ISX(x) is##x -# define WCHAR char -# define FMT_L -# define SNPRINTF snprintf -# define VSNPRINTF vsnprintf -# define SSCANF sscanf -# define MEMCPY memcpy -# define MEMMOVE memmove -# define MBSTOWCS strncpy -# define WCSTOMBS strncpy -# define FPRINTF fprintf -# define STRTOK strtok_r - -# define ADDSTR addstr -# define WADDSTR waddstr -# define INT int - -static int -tss_wgetch(win, d) - WINDOW *win; - int *d; -{ -int c; - if ((c = wgetch(win)) == ERR) - return ERR; - *d = c; - return OK; -} -# define WGETCH tss_wgetch -# define GETCH(c) tss_wgetch(stdscr,c) -#endif - -#define STRLEN WPFX(len) -#define STRCMP WPFX(cmp) -#define STRNCMP WPFX(ncmp) -#define STRCPY WPFX(cpy) -#define STRNCPY WPFX(ncpy) -#define STRSTR WPFX(str) -#define STRFTIME WPFX(ftime) -#define STRDUP WPFX(dup) -#define STRTOL WPFX(tol) - -#define ISSPACE ISX(space) - -#define WSIZEOF(s) (sizeof(s) / sizeof(WCHAR)) +#include "tts.h" +#include "entry.h" +#include "wide.h" +#include "tts_curses.h" +#include "ui.h" +#include "functions.h" +#include "commands.h" +#include "bindings.h" +#include "str.h" +#include "style.h" +#include "variable.h" extern char const *tts_version; -static volatile sig_atomic_t doexit; - -static WINDOW *titwin, *statwin, *listwin; - -static int in_curses; - -static void drawstatus(const WCHAR *msg, ...); -static void vdrawstatus(const WCHAR *msg, va_list); -static void drawheader(void); -static void drawentries(void); +volatile sig_atomic_t doexit; static time_t laststatus; -typedef struct entry { - WCHAR *en_desc; - int en_secs; - time_t en_started; - time_t en_created; - - struct { - int efl_visible:1; - int efl_invoiced:1; - int efl_marked:1; - int efl_deleted:1; - int efl_nonbillable:1; - } en_flags; - TAILQ_ENTRY(entry) en_entries; -} entry_t; - -static TAILQ_HEAD(entrylist, entry) entries = TAILQ_HEAD_INITIALIZER(entries); - -static entry_t *running; - -static entry_t *entry_new(const WCHAR *); -static void entry_start(entry_t *); -static void entry_stop(entry_t *); -static void entry_free(entry_t *); -static void entry_account(entry_t *); -static time_t entry_time_for_day(time_t, int, int); - -#define time_day(t) (((t) / (60 * 60 * 24)) * (60 * 60 * 24)) -#define entry_day(e) (time_day((e)->en_created)) - -#define time_to_hms(t, h, m, s) do { \ - time_t n = t; \ - h = n / (60 * 60); \ - n %= (60 * 60); \ - m = n / 60; \ - n %= 60; \ - s = n; \ - } while (0) - -#define NHIST 50 -typedef struct histent { - WCHAR *he_text; - TAILQ_ENTRY(histent) he_entries; -} histent_t; -typedef TAILQ_HEAD(hentlist, histent) hentlist_t; - -typedef struct history { - int hi_nents; - hentlist_t hi_ents; -} history_t; - -static history_t *hist_new(void); -static void hist_add(history_t *, WCHAR const *); - -static history_t *searchhist, *prompthist; - -static WCHAR *prompt(WCHAR const *, WCHAR const *, history_t *); -static int prduration(WCHAR *prompt, int *h, int *m, int *s); -static int yesno(WCHAR const *); -static void errbox(WCHAR const *, ...); -static void verrbox(WCHAR const *, va_list); +history_t *searchhist; +history_t *prompthist; #define STATFILE ".rttts" #define RCFILE ".ttsrc" -static int load(void); -static int save(void); +int load(void); +int save(void); static time_t lastsave; static char statfile[PATH_MAX + 1]; static int load_file(const char *); -static void cursadvance(void); - -static void kadd(void); -static void kaddold(void); -static void kquit(void); -static void kup(void); -static void kdown(void); -static void ktoggle(void); -static void kinvoiced(void); -static void kbillable(void); -static void keddesc(void); -static void kedtime(void); -static void ktoggleinv(void); -static void kcopy(void); -static void kaddtime(void); -static void kdeltime(void); -static void khelp(void); -static void kmark(void); -static void kunmarkall(void); -static void ksearch(void); -static void kmarkdel(void); -static void kundel(void); -static void ksync(void); -static void kexec(void); -static void kmerge(void); -static void kint(void); - -typedef struct function { - const WCHAR *fn_name; - void (*fn_hdl) (void); - const WCHAR *fn_desc; -} function_t; - -static function_t funcs[] = { - { WIDE("help"), khelp, WIDE("display help screen") }, - { WIDE("add"), kadd, WIDE("add a new entry and start the timer") }, - { WIDE("add-old"), kaddold, WIDE("add a new entry and specify its duration") }, - { WIDE("delete"), kmarkdel, WIDE("delete the current entry") }, - { WIDE("undelete"), kundel, WIDE("undelete the current entry") }, - { WIDE("quit"), kquit, WIDE("exit TTS") }, - { WIDE("invoice"), kinvoiced, WIDE("toggle current entry as invoiced") }, - { WIDE("billable"), kbillable, WIDE("toggle current entry as billable") }, - { WIDE("mark"), kmark, WIDE("mark the current entry") }, - { WIDE("unmarkall"), kunmarkall, WIDE("unmark all entries") }, - { WIDE("startstop"), ktoggle, WIDE("start or stop the timer") }, - { WIDE("edit-desc"), keddesc, WIDE("edit the current entry's description") }, - { WIDE("edit-time"), kedtime, WIDE("edit the current entry's duration") }, - { WIDE("showhide-inv"), ktoggleinv, WIDE("show or hide invoiced entries") }, - { WIDE("copy"), kcopy, WIDE("copy the current entry's description to a new entry") }, - { WIDE("add-time"), kaddtime, WIDE("add time to the current entry") }, - { WIDE("sub-time"), kdeltime, WIDE("subtract time from the current entry") }, - { WIDE("search"), ksearch, WIDE("search for an entry by name") }, - { WIDE("sync"), ksync, WIDE("purge all deleted entries") }, - { WIDE("prev"), kup, WIDE("move to the previous entry") }, - { WIDE("next"), kdown, WIDE("move to the next entry") }, - { WIDE("execute"), kexec, WIDE("execute a configuration command") }, - { WIDE("merge"), kmerge, WIDE("merge marked entries into current entry") }, - { WIDE("interrupt"), kint, WIDE("split current entry into new entry")}, -}; - -typedef struct tkey { - INT ky_code; - const WCHAR *ky_name; -} tkey_t; - static tkey_t keys[] = { { KEY_BREAK, WIDE("") }, { KEY_DOWN, WIDE("") }, @@ -358,58 +143,10 @@ static tkey_t keys[] = { { KEY_DC, WIDE("") } }; -typedef struct binding { - INT bi_code; - tkey_t *bi_key; - function_t *bi_func; - TAILQ_ENTRY(binding) bi_entries; -} binding_t; +int pagestart; +entry_t *curent; -typedef struct command { - const WCHAR *cm_name; - void (*cm_hdl) (size_t, WCHAR **); -} command_t; - -static command_t *find_command(const WCHAR *); -static void c_bind(size_t, WCHAR **); -static void c_style(size_t, WCHAR **); -static void c_set(size_t, WCHAR **); - -static command_t commands[] = { - { WIDE("bind"), c_bind }, - { WIDE("style"), c_style }, - { WIDE("set"), c_set }, -}; - -static void cmderr(const WCHAR *, ...); -static void vcmderr(const WCHAR *, va_list); - -static size_t tokenise(const WCHAR *, WCHAR ***result); -static void tokfree(WCHAR ***); - -static TAILQ_HEAD(bindlist, binding) bindings = TAILQ_HEAD_INITIALIZER(bindings); - -static tkey_t *find_key(const WCHAR *name); -static function_t *find_func(const WCHAR *name); -static void bind_key(const WCHAR *key, const WCHAR *func); - -static int pagestart; -static entry_t *curent; - -static int showinv = 0; - -typedef struct style { - short sy_pair; - attr_t sy_attrs; -} style_t; - -#define style_fg(s) (COLOR_PAIR((s).sy_pair) | (s).sy_attrs) -#define style_bg(s) ((INT) ' ' | COLOR_PAIR((s).sy_pair) | ((s).sy_attrs & ~WA_UNDERLINE)) - -typedef struct attrname { - const WCHAR *an_name; - attr_t an_value; -} attrname_t; +int showinv = 0; static attrname_t attrnames[] = { { WIDE("normal"), WA_NORMAL }, @@ -421,11 +158,6 @@ static attrname_t attrnames[] = { { WIDE("standout"), WA_STANDOUT } }; -typedef struct colour { - const WCHAR *co_name; - short co_value; -} colour_t; - static colour_t colours[] = { { WIDE("black"), COLOR_BLACK }, { WIDE("red"), COLOR_RED }, @@ -437,55 +169,34 @@ static colour_t colours[] = { { WIDE("white"), COLOR_WHITE } }; -static int attr_find(const WCHAR *name, attr_t *result); -static int colour_find(const WCHAR *name, short *result); - -static void style_clear(style_t *); -static int style_set(style_t *, const WCHAR *fg, const WCHAR *bg); -static int style_add(style_t *, const WCHAR *fg, const WCHAR *bg); - static short default_fg, default_bg; -static void apply_styles(void); +style_t sy_header = { 1, 0 }, + sy_status = { 2, 0 }, + sy_entry = { 3, 0 }, + sy_running = { 4, 0 }, + sy_selected = { 5, 0 }, + sy_date = { 6, 0 }; -static style_t - sy_header = { 1, 0 }, - sy_status = { 2, 0 }, - sy_entry = { 3, 0 }, - sy_running = { 4, 0 }, - sy_selected = { 5, 0 }, - sy_date = { 6, 0 }; +time_t itime = 0; -static time_t itime = 0; +int show_billable = 0; +int delete_advance = 1; +int mark_advance = 1; +int bill_advance = 0; +int bill_increment = 0; +WCHAR *auto_nonbillable; -static int show_billable = 0; -static int delete_advance = 1; -static int mark_advance = 1; -static int bill_advance = 0; -static int bill_increment = 0; -static WCHAR *auto_nonbillable; - -#define VTYPE_INT 1 -#define VTYPE_BOOL 2 -#define VTYPE_STRING 3 - -typedef struct variable { - WCHAR const *va_name; - int va_type; - void *va_addr; -} variable_t; - -static variable_t variables[] = { +variable_t variables[] = { { WIDE("delete_advance"), VTYPE_BOOL, &delete_advance }, { WIDE("mark_advance"), VTYPE_BOOL, &mark_advance }, { WIDE("billable_advance"), VTYPE_BOOL, &bill_advance }, { WIDE("show_billable"), VTYPE_BOOL, &show_billable }, { WIDE("auto_non_billable"), VTYPE_STRING, &auto_nonbillable }, - { WIDE("bill_increment"), VTYPE_INT, &bill_increment } + { WIDE("bill_increment"), VTYPE_INT, &bill_increment }, + { } }; -static variable_t *find_variable(const WCHAR *name); - #ifdef USE_DARWIN_POWER static pthread_t power_thread; static io_connect_t root_port; @@ -751,805 +462,6 @@ char rcfile[PATH_MAX + 1]; return 0; } -void -kquit() -{ -entry_t *en; -int ndel = 0; - - TAILQ_FOREACH(en, &entries, en_entries) { - if (en->en_flags.efl_deleted) - ndel++; - } - - if (ndel) { - WCHAR s[128]; - SNPRINTF(s, WSIZEOF(s), WIDE("Purge %d deleted entries?"), ndel); - if (yesno(s)) { - ksync(); - } - } - - doexit = 1; -} - -void -kadd() -{ -WCHAR *name; -entry_t *en; - name = prompt(WIDE("Description:"), NULL, NULL); - if (!name || !*name) { - free(name); - return; - } - en = entry_new(name); - entry_start(en); - curent = en; - save(); -} - -void -kaddold() -{ -WCHAR *name; -entry_t *en; - name = prompt(WIDE("Description:"), NULL, NULL); - - if (!name || !*name) { - free(name); - return; - } - - en = entry_new(name); - curent = en; - kedtime(); - save(); -} - -void -ktoggle() -{ - itime = 0; - - if (!curent) - return; - - if (curent == running) { - entry_stop(curent); - save(); - return; - } - - if (running) - entry_stop(running); - entry_start(curent); - save(); -} - -void -kundel() -{ - if (!curent) - return; - - curent->en_flags.efl_deleted = 0; - if (delete_advance) - cursadvance(); -} - -void -kmarkdel() -{ -entry_t *en; -int nmarked = 0; - - TAILQ_FOREACH(en, &entries, en_entries) { - if (en->en_flags.efl_marked) { - nmarked++; - en->en_flags.efl_deleted = 1; - } - } - - if (nmarked) - return; - - if (!curent) { - drawstatus(WIDE("No entries to delete.")); - return; - } - - curent->en_flags.efl_deleted = 1; - - if (delete_advance) - cursadvance(); -} - -/* - * Move the cursor to the next entry after an operation like mark or deleted. - * If there are no suitable entries after this one, move it backwards instead. - */ -void -cursadvance() -{ -entry_t *en; - - if (!curent) { - curent = TAILQ_FIRST(&entries); - return; - } - - /* - * Try to find the next suitable entry to move the cursor to. - */ - for (en = TAILQ_NEXT(curent, en_entries); en; en = TAILQ_NEXT(en, en_entries)) { - if (!showinv && en->en_flags.efl_invoiced) - continue; - curent = en; - if (!curent->en_flags.efl_visible) - pagestart++; - return; - } - - /* - * No entries; if the current entry is visible, stay here, otherwise - * try moving backwards instead. - */ - if (showinv || !curent->en_flags.efl_invoiced) - return; - - for (en = TAILQ_PREV(curent, entrylist, en_entries); en; - en = TAILQ_PREV(en, entrylist, en_entries)) { - if (!showinv && en->en_flags.efl_invoiced) - continue; - curent = en; - if (!curent->en_flags.efl_visible) - pagestart--; - return; - } - - /* - * Couldn't find any entries at all? - */ - curent = NULL; -} - -void -ksync() -{ -entry_t *en, *ten; - - TAILQ_FOREACH_SAFE(en, &entries, en_entries, ten) { - if (!en->en_flags.efl_deleted) - continue; - if (en == curent) - curent = NULL; - TAILQ_REMOVE(&entries, en, en_entries); - entry_free(en); - } - - if (curent == NULL) - curent = TAILQ_FIRST(&entries); - save(); -} - -void -kup() -{ -entry_t *prev = curent; - if (!curent) - return; - - do { - if ((prev = TAILQ_PREV(prev, entrylist, en_entries)) == NULL) - break; - } while (!showinv && prev->en_flags.efl_invoiced); - - if (prev == NULL) { - drawstatus(WIDE("Already at first entry.")); - return; - } - - curent = prev; - if (!curent->en_flags.efl_visible) - pagestart--; -} - -void -kdown() -{ -entry_t *next = curent; - if (!curent) - return; - - do { - if ((next = TAILQ_NEXT(next, en_entries)) == NULL) - break; - } while (!showinv && next->en_flags.efl_invoiced); - - if (next == NULL) { - drawstatus(WIDE("Already at last entry.")); - return; - } - - curent = next; - if (!curent->en_flags.efl_visible) - pagestart++; -} - -void -kinvoiced() -{ -entry_t *en; -int anymarked = 0; - - TAILQ_FOREACH(en, &entries, en_entries) { - if (!en->en_flags.efl_marked) - continue; - anymarked = 1; - en->en_flags.efl_invoiced = !en->en_flags.efl_invoiced; - en->en_flags.efl_marked = 0; - } - - if (anymarked) { - save(); - return; - } - - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - curent->en_flags.efl_invoiced = !curent->en_flags.efl_invoiced; - save(); - - en = curent; - - if (showinv) { - if (TAILQ_NEXT(curent, en_entries) != NULL) - curent = TAILQ_NEXT(curent, en_entries); - return; - } - - /* - * Try to find the next uninvoiced request to move the cursor to. - */ - for (;;) { - if ((curent = TAILQ_NEXT(curent, en_entries)) == NULL) - break; /* end of list */ - if (!curent->en_flags.efl_invoiced) - return; - } - - /* - * We didn't find any, so try searching backwards instead. - */ - for (curent = en;;) { - if ((curent = TAILQ_PREV(curent, entrylist, en_entries)) == NULL) - break; /* end of list */ - if (!curent->en_flags.efl_invoiced) - return; - } -} - -void -kbillable() -{ -entry_t *en; -int anymarked = 0; - - TAILQ_FOREACH(en, &entries, en_entries) { - if (!en->en_flags.efl_marked) - continue; - anymarked = 1; - en->en_flags.efl_nonbillable = !en->en_flags.efl_nonbillable; - en->en_flags.efl_marked = 0; - } - - if (anymarked) { - save(); - return; - } - - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - curent->en_flags.efl_nonbillable = !curent->en_flags.efl_nonbillable; - save(); - - if (bill_advance) - cursadvance(); -} - -void -keddesc() -{ -WCHAR *new; - - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - if ((new = prompt(WIDE("Description:"), curent->en_desc, NULL)) == NULL) - return; - - free(curent->en_desc); - curent->en_desc = new; - save(); -} - -void -kedtime() -{ -WCHAR *new, old[64]; -time_t n; -int h, m, s; - - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - n = curent->en_secs; - if (curent->en_started) - n += time(NULL) - curent->en_started; - h = n / (60 * 60); - n %= (60 * 60); - m = n / 60; - n %= 60; - s = n; - - SNPRINTF(old, WSIZEOF(old), WIDE("%02d:%02d:%02d"), h, m, s); - if ((new = prompt(WIDE("Duration [HH:MM:SS]:"), old, NULL)) == NULL) - return; - - if (!SSCANF(new, WIDE("%d:%d:%d"), &h, &m, &s)) { - free(new); - drawstatus(WIDE("Invalid duration.")); - } - - curent->en_secs = (h * 60 * 60) + (m * 60) + s; - if (curent->en_started) - time(&curent->en_started); - - save(); -} - -void -ktoggleinv() -{ -entry_t *en = curent; - showinv = !showinv; - drawstatus(WIDE("%"FMT_L"s invoiced entries."), - showinv ? L"Showing" : L"Hiding"); - - if (curent && !curent->en_flags.efl_invoiced) - return; - - if (!curent) { - curent = TAILQ_FIRST(&entries); - return; - } - - /* - * Try to find the next uninvoiced request to move the cursor to. - */ - for (;;) { - if ((curent = TAILQ_NEXT(curent, en_entries)) == NULL) - break; /* end of list */ - if (!curent->en_flags.efl_invoiced) - return; - } - - /* - * We didn't find any, so try searching backwards instead. - */ - for (curent = en;;) { - if ((curent = TAILQ_PREV(curent, entrylist, en_entries)) == NULL) - break; /* end of list */ - if (!curent->en_flags.efl_invoiced) - return; - } -} - -void -kcopy() -{ -entry_t *en; - - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - en = entry_new(curent->en_desc); - curent = en; - entry_start(en); - save(); -} - -void -kaddtime() -{ -WCHAR *tstr; -int h = 0, m = 0, s = 0, secs; - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - if ((tstr = prompt(WIDE("Time to add ([[HH:]MM:]SS):"), NULL, NULL)) == NULL) - return; - - if (!*tstr) { - drawstatus(WIDE("")); - free(tstr); - return; - } - - if (SSCANF(tstr, WIDE("%d:%d:%d"), &h, &m, &s) != 3) { - h = 0; - if (SSCANF(tstr, WIDE("%d:%d"), &m, &s) != 2) { - m = 0; - if (SSCANF(tstr, WIDE("%d"), &s) != 1) { - free(tstr); - drawstatus(WIDE("Invalid time format.")); - return; - } - } - } - - free(tstr); - - if (m >= 60) { - drawstatus(WIDE("Minutes cannot be more than 59.")); - return; - } - - if (s >= 60) { - drawstatus(WIDE("Seconds cannot be more than 59.")); - return; - } - - secs = s + m*60 + h*60*60; - curent->en_secs += secs; - save(); -} - -void -kdeltime() -{ -WCHAR *tstr; -int h = 0, m = 0, s = 0, secs; - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - if ((tstr = prompt(WIDE("Time to subtract, ([[HH:]MM:]SS):"), NULL, NULL)) == NULL) - return; - - if (!*tstr) { - drawstatus(WIDE("")); - free(tstr); - return; - } - - if (SSCANF(tstr, WIDE("%d:%d:%d"), &h, &m, &s) != 3) { - h = 0; - if (SSCANF(tstr, WIDE("%d:%d"), &m, &s) != 2) { - m = 0; - if (SSCANF(tstr, WIDE("%d"), &s) != 1) { - free(tstr); - drawstatus(WIDE("Invalid time format.")); - return; - } - } - } - - free(tstr); - if (m >= 60) { - drawstatus(WIDE("Minutes cannot be more than 59.")); - return; - } - - if (s >= 60) { - drawstatus(WIDE("Seconds cannot be more than 59.")); - return; - } - - entry_account(curent); - - secs = s + m*60 + h*60*60; - if (curent->en_secs - secs < 0) { - drawstatus(WIDE("Remaining time cannot be less than zero.")); - return; - } - - curent->en_secs -= secs; - save(); -} - -void -kmerge() -{ -entry_t *en, *ten; -int nmarked = 0; -WCHAR pr[128]; -int h, m, s = 0; - - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - /* - * Count number of marked entries and the summed time. - */ - TAILQ_FOREACH(en, &entries, en_entries) { - if (!en->en_flags.efl_marked || en == curent) - continue; - nmarked++; - s += en->en_secs; - if (en->en_started) - s += time(NULL) - en->en_started; - } - - if (nmarked == 0) { - drawstatus(WIDE("No marked entries.")); - return; - } - - h = s / (60 * 60); - s %= (60 * 60); - m = s / 60; - s %= 60; - - SNPRINTF(pr, WSIZEOF(pr), WIDE("Merge %d marked entries [%02d:%02d:%02d] into current entry?"), - nmarked, h, m, s); - if (!yesno(pr)) - return; - - TAILQ_FOREACH_SAFE(en, &entries, en_entries, ten) { - if (!en->en_flags.efl_marked || en == curent) - continue; - if (en->en_started) - entry_stop(en); - curent->en_secs += en->en_secs; - TAILQ_REMOVE(&entries, en, en_entries); - entry_free(en); - } - save(); -} - -void -khelp() -{ -WINDOW *hwin; -size_t nhelp = 0; -WCHAR **help; -#define HTITLE WIDE(" TTS keys ") -size_t width = 0; -size_t i; -INT c; -binding_t *bi; - /* Count the number of bindings */ - TAILQ_FOREACH(bi, &bindings, bi_entries) - nhelp++; - help = calloc(nhelp, sizeof(const WCHAR *)); - - i = 0; - TAILQ_FOREACH(bi, &bindings, bi_entries) { - WCHAR s[128]; - if (bi->bi_key) - SNPRINTF(s, WSIZEOF(s), WIDE("%-10"FMT_L"s %"FMT_L"s (%"FMT_L"s)"), - bi->bi_key->ky_name, bi->bi_func->fn_desc, - bi->bi_func->fn_name); - else - SNPRINTF(s, WSIZEOF(s), WIDE("%"FMT_L"c %"FMT_L"s (%"FMT_L"s)"), - bi->bi_code, bi->bi_func->fn_desc, bi->bi_func->fn_name); - help[i] = STRDUP(s); - i++; - } - - if (nhelp > (LINES - 6)) - nhelp = LINES - 6; - - for (i = 0; i < nhelp; i++) - if (STRLEN(help[i]) > width) - width = STRLEN(help[i]); - - hwin = newwin(nhelp + 4, width + 4, - (LINES / 2) - ((nhelp + 2) / 2), - (COLS / 2) - ((width + 2) / 2)); - wborder(hwin, 0, 0, 0, 0, 0, 0, 0, 0); - - wattron(hwin, A_REVERSE | A_BOLD); - wmove(hwin, 0, (width / 2) - (WSIZEOF(HTITLE) - 1)/2); - WADDSTR(hwin, HTITLE); - wattroff(hwin, A_REVERSE | A_BOLD); - - for (i = 0; i < nhelp; i++) { - wmove(hwin, i + 2, 2); - WADDSTR(hwin, help[i]); - } - - wrefresh(hwin); - - while (WGETCH(hwin, &c) == ERR -#ifdef KEY_RESIZE - || (c == KEY_RESIZE) -#endif - ) - ; - - delwin(hwin); - - for (i = 0; i < nhelp; i++) - free(help[i]); - free(help); -} - -void -kmark() -{ - if (!curent) { - drawstatus(WIDE("No entry selected.")); - return; - } - - curent->en_flags.efl_marked = !curent->en_flags.efl_marked; - - if (mark_advance) - cursadvance(); -} - -void -kunmarkall() -{ -entry_t *en; - TAILQ_FOREACH(en, &entries, en_entries) - en->en_flags.efl_marked = 0; -} - -void -kint() -{ -time_t duration; -entry_t *en; -WCHAR *name; - - if (!itime) { - if (!running) { - drawstatus(WIDE("No running entry.")); - return; - } - - itime = time(NULL); - return; - } - - if (!running) { - drawstatus(WIDE("No running entry.")); - return; - } - - name = prompt(WIDE("Description:"), NULL, NULL); - - if (!name || !*name) { - itime = 0; - free(name); - return; - } - - if (itime) { - duration = time(NULL) - itime; - } else { - int h, m, s; - if (prduration(WIDE("Duration [HH:MM:SS]:"), &h, &m, &s) == -1) - return; - - duration = (h * 60 * 60) + (m * 60) + s; - } - - itime = 0; - running->en_secs += (time(NULL) - running->en_started); - running->en_started = time(NULL); - running->en_secs -= duration; - - en = entry_new(name); - en->en_created = time(NULL) - duration; - en->en_secs = duration; - save(); - - free(name); -} - -void -ksearch() -{ -static WCHAR *lastsearch; -WCHAR *term; -entry_t *start, *cur; - - if (!curent) { - drawstatus(WIDE("No entries.")); - return; - } - - if ((term = prompt(WIDE("Search:"), NULL, NULL)) == NULL) - return; - - if (!*term) { - free(term); - if (!lastsearch) - return; - - term = lastsearch; - } else { - free(lastsearch); - lastsearch = term; - } - - cur = start = curent; - - for (;;) { - cur = TAILQ_NEXT(cur, en_entries); - if (cur == NULL) { - drawstatus(WIDE("Search reached last entry, continuing from top.")); - cur = TAILQ_FIRST(&entries); - } - - if (cur == start) { - drawstatus(WIDE("No matches.")); - break; - } - - if (STRSTR(cur->en_desc, term)) { - curent = cur; - if (!showinv && cur->en_flags.efl_invoiced) - showinv = 1; - return; - } - - } -} - -void -kexec() -{ -WCHAR *cmd; -WCHAR **args; -command_t *cmds; -size_t nargs; - - if ((cmd = prompt(WIDE(":"), NULL, NULL)) == NULL || !*cmd) { - free(cmd); - return; - } - - nargs = tokenise(cmd, &args); - free(cmd); - - if (nargs == 0) { - tokfree(&args); - return; - } - - if ((cmds = find_command(args[0])) == NULL) { - drawstatus(WIDE("Unknown command.")); - tokfree(&args); - return; - } - - cmds->cm_hdl(nargs, args); - tokfree(&args); -} - void drawheader() { @@ -1982,107 +894,6 @@ chtype oldbg; wbkgdset(listwin, oldbg); } -entry_t * -entry_new(desc) - const WCHAR *desc; -{ -entry_t *en; - if ((en = calloc(1, sizeof(*en))) == NULL) - return NULL; - - if (auto_nonbillable && STRSTR(desc, auto_nonbillable)) - en->en_flags.efl_nonbillable = 1; - - TAILQ_INSERT_HEAD(&entries, en, en_entries); - - en->en_desc = STRDUP(desc); - time(&en->en_created); - - return en; -} - -void -entry_start(en) - entry_t *en; -{ - if (running) - entry_stop(running); - time(&en->en_started); - running = en; -} - -void -entry_stop(en) - entry_t *en; -{ - if (running == en) - running = NULL; - en->en_secs += time(NULL) - en->en_started; - en->en_started = 0; -} - -void -entry_free(en) - entry_t *en; -{ - if (en == running) - entry_stop(en); - free(en->en_desc); -} - -void -entry_account(en) - entry_t *en; -{ - if (!en->en_started) - return; - en->en_secs += time(NULL) - en->en_started; - time(&en->en_started); -} - -/* - * Return the amount of time for the day on which the timestamp .when falls. - * If .inv is 0, sum non-invoiced entries; if 1, sum invoiced entries; if - * 2, sum billable entries; if -1, sum all entries. If .incr is non-zero, - * individual entry time will be rounded up to intervals of that many minutes. - */ -time_t -entry_time_for_day(when, inv, incr) - time_t when; -{ -time_t day = time_day(when); -time_t sum = 0; -entry_t *en; -int rnd = incr * 60; - TAILQ_FOREACH(en, &entries, en_entries) { - time_t n; - - if (entry_day(en) > day) - continue; - if (entry_day(en) < day) - break; - - if (inv == 0 && en->en_flags.efl_invoiced) - continue; - if (inv == 1 && !en->en_flags.efl_invoiced) - continue; - if (inv == 2 && en->en_flags.efl_nonbillable) - continue; - - n = en->en_secs; - if (en->en_started) - n += time(NULL) - en->en_started; - - if (!n) - continue; - - if (rnd) - n = (1 + round((n - 1) / rnd)) * rnd; - sum += n; - } - return sum; -} - int load() { @@ -2368,10 +1179,10 @@ function_t * find_func(name) const WCHAR *name; { -size_t i; - for (i = 0; i < sizeof(funcs) / sizeof(*funcs); i++) - if (STRCMP(name, funcs[i].fn_name) == 0) - return &funcs[i]; +function_t *f; + for (f = funcs; f->fn_name; f++) + if (STRCMP(name, f->fn_name) == 0) + return f; return NULL; } @@ -2424,223 +1235,17 @@ INT code; TAILQ_INSERT_TAIL(&bindings, binding, bi_entries); } -size_t -tokenise(str, res) - const WCHAR *str; - WCHAR ***res; -{ -int ntoks = 0; -const WCHAR *p, *q; - - *res = NULL; - p = str; - - for (;;) { - ptrdiff_t sz; - int qskip = 0; - - /* Skip leading whitespace */ - while (ISSPACE(*p)) - p++; - - /* End of string - no more arguments */ - if (!*p) - break; - - q = p; - - if (*q == '"') { - /* Quoted string - scan for end of string */ - int isbsl = 0; - p++; - - while (*++q) { - /* Handle escaping with backslash; currently works but the \ isn't - * removed from the string. - */ - if (*q == '\\') { - isbsl = 1; - continue; - } - - if (!isbsl && (*q == '"')) - break; - - isbsl = 0; - } - /* At this point, *q == '"'. If it's NUL instead, then the - * string was not terminated with a closing '"' before the end - * of the line. We could give an error here, but it seems - * more useful to just accept it. - */ - if (*q == '"') - qskip = 1; - } else { - /* Not quoted - just find the next whitespace */ - while (!ISSPACE(*q) && *q) - q++; - } - - /* Copy the argument (which is sz bytes long) into the result array */ - sz = (q - p); - *res = realloc(*res, sizeof(WCHAR *) * (ntoks + 1)); - (*res)[ntoks] = malloc(sizeof(WCHAR) * (sz + 1)); - MEMCPY((*res)[ntoks], p, sz); - (*res)[ntoks][sz] = 0; - ntoks++; - - if (qskip) - q += qskip; - - while (ISSPACE(*q)) - q++; - - /* - * q is the start of the next token (with leading whitespace); reset - * p to process the next argument. - */ - if (!*q) - break; - p = q; - } - - *res = realloc(*res, sizeof(WCHAR *) * (ntoks + 1)); - (*res)[ntoks] = NULL; - return ntoks; -} - -void -tokfree(vec) - WCHAR ***vec; -{ -WCHAR **p; - for (p = (*vec); *p; p++) - free(*p); - free(*vec); -} - -command_t * -find_command(name) - const WCHAR *name; -{ -size_t i; - for (i = 0; i < sizeof(commands) / sizeof(*commands); i++) - if (STRCMP(name, commands[i].cm_name) == 0) - return &commands[i]; - return NULL; -} - -void -c_style(argc, argv) - size_t argc; - WCHAR **argv; -{ -style_t *sy; -WCHAR *last, *tok; - - if (argc < 3 || argc > 4) { - cmderr(WIDE("Usage: style [background]")); - return; - } - - if (STRCMP(argv[1], WIDE("header")) == 0) - sy = &sy_header; - else if (STRCMP(argv[1], WIDE("status")) == 0) - sy = &sy_status; - else if (STRCMP(argv[1], WIDE("entry")) == 0) - sy = &sy_entry; - else if (STRCMP(argv[1], WIDE("selected")) == 0) - sy = &sy_selected; - else if (STRCMP(argv[1], WIDE("running")) == 0) - sy = &sy_running; - else if (STRCMP(argv[1], WIDE("date")) == 0) - sy = &sy_date; - else { - cmderr(WIDE("Unknown style item.")); - return; - } - - style_clear(sy); - for (tok = STRTOK(argv[2], WIDE(","), &last); tok != NULL; - tok = STRTOK(NULL, WIDE(","), &last)) { - style_add(sy, tok, argv[3]); - } - - apply_styles(); -} - -void -c_bind(argc, argv) - size_t argc; - WCHAR **argv; -{ - if (argc != 3) { - cmderr(WIDE("Usage: bind ")); - return; - } - - bind_key(argv[1], argv[2]); -} - variable_t * find_variable(name) WCHAR const *name; { -size_t i; - for (i = 0; i < sizeof(variables) / sizeof(*variables); i++) - if (STRCMP(name, variables[i].va_name) == 0) - return &variables[i]; +variable_t *v; + for (v = variables; v->va_name; v++) + if (STRCMP(name, v->va_name) == 0) + return v; return NULL; } -void -c_set(argc, argv) - size_t argc; - WCHAR **argv; -{ -variable_t *var; -int val; - - if (argc != 3) { - cmderr(WIDE("Usage: set ")); - return; - } - - if ((var = find_variable(argv[1])) == NULL) { - cmderr(WIDE("Unknown variable \"%"FMT_L"s\"."), argv[1]); - return; - } - - switch (var->va_type) { - case VTYPE_BOOL: - if (STRCMP(argv[2], WIDE("true")) == 0 || - STRCMP(argv[2], WIDE("yes")) == 0 || - STRCMP(argv[2], WIDE("on")) == 0 || - STRCMP(argv[2], WIDE("1")) == 0) { - val = 1; - } else if (STRCMP(argv[2], WIDE("false")) == 0 || - STRCMP(argv[2], WIDE("no")) == 0 || - STRCMP(argv[2], WIDE("off")) == 0 || - STRCMP(argv[2], WIDE("0")) == 0) { - val = 0; - } else { - cmderr(WIDE("Invalid value for boolean: \"%"FMT_L"s\"."), argv[2]); - return; - } - - *(int *)var->va_addr = val; - break; - - case VTYPE_STRING: - *(WCHAR **)var->va_addr = STRDUP(argv[2]); - break; - - case VTYPE_INT: - *(int *)var->va_addr = STRTOL(argv[2], NULL, 0); - break; - } -} - int attr_find(name, result) const WCHAR *name; @@ -2672,6 +1277,7 @@ size_t i; return -1; } + void style_clear(sy) style_t *sy; diff --git a/tts.h b/tts.h new file mode 100644 index 0000000..318f8ba --- /dev/null +++ b/tts.h @@ -0,0 +1,65 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_H +#define TTS_H + +#include + +#include "wide.h" +#include "queue.h" +#include "entry.h" + +/* + * Configuration options. + */ + +extern int show_billable; +extern int delete_advance; +extern int mark_advance; +extern int bill_advance; +extern int bill_increment; +extern WCHAR *auto_nonbillable; + +/* + * Global state. + */ + +extern entry_t *curent; +extern volatile sig_atomic_t doexit; +extern int pagestart; +extern time_t itime; + +int load(void); +int save(void); + +/* + * Command history. + */ + +#define NHIST 50 +typedef struct histent { + WCHAR *he_text; + TAILQ_ENTRY(histent) he_entries; +} histent_t; +typedef TAILQ_HEAD(hentlist, histent) hentlist_t; + +typedef struct history { + int hi_nents; + hentlist_t hi_ents; +} history_t; + +history_t *hist_new(void); +void hist_add(history_t *, WCHAR const *); + +extern history_t *searchhist; +extern history_t *prompthist; + +#endif /* !TTS_H */ diff --git a/tts_curses.h b/tts_curses.h new file mode 100644 index 0000000..79f4e20 --- /dev/null +++ b/tts_curses.h @@ -0,0 +1,30 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_CURSES_H +#define TTS_CURSES_H + +#include "config.h" + +#if defined HAVE_NCURSESW_CURSES_H +# include +#elif defined HAVE_NCURSESW_H +# include +#elif defined HAVE_NCURSES_CURSES_H +# include +#elif defined HAVE_NCURSES_H +# include +#elif defined HAVE_CURSES_H +# include +#else +# error "SVR4 or XSI compatible curses header file required" +#endif + +#endif /* !TTS_CURSES_H */ diff --git a/ui.c b/ui.c new file mode 100644 index 0000000..1ac4965 --- /dev/null +++ b/ui.c @@ -0,0 +1,65 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#include "ui.h" +#include "tts.h" + +WINDOW *titwin, *statwin, *listwin; +int in_curses; + +/* + * Move the cursor to the next entry after an operation like mark or deleted. + * If there are no suitable entries after this one, move it backwards instead. + */ +void +cursadvance() +{ +entry_t *en; + + if (!curent) { + curent = TAILQ_FIRST(&entries); + return; + } + + /* + * Try to find the next suitable entry to move the cursor to. + */ + for (en = TAILQ_NEXT(curent, en_entries); en; en = TAILQ_NEXT(en, en_entries)) { + if (!showinv && en->en_flags.efl_invoiced) + continue; + curent = en; + if (!curent->en_flags.efl_visible) + pagestart++; + return; + } + + /* + * No entries; if the current entry is visible, stay here, otherwise + * try moving backwards instead. + */ + if (showinv || !curent->en_flags.efl_invoiced) + return; + + for (en = TAILQ_PREV(curent, entrylist, en_entries); en; + en = TAILQ_PREV(en, entrylist, en_entries)) { + if (!showinv && en->en_flags.efl_invoiced) + continue; + curent = en; + if (!curent->en_flags.efl_visible) + pagestart--; + return; + } + + /* + * Couldn't find any entries at all? + */ + curent = NULL; +} + diff --git a/ui.h b/ui.h new file mode 100644 index 0000000..005ac7e --- /dev/null +++ b/ui.h @@ -0,0 +1,37 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_UI_H +#define TTS_UI_H + +#include + +#include "tts.h" +#include "tts_curses.h" +#include "wide.h" + +extern WINDOW *titwin, *statwin, *listwin; +extern int in_curses; +extern int showinv; + +void cursadvance(void); + +void drawstatus (const WCHAR *msg, ...); +void vdrawstatus (const WCHAR *msg, va_list); +void drawheader (void); +void drawentries (void); + +WCHAR *prompt (WCHAR const *, WCHAR const *, history_t *); +int prduration (WCHAR *prompt, int *h, int *m, int *s); +int yesno (WCHAR const *); +void errbox (WCHAR const *, ...); +void verrbox (WCHAR const *, va_list); + +#endif /* !TTS_UI_H */ diff --git a/variable.h b/variable.h new file mode 100644 index 0000000..8cb8447 --- /dev/null +++ b/variable.h @@ -0,0 +1,28 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_VARIABLE_H +#define TTS_VARIABLE_H + +#include "wide.h" + +typedef struct variable { + WCHAR const *va_name; + int va_type; + void *va_addr; +} variable_t; + +#define VTYPE_INT 1 +#define VTYPE_BOOL 2 +#define VTYPE_STRING 3 + +variable_t *find_variable(const WCHAR *name); + +#endif /* !TTS_VARIABLE_H */ diff --git a/wide.c b/wide.c new file mode 100644 index 0000000..7e339c5 --- /dev/null +++ b/wide.c @@ -0,0 +1,26 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#include "wide.h" +#include "tts_curses.h" + +#ifdef NEED_TTS_WGETCH +static int +tts_wgetch(win, d) + WINDOW *win; + int *d; +{ +int c; + if ((c = wgetch(win)) == ERR) + return ERR; + *d = c; + return OK; +} +#endif /* !NEED_TTS_WGETCH */ diff --git a/wide.h b/wide.h new file mode 100644 index 0000000..85df84d --- /dev/null +++ b/wide.h @@ -0,0 +1,81 @@ +/* + * TTS - track your time. + * Copyright (c) 2012-2014 Felicity Tarnell. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely. This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef TTS_WIDE_H +#define TTS_WIDE_H + +#include "config.h" +#include "tts_curses.h" + +#ifdef HAVE_CURSES_ENHANCED +# include + +# define WPFX(x) wcs##x +# define WIDE(x) L##x +# define ISX(x) isw##x +# define WCHAR wchar_t +# define FMT_L "l" +# define SNPRINTF swprintf +# define VSNPRINTF vswprintf +# define SSCANF swscanf +# define MEMCPY wmemcpy +# define MEMMOVE wmemmove +# define MBSTOWCS mbstowcs +# define WCSTOMBS wcstombs +# define FPRINTF fwprintf +# define STRTOK wcstok + +# define GETCH get_wch +# define WGETCH wget_wch +# define ADDSTR addwstr +# define WADDSTR waddwstr +# define INT wint_t +#else +# define WPFX(x) str##x +# define WIDE(x) x +# define ISX(x) is##x +# define WCHAR char +# define FMT_L +# define SNPRINTF snprintf +# define VSNPRINTF vsnprintf +# define SSCANF sscanf +# define MEMCPY memcpy +# define MEMMOVE memmove +# define MBSTOWCS strncpy +# define WCSTOMBS strncpy +# define FPRINTF fprintf +# define STRTOK strtok_r + +# define ADDSTR addstr +# define WADDSTR waddstr +# define INT int + +# define NEED_TTS_WGETCH +extern int tts_wgetch(WINDOW *, int *); + +# define WGETCH tts_wgetch +# define GETCH(c) tts_wgetch(stdscr,c) +#endif + +#define STRLEN WPFX(len) +#define STRCMP WPFX(cmp) +#define STRNCMP WPFX(ncmp) +#define STRCPY WPFX(cpy) +#define STRNCPY WPFX(ncpy) +#define STRSTR WPFX(str) +#define STRFTIME WPFX(ftime) +#define STRDUP WPFX(dup) +#define STRTOL WPFX(tol) + +#define ISSPACE ISX(space) + +#define WSIZEOF(s) (sizeof(s) / sizeof(WCHAR)) + +#endif /* !TTS_WIDE_H */