Compare commits

...

51 commits

Author SHA1 Message Date
Liss Tarnell
4436927dcb configure: rebuild 2017-09-25 13:26:36 +01:00
Liss Tarnell
be5a5c6a41 configure.ac: RT/TTS -> TTS 2017-09-25 13:25:58 +01:00
Liss Tarnell
b473a74c9d configure.ac: bump version to D.84.1 2017-09-25 13:25:23 +01:00
Liss Tarnell
b4e2a8e6cf functions.c: do not allow entry titles containing only whitespace 2017-09-25 13:24:40 +01:00
Liss Tarnell
e37b911807 kmerge: remove extraneous '?' in merge prompt 2017-09-25 13:18:23 +01:00
Liss Tarnell
6c853f1b95 configure: raise error if curses is not found 2017-09-25 13:14:35 +01:00
Liss Tarnell
000a019648 README: -> README.md 2017-06-27 15:14:11 +01:00
Liss Tarnell
75750530a7 update README and add a screenshot 2017-06-27 15:13:57 +01:00
Felicity Tarnell
e73d4f8a10 bling_import.pl: fix LWP import 2017-05-23 09:42:45 +01:00
Felicity Tarnell
bf5a894fc2 .gitignore: update 2016-04-12 10:13:21 +01:00
Felicity Tarnell
a91ce6326d str.c: missing #include <time.h> 2016-04-12 10:12:44 +01:00
Felicity Tarnell
c0074a61d2 prompt(): handle tab characters 2014-11-18 15:48:14 +00:00
Felicity Tarnell
7ff1070bb7 Fix bling_import.pl error message. 2014-05-27 12:18:10 +01:00
Felicity Tarnell
86f643dc0e Add mailing list info to README. 2014-03-31 02:08:17 +01:00
Felicity Tarnell
49651c90a2 Honour user's time format when editing entry duration. 2014-03-08 13:58:21 +00:00
Felicity Tarnell
0479ee0f89 Configurable time format. 2014-03-08 13:49:19 +00:00
Felicity Tarnell
2af789884e Use new time format in day headers. 2014-03-08 13:26:35 +00:00
Felicity Tarnell
1ea26690e5 Include tts.h for wcs* prototypes. 2014-03-08 13:09:14 +00:00
Felicity Tarnell
2e64382573 Prototype wcsl* 2014-03-08 13:08:55 +00:00
Felicity Tarnell
1758e63f6b Fix wcslcpy compat. 2014-03-08 13:05:44 +00:00
Felicity Tarnell
7c26144e31 Provide compat functions for wcsl{cpy,cat}(). 2014-03-08 13:03:19 +00:00
Felicity Tarnell
684c581b32 Second argument to input_char() is wint_t, not wchar_t. 2014-03-08 12:57:35 +00:00
Felicity Tarnell
0959aad655 Prompt support in macros: $[Text]. 2014-03-08 12:54:05 +00:00
Felicity Tarnell
0bf6dd5766 Escape macro bodies when displaying in help. 2014-03-08 12:31:58 +00:00
Felicity Tarnell
34712e643c New time format: "1h30m7s".
This is fairly flexible, so you could say 10s1h, or even 1h1h1h (which is
equivalent to 3h).  Time parsing is now implemented by a single function, so
there should be no inconsistencies.

The old [[HH:]MM:]SS syntax is still supported, but not used in prompts.
2014-03-08 12:18:22 +00:00
Felicity Tarnell
c0df452c04 Provide error for invalid number in c_set(). 2014-03-08 11:37:52 +00:00
Felicity Tarnell
a5dd2e4ae5 Fix crash on executing unknown command. 2014-03-08 11:31:08 +00:00
Felicity Tarnell
bf84f9a054 Fix memory leak when re-setting variable. 2014-03-08 11:29:01 +00:00
Felicity Tarnell
5739ca900f Remove obsolete comment. 2014-03-08 11:27:06 +00:00
River Tarnell
e8ccd681e3 Don't spin-loop in prompt(). 2014-03-08 01:00:30 +00:00
Felicity Tarnell
f25e283f21 Fix NetBSD and non-ncurses build. Simplify CPPFLAGS. 2014-03-08 00:50:11 +00:00
Felicity Tarnell
003ac6cca4 Always use wide characters. 2014-03-08 00:30:40 +00:00
Felicity Tarnell
d239b1669b Clean up power code. 2014-03-07 23:50:13 +00:00
Felicity Tarnell
524b09511d Code cleanups; no functional changes. 2014-03-07 23:47:31 +00:00
Felicity Tarnell
3440a87ea3 Add macro support.
Replace WGETCH() with input_char(), which works the same except it supports an
input buffer.  Calling input_macro(s) sets the input buffer to 's'.  The input
buffer is returned by input_char() as if it had been typed by the user.

If an error occurs (cmderr()), the input buffer is cleared.
2014-03-07 23:34:06 +00:00
Felicity Tarnell
44b2e4371e Copyright notice for Makefile. 2014-03-07 22:44:57 +00:00
Felicity Tarnell
ba30bffef2 Lots of changes, so make this version D.84.0. 2014-03-07 22:40:18 +00:00
Felicity Tarnell
0b072f4d3a Fix up the sample style a bit. 2014-03-07 22:39:02 +00:00
Felicity Tarnell
fb9ffde209 Fix prompt styling & memory leak in yesno(). 2014-03-07 22:37:43 +00:00
Felicity Tarnell
707812a078 Draw the input prompt in the right place. 2014-03-07 22:24:33 +00:00
Felicity Tarnell
ca908281b4 No need to use kevent64(). 2014-03-07 22:19:07 +00:00
Felicity Tarnell
e8396d70ba Always timeout the poll.
Otherwise, events like the laststatus update and (more importantly) lastsave
don't get run unless there's a running entry.
2014-03-07 22:03:47 +00:00
Felicity Tarnell
0c3b7443d5 Handle multiple input characters at once. 2014-03-07 22:00:47 +00:00
Felicity Tarnell
9a3719817e Fix ^C handling. 2014-03-07 21:58:10 +00:00
Felicity Tarnell
38021971e3 Rework input handling; no functional changes.
Instead of using halfdelay() and relying on GETCH() to return, poll for
input ourselves using select() and only call GETCH() when input is ready.

On Darwin, use kqueue instead of select.  This allows us to receive IOKit
notifications directly to the main thread, instead of having a separate
thread dedicated to that.  We also no longer link to CoreFoundation.
2014-03-07 21:54:26 +00:00
Felicity Tarnell
9676c88378 Add .gitignore. 2014-03-07 20:21:09 +00:00
Felicity Tarnell
a1a3b576a0 OS X: Remove CoreFoundation; use kqueue() directly. 2014-03-07 20:20:20 +00:00
Felicity Tarnell
50d9efdac9 Cleanups; add 'make depend'. 2014-03-07 19:18:20 +00:00
Felicity Tarnell
332fdba0bd Source refactoring; no functional changes. 2014-03-07 17:43:41 +00:00
Felicity Tarnell
b174130c91 T.83.6: Incorrect memory allocation in configuration parser. 2014-03-07 16:11:23 +00:00
Felicity Tarnell
af2b5dc45c T.83.5: Fix 'M'erge giving bogus time when a marked entry is running. 2014-03-06 16:21:40 +00:00
35 changed files with 7037 additions and 3137 deletions

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
*.o
*.swp
*~
/Makefile
/config.h
/config.log
/config.status
/tts
/vers.c
/build
/autom4te.cache
/doconf
/bling_import.pl.old
/queue.h

View file

@ -1,13 +1,25 @@
# 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.
.SUFFIXES: .c .o .d .h
VPATH = @top_srcdir@ VPATH = @top_srcdir@
CC = @CC@ CC = @CC@
CPPFLAGS = @CPPFLAGS@ MAKEDEPEND = @CC@ -M
CPPFLAGS = @CPPFLAGS@ -D_XOPEN_SOURCE_EXTENDED
CFLAGS = @CFLAGS@ -I@top_srcdir@ -I@top_builddir@ CFLAGS = @CFLAGS@ -I@top_srcdir@ -I@top_builddir@
LDFLAGS = @LDFLAGS@ LDFLAGS = @LDFLAGS@
LIBS = @LIBS@ LIBS = @LIBS@
INSTALL = @INSTALL@ INSTALL = @INSTALL@
OBJS = tts.o OBJS = tts.o wide.o entry.o ui.o functions.o commands.o bindings.o \
str.o style.o wcslcpy.o
prefix = @prefix@ prefix = @prefix@
exec_prefix = @exec_prefix@ exec_prefix = @exec_prefix@
@ -23,8 +35,22 @@ install:
.c.o: .c.o:
${CC} ${CPPFLAGS} ${CFLAGS} -c $< ${CC} ${CPPFLAGS} ${CFLAGS} -c $<
.c.d:
$(MAKEDEPEND) $(CPPFLAGS) $(CFLAGS) $< -o $@
vers.c: vers.c.sh configure.ac vers.c: vers.c.sh configure.ac
sh @top_srcdir@/vers.c.sh @top_srcdir@/configure.ac sh @top_srcdir@/vers.c.sh @top_srcdir@/configure.ac
clean: clean:
rm -f tts *.o rm -f tts ${OBJS} $(OBJS:.o=.d)
depend: $(OBJS:.o=.d)
sed '/^# Do not remove this line -- make depend needs it/,$$ d' \
<Makefile >Makefile.new
echo '# Do not remove this line -- make depend needs it' >>Makefile.new
cat *.d >> Makefile.new
mv Makefile.new Makefile
.PHONY: depend clean install libuv
# Do not remove this line -- make depend needs it

View file

@ -1,8 +1,21 @@
TTS - Time-tracking software TTS - Time-tracking software
============================ ============================
TTS is a simple, text-based (curses) time-tracking application. For more TTS is a simple, text-based (curses) time-tracking application. It allows you
details, see the website at <http://loreley.flyingparchment.org.uk/~felicity/pages/tts>. to track the time you spend working by client, project, etc. to allow accurate
invoicing of clients or reporting to a corporate time-tracking system. It
uses a simple text format to store data which can easily be parsed to export
data to another system automatically. Entries can be added in bulk or in real
time using a timer, and invoiced and non-billable entries are tracked.
Entries can optionally be rounded up to a minimum billing increment.
Screenshot
----------
![A screenshot of TTS in use](screenshot.png)
Installation
------------
TTS has been tested on FreeBSD, NetBSD, Solaris, Cygwin and Linux, with the TTS has been tested on FreeBSD, NetBSD, Solaris, Cygwin and Linux, with the
following caveats: following caveats:
@ -21,7 +34,8 @@ TTS uses autoconf and can be built as follows:
After starting with 'tts', type '?' for help. After starting with 'tts', type '?' for help.
### Quick start: Quick start
-----------
* Press 'a' to add a new entry, and enter its name. The timer starts running. * Press 'a' to add a new entry, and enter its name. The timer starts running.
* Press space to toggle the timer on and off. * Press space to toggle the timer on and off.
@ -34,6 +48,9 @@ After starting with 'tts', type '?' for help.
start the interrupt timer. When you're done with the other thing, press 'r' start the interrupt timer. When you're done with the other thing, press 'r'
again to assign the interrupt time to a new entry. again to assign the interrupt time to a new entry.
### Contact Contact
-------
Send questions/comments/bugs/patches to <felicity@loreley.flyingparchment.org.uk>. To report problems or request features, please use the GitHub issue tracker at
<https://github.com/ftarnell/tts/issues>. This requires that you have a GitHub
account.

10
aclocal.m4 vendored
View file

@ -108,7 +108,7 @@
# HAVE_CURSES_ENHANCED and ax_cv_curses_enhanced are defined if the # HAVE_CURSES_ENHANCED and ax_cv_curses_enhanced are defined if the
# library supports the X/Open Enhanced Curses definition. In particular, # library supports the X/Open Enhanced Curses definition. In particular,
# the wide-character types attr_t, cchar_t and wint_t, the functions # the wide-character types attr_t, cchar_t and wint_t, the functions
# wattr_set() and wget_wch() and the macros WA_NORMAL and _XOPEN_CURSES # wattr_set() and wget_wch() and the macros WA_BOLD and _XOPEN_CURSES
# are checked. The Ncurses library does NOT conform to this definition, # are checked. The Ncurses library does NOT conform to this definition,
# although NcursesW does. # although NcursesW does.
# #
@ -228,7 +228,7 @@ AC_DEFUN([AX_WITH_CURSES], [
chtype a = A_BOLD; chtype a = A_BOLD;
int b = KEY_LEFT; int b = KEY_LEFT;
chtype c = COLOR_PAIR(1) & A_COLOR; chtype c = COLOR_PAIR(1) & A_COLOR;
attr_t d = WA_NORMAL; attr_t d = WA_BOLD;
cchar_t e; cchar_t e;
wint_t f; wint_t f;
int g = getattrs(stdscr); int g = getattrs(stdscr);
@ -259,7 +259,7 @@ AC_DEFUN([AX_WITH_CURSES], [
chtype a = A_BOLD; chtype a = A_BOLD;
int b = KEY_LEFT; int b = KEY_LEFT;
chtype c = COLOR_PAIR(1) & A_COLOR; chtype c = COLOR_PAIR(1) & A_COLOR;
attr_t d = WA_NORMAL; attr_t d = WA_BOLD;
cchar_t e; cchar_t e;
wint_t f; wint_t f;
int g = getattrs(stdscr); int g = getattrs(stdscr);
@ -290,7 +290,7 @@ AC_DEFUN([AX_WITH_CURSES], [
chtype a = A_BOLD; chtype a = A_BOLD;
int b = KEY_LEFT; int b = KEY_LEFT;
chtype c = COLOR_PAIR(1) & A_COLOR; chtype c = COLOR_PAIR(1) & A_COLOR;
attr_t d = WA_NORMAL; attr_t d = WA_BOLD;
cchar_t e; cchar_t e;
wint_t f; wint_t f;
int g = getattrs(stdscr); int g = getattrs(stdscr);
@ -443,7 +443,7 @@ AC_DEFUN([AX_WITH_CURSES], [
chtype a = A_BOLD; chtype a = A_BOLD;
int b = KEY_LEFT; int b = KEY_LEFT;
chtype c = COLOR_PAIR(1) & A_COLOR; chtype c = COLOR_PAIR(1) & A_COLOR;
attr_t d = WA_NORMAL; attr_t d = WA_BOLD;
cchar_t e; cchar_t e;
wint_t f; wint_t f;
initscr(); initscr();

194
bindings.c Normal file
View file

@ -0,0 +1,194 @@
/*
* 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 <stdlib.h>
#include "bindings.h"
#include "wide.h"
#include "ui.h"
binding_list_t bindings = TTS_TAILQ_HEAD_INITIALIZER(bindings);
static tkey_t keys[] = {
{ KEY_BREAK, L"<BREAK>" },
{ KEY_DOWN, L"<DOWN>" },
{ KEY_UP, L"<UP>" },
{ KEY_LEFT, L"<LEFT>" },
{ KEY_RIGHT, L"<RIGHT>" },
{ KEY_HOME, L"<HOME>" },
{ KEY_BACKSPACE, L"<BACKSPACE>" },
{ 0x7F, L"<BACKSPACE>" }, /* DEL */
{ KEY_F(0), L"<F0>" },
{ KEY_F(1), L"<F1>" },
{ KEY_F(2), L"<F2>" },
{ KEY_F(3), L"<F3>" },
{ KEY_F(4), L"<F4>" },
{ KEY_F(5), L"<F5>" },
{ KEY_F(6), L"<F6>" },
{ KEY_F(7), L"<F7>" },
{ KEY_F(8), L"<F8>" },
{ KEY_F(9), L"<F9>" },
{ KEY_F(10), L"<F10>" },
{ KEY_F(11), L"<F1l>" },
{ KEY_F(12), L"<F12>" },
{ KEY_F(13), L"<F13>" },
{ KEY_F(14), L"<F14>" },
{ KEY_F(15), L"<F15>" },
{ KEY_F(16), L"<F16>" },
{ KEY_F(17), L"<F17>" },
{ KEY_F(18), L"<F18>" },
{ KEY_F(19), L"<F19>" },
{ KEY_F(20), L"<F20>" },
{ KEY_F(21), L"<F21>" },
{ KEY_F(22), L"<F22>" },
{ KEY_F(23), L"<F23>" },
{ KEY_F(24), L"<F24>" },
{ KEY_NPAGE, L"<NEXT>" },
{ KEY_PPAGE, L"<PREV>" },
{ '\001', L"<CTRL-A>" },
{ '\002', L"<CTRL-B>" },
{ '\003', L"<CTRL-C>" },
{ '\004', L"<CTRL-D>" },
{ '\005', L"<CTRL-E>" },
{ '\006', L"<CTRL-F>" },
{ '\007', L"<CTRL-G>" },
{ '\010', L"<CTRL-H>" },
{ '\011', L"<CTRL-I>" },
{ '\011', L"<TAB>" },
{ '\012', L"<CTRL-J>" },
{ '\013', L"<CTRL-K>" },
{ '\014', L"<CTRL-L>" },
{ '\015', L"<CTRL-N>" },
{ '\016', L"<CTRL-O>" },
{ '\017', L"<CTRL-P>" },
{ '\020', L"<CTRL-Q>" },
{ '\021', L"<CTRL-R>" },
{ '\022', L"<CTRL-S>" },
{ '\023', L"<CTRL-T>" },
{ '\024', L"<CTRL-U>" },
{ '\025', L"<CTRL-V>" },
{ '\026', L"<CTRL-W>" },
{ '\027', L"<CTRL-X>" },
{ '\030', L"<CTRL-Y>" },
{ '\031', L"<CTRL-Z>" },
{ ' ', L"<SPACE>" },
{ KEY_ENTER, L"<ENTER>" },
{ KEY_BACKSPACE, L"<BACKSPACE>" },
{ KEY_DC, L"<DELETE>" }
};
/*
* Bind .keyname to run the function .funcname. If a binding for .keyname
* already exists, overwrite it.
*
* If .keyname is a single character, e.g. 'a', it is used as a key name
* directly, rather than being looked up in the key table.
*/
void
bind_key(keyname, funcname, is_macro)
const wchar_t *keyname, *funcname;
{
tkey_t *key = NULL;
function_t *func;
binding_t *binding;
wint_t code;
/* Find the key and the function */
if (wcslen(keyname) > 1) {
if ((key = find_key(keyname)) == NULL) {
errbox(L"Unknown key \"%ls\"", keyname);
return;
}
code = key->ky_code;
} else
code = *keyname;
if (!is_macro) {
if ((func = find_func(funcname)) == NULL) {
errbox(L"Unknown function \"%ls\"", funcname);
return;
}
}
/* Do we already have a binding for this key? */
TTS_TAILQ_FOREACH(binding, &bindings, bi_entries) {
if (binding->bi_code == code) {
if (is_macro) {
binding->bi_func = NULL;
binding->bi_macro = wcsdup(funcname);
} else {
free(binding->bi_macro);
binding->bi_func = func;
}
return;
}
}
/* No, add a new one */
if ((binding = calloc(1, sizeof(*binding))) == NULL)
return;
binding->bi_key = key;
binding->bi_code = code;
if (is_macro)
binding->bi_macro = wcsdup(funcname);
else
binding->bi_func = func;
TTS_TAILQ_INSERT_TAIL(&bindings, binding, bi_entries);
}
/*
* Return the tkey_t for the key called .name, or NULL if such a key doesn't
* exist.
*/
tkey_t *
find_key(name)
const wchar_t *name;
{
size_t i;
for (i = 0; i < sizeof(keys) / sizeof(*keys); i++)
if (wcscmp(name, keys[i].ky_name) == 0)
return &keys[i];
return NULL;
}
void
bind_defaults(void)
{
bind_key(L"?", L"help", 0);
bind_key(L"a", L"add", 0);
bind_key(L"A", L"add-old", 0);
bind_key(L"d", L"delete", 0);
bind_key(L"u", L"undelete", 0);
bind_key(L"q", L"quit", 0);
bind_key(L"<CTRL-C>", L"quit", 0);
bind_key(L"i", L"invoice", 0);
bind_key(L"b", L"billable", 0);
bind_key(L"m", L"mark", 0);
bind_key(L"U", L"unmarkall", 0);
bind_key(L"<SPACE>", L"startstop", 0);
bind_key(L"e", L"edit-desc", 0);
bind_key(L"\\", L"edit-time", 0);
bind_key(L"<TAB>", L"showhide-inv", 0);
bind_key(L"c", L"copy", 0);
bind_key(L"+", L"add-time", 0);
bind_key(L"-", L"sub-time", 0);
bind_key(L"/", L"search", 0);
bind_key(L"$", L"sync", 0);
bind_key(L"<UP>", L"prev", 0);
bind_key(L"<DOWN>", L"next", 0);
bind_key(L":", L"execute", 0);
bind_key(L"M", L"merge", 0);
bind_key(L"r", L"interrupt", 0);
bind_key(L"R", L"interrupt", 0);
}

40
bindings.h Normal file
View file

@ -0,0 +1,40 @@
/*
* 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 "tailq.h"
typedef struct tkey {
wint_t ky_code;
const wchar_t *ky_name;
} tkey_t;
typedef struct binding {
wint_t bi_code;
tkey_t *bi_key;
function_t *bi_func;
wchar_t *bi_macro;
TTS_TAILQ_ENTRY(binding) bi_entries;
} binding_t;
typedef TTS_TAILQ_HEAD(bindlist, binding) binding_list_t;
extern binding_list_t bindings;
void bind_defaults();
tkey_t *find_key(const wchar_t *name);
void bind_key(const wchar_t *key, const wchar_t *func, int is_macro);
#endif /* !TTS_BINDINGS_H */

View file

@ -20,6 +20,7 @@
use warnings; use warnings;
use strict; use strict;
use LWP::Protocol::https;
use LWP::UserAgent; use LWP::UserAgent;
use URI::Escape qw/uri_escape/; use URI::Escape qw/uri_escape/;
use POSIX qw/strftime/; use POSIX qw/strftime/;
@ -89,7 +90,7 @@ while (<INF>) {
if ($res->is_success) { if ($res->is_success) {
my $resp = decode_json($res->content); my $resp = decode_json($res->content);
if (defined($resp->{description})) { if (defined($resp->{description})) {
print "Failed to Bling [$desc]: " . $resp->description . "\n"; print "Failed to Bling [$desc]: " . $resp->{description} . "\n";
} else { } else {
if ($flags eq "-") { if ($flags eq "-") {
$flags = "i"; $flags = "i";

157
commands.c Normal file
View file

@ -0,0 +1,157 @@
/*
* 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 <stdlib.h>
#include "commands.h"
#include "style.h"
#include "bindings.h"
#include "variable.h"
static command_t commands[] = {
{ L"bind", c_bind },
{ L"macro", c_macro },
{ L"style", c_style },
{ L"set", c_set },
{ }
};
command_t *
find_command(name)
const wchar_t *name;
{
command_t *c;
for (c = commands; c->cm_name; c++)
if (wcscmp(name, c->cm_name) == 0)
return c;
return NULL;
}
void
c_style(argc, argv)
size_t argc;
wchar_t **argv;
{
style_t *sy;
wchar_t *last, *tok;
if (argc < 3 || argc > 4) {
cmderr(L"Usage: style <item> <foreground> [background]");
return;
}
if (wcscmp(argv[1], L"header") == 0)
sy = &sy_header;
else if (wcscmp(argv[1], L"status") == 0)
sy = &sy_status;
else if (wcscmp(argv[1], L"entry") == 0)
sy = &sy_entry;
else if (wcscmp(argv[1], L"selected") == 0)
sy = &sy_selected;
else if (wcscmp(argv[1], L"running") == 0)
sy = &sy_running;
else if (wcscmp(argv[1], L"date") == 0)
sy = &sy_date;
else {
cmderr(L"Unknown style item.");
return;
}
style_clear(sy);
for (tok = wcstok(argv[2], L",", &last); tok != NULL;
tok = wcstok(NULL, L",", &last)) {
style_add(sy, tok, argv[3]);
}
apply_styles();
}
void
c_bind(argc, argv)
size_t argc;
wchar_t **argv;
{
if (argc != 3) {
cmderr(L"Usage: bind <key> <function>");
return;
}
bind_key(argv[1], argv[2], 0);
}
void
c_macro(argc, argv)
size_t argc;
wchar_t **argv;
{
if (argc != 3) {
cmderr(L"Usage: macro <key> <def>");
return;
}
bind_key(argv[1], argv[2], 1);
}
void
c_set(argc, argv)
size_t argc;
wchar_t **argv;
{
variable_t *var;
int val;
wchar_t *p = NULL;
if (argc != 3) {
cmderr(L"Usage: set <variable> <value>");
return;
}
if ((var = find_variable(argv[1])) == NULL) {
cmderr(L"Unknown variable \"%ls\".", argv[1]);
return;
}
switch (var->va_type) {
case VTYPE_BOOL:
if (wcscmp(argv[2], L"true") == 0 ||
wcscmp(argv[2], L"yes") == 0 ||
wcscmp(argv[2], L"on") == 0 ||
wcscmp(argv[2], L"1") == 0) {
val = 1;
} else if (wcscmp(argv[2], L"false") == 0 ||
wcscmp(argv[2], L"no") == 0 ||
wcscmp(argv[2], L"off") == 0 ||
wcscmp(argv[2], L"0") == 0) {
val = 0;
} else {
cmderr(L"Invalid value for boolean: \"%ls\".", argv[2]);
return;
}
*(int *)var->va_addr = val;
break;
case VTYPE_STRING:
free(*(wchar_t **)var->va_addr);
*(wchar_t **)var->va_addr = wcsdup(argv[2]);
break;
case VTYPE_INT:
val = wcstol(argv[2], &p, 0);
if (!*argv[2] || *p) {
cmderr(L"Invalid number \"%ls\"", argv[2]);
break;
}
*(int *)var->va_addr = val;
break;
}
}

33
commands.h Normal file
View file

@ -0,0 +1,33 @@
/*
* 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 <stdarg.h>
#include "wide.h"
typedef struct command {
const wchar_t *cm_name;
void (*cm_hdl) (size_t, wchar_t **);
} command_t;
command_t *find_command(const wchar_t *);
void c_bind (size_t, wchar_t **);
void c_style (size_t, wchar_t **);
void c_set (size_t, wchar_t **);
void c_macro (size_t, wchar_t **);
void cmderr (const wchar_t *, ...);
void vcmderr (const wchar_t *, va_list);
#endif /* !TTS_COMMANDS_H */

1558
config.guess vendored Executable file

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,9 @@
/* Define to 1 if you have the `IORegisterForSystemPower' function. */ /* Define to 1 if you have the `IORegisterForSystemPower' function. */
#undef HAVE_IOREGISTERFORSYSTEMPOWER #undef HAVE_IOREGISTERFORSYSTEMPOWER
/* Define to 1 if you have the `m' library (-lm). */
#undef HAVE_LIBM
/* Define to 1 if you have the <memory.h> header file. */ /* Define to 1 if you have the <memory.h> header file. */
#undef HAVE_MEMORY_H #undef HAVE_MEMORY_H
@ -69,6 +72,12 @@
/* Define to 1 if you have the `use_default_colors' function. */ /* Define to 1 if you have the `use_default_colors' function. */
#undef HAVE_USE_DEFAULT_COLORS #undef HAVE_USE_DEFAULT_COLORS
/* Define to 1 if you have the `wcslcat' function. */
#undef HAVE_WCSLCAT
/* Define to 1 if you have the `wcslcpy' function. */
#undef HAVE_WCSLCPY
/* Define to the address where bug reports for this package should be sent. */ /* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT #undef PACKAGE_BUGREPORT

1788
config.sub vendored Executable file

File diff suppressed because it is too large Load diff

229
configure vendored
View file

@ -1,8 +1,8 @@
#! /bin/sh #! /bin/sh
# Guess values for system-dependent variables and create Makefiles. # Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for RT/TTS T.83.4. # Generated by GNU Autoconf 2.69 for TTS D.84.1.
# #
# Report bugs to <felicity@loreley.flyingparchment.org.uk>. # Report bugs to <ft@le-fay.org>.
# #
# #
# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@ -266,11 +266,11 @@ fi
$as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
$as_echo "$0: be upgraded to zsh 4.3.4 or later." $as_echo "$0: be upgraded to zsh 4.3.4 or later."
else else
$as_echo "$0: Please tell bug-autoconf@gnu.org and $as_echo "$0: Please tell bug-autoconf@gnu.org and ft@le-fay.org
$0: felicity@loreley.flyingparchment.org.uk about your $0: about your system, including any error possibly output
$0: system, including any error possibly output before this $0: before this message. Then install a modern shell, or
$0: message. Then install a modern shell, or manually run $0: manually run the script under such a shell if you do
$0: the script under such a shell if you do have one." $0: have one."
fi fi
exit 1 exit 1
fi fi
@ -578,11 +578,11 @@ MFLAGS=
MAKEFLAGS= MAKEFLAGS=
# Identity of this package. # Identity of this package.
PACKAGE_NAME='RT/TTS' PACKAGE_NAME='TTS'
PACKAGE_TARNAME='rt-tts' PACKAGE_TARNAME='tts'
PACKAGE_VERSION='T.83.4' PACKAGE_VERSION='D.84.1'
PACKAGE_STRING='RT/TTS T.83.4' PACKAGE_STRING='TTS D.84.1'
PACKAGE_BUGREPORT='felicity@loreley.flyingparchment.org.uk' PACKAGE_BUGREPORT='ft@le-fay.org'
PACKAGE_URL='' PACKAGE_URL=''
ac_unique_file="tts.c" ac_unique_file="tts.c"
@ -638,6 +638,14 @@ CPPFLAGS
LDFLAGS LDFLAGS
CFLAGS CFLAGS
CC CC
host_os
host_vendor
host_cpu
host
build_os
build_vendor
build_cpu
build
target_alias target_alias
host_alias host_alias
build_alias build_alias
@ -1232,7 +1240,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing. # Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh. # This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF cat <<_ACEOF
\`configure' configures RT/TTS T.83.4 to adapt to many kinds of systems. \`configure' configures TTS D.84.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]... Usage: $0 [OPTION]... [VAR=VALUE]...
@ -1280,7 +1288,7 @@ Fine tuning of the installation directories:
--infodir=DIR info documentation [DATAROOTDIR/info] --infodir=DIR info documentation [DATAROOTDIR/info]
--localedir=DIR locale-dependent data [DATAROOTDIR/locale] --localedir=DIR locale-dependent data [DATAROOTDIR/locale]
--mandir=DIR man documentation [DATAROOTDIR/man] --mandir=DIR man documentation [DATAROOTDIR/man]
--docdir=DIR documentation root [DATAROOTDIR/doc/rt-tts] --docdir=DIR documentation root [DATAROOTDIR/doc/tts]
--htmldir=DIR html documentation [DOCDIR] --htmldir=DIR html documentation [DOCDIR]
--dvidir=DIR dvi documentation [DOCDIR] --dvidir=DIR dvi documentation [DOCDIR]
--pdfdir=DIR pdf documentation [DOCDIR] --pdfdir=DIR pdf documentation [DOCDIR]
@ -1288,12 +1296,16 @@ Fine tuning of the installation directories:
_ACEOF _ACEOF
cat <<\_ACEOF cat <<\_ACEOF
System types:
--build=BUILD configure for building on BUILD [guessed]
--host=HOST cross-compile to build programs to run on HOST [BUILD]
_ACEOF _ACEOF
fi fi
if test -n "$ac_init_help"; then if test -n "$ac_init_help"; then
case $ac_init_help in case $ac_init_help in
short | recursive ) echo "Configuration of RT/TTS T.83.4:";; short | recursive ) echo "Configuration of TTS D.84.1:";;
esac esac
cat <<\_ACEOF cat <<\_ACEOF
@ -1317,7 +1329,7 @@ Some influential environment variables:
Use these variables to override the choices made by `configure' or to help Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations. it to find libraries and programs with nonstandard names/locations.
Report bugs to <felicity@loreley.flyingparchment.org.uk>. Report bugs to <ft@le-fay.org>.
_ACEOF _ACEOF
ac_status=$? ac_status=$?
fi fi
@ -1380,7 +1392,7 @@ fi
test -n "$ac_init_help" && exit $ac_status test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then if $ac_init_version; then
cat <<\_ACEOF cat <<\_ACEOF
RT/TTS configure T.83.4 TTS configure D.84.1
generated by GNU Autoconf 2.69 generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc. Copyright (C) 2012 Free Software Foundation, Inc.
@ -1652,9 +1664,9 @@ $as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} $as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
( $as_echo "## ------------------------------------------------------ ## ( $as_echo "## ---------------------------- ##
## Report this to felicity@loreley.flyingparchment.org.uk ## ## Report this to ft@le-fay.org ##
## ------------------------------------------------------ ##" ## ---------------------------- ##"
) | sed "s/^/$as_me: WARNING: /" >&2 ) | sed "s/^/$as_me: WARNING: /" >&2
;; ;;
esac esac
@ -1749,7 +1761,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake. running configure, to aid debugging if configure makes a mistake.
It was created by RT/TTS $as_me T.83.4, which was It was created by TTS $as_me D.84.1, which was
generated by GNU Autoconf 2.69. Invocation command line was generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@ $ $0 $@
@ -2101,6 +2113,122 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
ac_config_headers="$ac_config_headers config.h" ac_config_headers="$ac_config_headers config.h"
ac_aux_dir=
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
if test -f "$ac_dir/install-sh"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/install-sh -c"
break
elif test -f "$ac_dir/install.sh"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/install.sh -c"
break
elif test -f "$ac_dir/shtool"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/shtool install -c"
break
fi
done
if test -z "$ac_aux_dir"; then
as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
fi
# These three variables are undocumented and unsupported,
# and are intended to be withdrawn in a future Autoconf release.
# They can cause serious problems if a builder's source tree is in a directory
# whose full name contains unusual characters.
ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var.
ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var.
ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
# Make sure we can run config.sub.
$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5
$as_echo_n "checking build system type... " >&6; }
if ${ac_cv_build+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_build_alias=$build_alias
test "x$ac_build_alias" = x &&
ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"`
test "x$ac_build_alias" = x &&
as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5
ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` ||
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5
$as_echo "$ac_cv_build" >&6; }
case $ac_cv_build in
*-*-*) ;;
*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;;
esac
build=$ac_cv_build
ac_save_IFS=$IFS; IFS='-'
set x $ac_cv_build
shift
build_cpu=$1
build_vendor=$2
shift; shift
# Remember, the first character of IFS is used to create $*,
# except with old shells:
build_os=$*
IFS=$ac_save_IFS
case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5
$as_echo_n "checking host system type... " >&6; }
if ${ac_cv_host+:} false; then :
$as_echo_n "(cached) " >&6
else
if test "x$host_alias" = x; then
ac_cv_host=$ac_cv_build
else
ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` ||
as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5
$as_echo "$ac_cv_host" >&6; }
case $ac_cv_host in
*-*-*) ;;
*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;;
esac
host=$ac_cv_host
ac_save_IFS=$IFS; IFS='-'
set x $ac_cv_host
shift
host_cpu=$1
host_vendor=$2
shift; shift
# Remember, the first character of IFS is used to create $*,
# except with old shells:
host_os=$*
IFS=$ac_save_IFS
case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
case $host_os in
darwin*)
CPPFLAGS="$CPPFLAGS -D_DARWIN_USE_64_BIT_INODE=1"
;;
linux*)
CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64"
;;
netbsd*)
CPPFLAGS="$CPPFLAGS -D_NETBSD_SOURCE"
;;
solaris*)
CPPFLAGS="-D__EXTENSIONS__ -D_FILE_OFFSET_BITS=64"
;;
esac
ac_ext=c ac_ext=c
ac_cpp='$CPP $CPPFLAGS' ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@ -2890,35 +3018,6 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu ac_compiler_gnu=$ac_cv_c_compiler_gnu
ac_aux_dir=
for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
if test -f "$ac_dir/install-sh"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/install-sh -c"
break
elif test -f "$ac_dir/install.sh"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/install.sh -c"
break
elif test -f "$ac_dir/shtool"; then
ac_aux_dir=$ac_dir
ac_install_sh="$ac_aux_dir/shtool install -c"
break
fi
done
if test -z "$ac_aux_dir"; then
as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
fi
# These three variables are undocumented and unsupported,
# and are intended to be withdrawn in a future Autoconf release.
# They can cause serious problems if a builder's source tree is in a directory
# whose full name contains unusual characters.
ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var.
ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var.
ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
# Find a good install program. We prefer a C program (faster), # Find a good install program. We prefer a C program (faster),
# so one script is as good as another. But avoid the broken or # so one script is as good as another. But avoid the broken or
# incompatible versions: # incompatible versions:
@ -3120,7 +3219,7 @@ main ()
chtype a = A_BOLD; chtype a = A_BOLD;
int b = KEY_LEFT; int b = KEY_LEFT;
chtype c = COLOR_PAIR(1) & A_COLOR; chtype c = COLOR_PAIR(1) & A_COLOR;
attr_t d = WA_NORMAL; attr_t d = WA_BOLD;
cchar_t e; cchar_t e;
wint_t f; wint_t f;
int g = getattrs(stdscr); int g = getattrs(stdscr);
@ -3184,7 +3283,7 @@ main ()
chtype a = A_BOLD; chtype a = A_BOLD;
int b = KEY_LEFT; int b = KEY_LEFT;
chtype c = COLOR_PAIR(1) & A_COLOR; chtype c = COLOR_PAIR(1) & A_COLOR;
attr_t d = WA_NORMAL; attr_t d = WA_BOLD;
cchar_t e; cchar_t e;
wint_t f; wint_t f;
int g = getattrs(stdscr); int g = getattrs(stdscr);
@ -3248,7 +3347,7 @@ main ()
chtype a = A_BOLD; chtype a = A_BOLD;
int b = KEY_LEFT; int b = KEY_LEFT;
chtype c = COLOR_PAIR(1) & A_COLOR; chtype c = COLOR_PAIR(1) & A_COLOR;
attr_t d = WA_NORMAL; attr_t d = WA_BOLD;
cchar_t e; cchar_t e;
wint_t f; wint_t f;
int g = getattrs(stdscr); int g = getattrs(stdscr);
@ -3612,7 +3711,7 @@ main ()
chtype a = A_BOLD; chtype a = A_BOLD;
int b = KEY_LEFT; int b = KEY_LEFT;
chtype c = COLOR_PAIR(1) & A_COLOR; chtype c = COLOR_PAIR(1) & A_COLOR;
attr_t d = WA_NORMAL; attr_t d = WA_BOLD;
cchar_t e; cchar_t e;
wint_t f; wint_t f;
initscr(); initscr();
@ -3771,14 +3870,20 @@ fi
LIBS=$ax_saved_LIBS LIBS=$ax_saved_LIBS
if test "x$ax_cv_curses" != xyes; then :
as_fn_error $? "XSI/SVR4 curses is required to compile TTS" "$LINENO" 5
fi
oLIBS="$LIBS" oLIBS="$LIBS"
LIBS="$LIBS $CURSES_LIB" LIBS="$LIBS $CURSES_LIB"
for ac_func in use_default_colors for ac_func in use_default_colors wcslcpy wcslcat
do : do :
ac_fn_c_check_func "$LINENO" "use_default_colors" "ac_cv_func_use_default_colors" as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
if test "x$ac_cv_func_use_default_colors" = xyes; then : ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
if eval test \"x\$"$as_ac_var"\" = x"yes"; then :
cat >>confdefs.h <<_ACEOF cat >>confdefs.h <<_ACEOF
#define HAVE_USE_DEFAULT_COLORS 1 #define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1
_ACEOF _ACEOF
fi fi
@ -4243,7 +4348,7 @@ fi
oLDFLAGS="$LDFLAGS" oLDFLAGS="$LDFLAGS"
LDFLAGS="$LDFLAGS -framework CoreFoundation -framework IOKit" LDFLAGS="$LDFLAGS -framework IOKit"
for ac_func in IORegisterForSystemPower for ac_func in IORegisterForSystemPower
do : do :
ac_fn_c_check_func "$LINENO" "IORegisterForSystemPower" "ac_cv_func_IORegisterForSystemPower" ac_fn_c_check_func "$LINENO" "IORegisterForSystemPower" "ac_cv_func_IORegisterForSystemPower"
@ -4766,7 +4871,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their # report actual input values of CONFIG_FILES etc. instead of their
# values after options handling. # values after options handling.
ac_log=" ac_log="
This file was extended by RT/TTS $as_me T.83.4, which was This file was extended by TTS $as_me D.84.1, which was
generated by GNU Autoconf 2.69. Invocation command line was generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES CONFIG_FILES = $CONFIG_FILES
@ -4822,13 +4927,13 @@ $config_files
Configuration headers: Configuration headers:
$config_headers $config_headers
Report bugs to <felicity@loreley.flyingparchment.org.uk>." Report bugs to <ft@le-fay.org>."
_ACEOF _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\ ac_cs_version="\\
RT/TTS config.status T.83.4 TTS config.status D.84.1
configured by $0, generated by GNU Autoconf 2.69, configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\" with options \\"\$ac_cs_config\\"

View file

@ -1,15 +1,35 @@
AC_PREREQ([2.69]) AC_PREREQ([2.69])
AC_INIT([RT/TTS], [T.83.4], [felicity@loreley.flyingparchment.org.uk]) AC_INIT([TTS], [D.84.1], [ft@le-fay.org])
AC_CONFIG_SRCDIR([tts.c]) AC_CONFIG_SRCDIR([tts.c])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
AC_CANONICAL_HOST
case $host_os in
darwin*)
CPPFLAGS="$CPPFLAGS -D_DARWIN_USE_64_BIT_INODE=1"
;;
linux*)
CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64"
;;
netbsd*)
CPPFLAGS="$CPPFLAGS -D_NETBSD_SOURCE"
;;
solaris*)
CPPFLAGS="-D__EXTENSIONS__ -D_FILE_OFFSET_BITS=64"
;;
esac
AC_PROG_CC AC_PROG_CC
AC_PROG_INSTALL AC_PROG_INSTALL
AX_WITH_CURSES AX_WITH_CURSES
AS_IF([test "x$ax_cv_curses" != xyes], [
AC_ERROR([XSI/SVR4 curses is required to compile TTS])
])
oLIBS="$LIBS" oLIBS="$LIBS"
LIBS="$LIBS $CURSES_LIB" LIBS="$LIBS $CURSES_LIB"
AC_CHECK_FUNCS([use_default_colors]) AC_CHECK_FUNCS([use_default_colors wcslcpy wcslcat])
LIBS="$oLIBS" LIBS="$oLIBS"
AC_CHECK_HEADERS([IOKit/pwr_mgt/IOPMLib.h]) AC_CHECK_HEADERS([IOKit/pwr_mgt/IOPMLib.h])
@ -17,7 +37,7 @@ AC_CHECK_HEADERS([IOKit/pwr_mgt/IOPMLib.h])
AC_CHECK_LIB(m, round) AC_CHECK_LIB(m, round)
oLDFLAGS="$LDFLAGS" oLDFLAGS="$LDFLAGS"
LDFLAGS="$LDFLAGS -framework CoreFoundation -framework IOKit" LDFLAGS="$LDFLAGS -framework IOKit"
AC_CHECK_FUNCS([IORegisterForSystemPower], [], [LDFLAGS="$oLDFLAGS"]) AC_CHECK_FUNCS([IORegisterForSystemPower], [], [LDFLAGS="$oLDFLAGS"])
AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([Makefile])

124
entry.c Normal file
View file

@ -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 <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include "entry.h"
#include "wide.h"
#include "tts.h"
entry_list entries = TTS_TAILQ_HEAD_INITIALIZER(entries);
entry_t *running;
entry_t *
entry_new(desc)
const wchar_t *desc;
{
entry_t *en;
if ((en = calloc(1, sizeof(*en))) == NULL)
return NULL;
if (auto_nonbillable && wcsstr(desc, auto_nonbillable))
en->en_flags.efl_nonbillable = 1;
TTS_TAILQ_INSERT_HEAD(&entries, en, en_entries);
en->en_desc = wcsdup(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;
TTS_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;
}

59
entry.h Normal file
View file

@ -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 <time.h>
#include "tailq.h"
#include "wide.h"
typedef struct entry {
wchar_t *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;
TTS_TAILQ_ENTRY(entry) en_entries;
} entry_t;
typedef TTS_TAILQ_HEAD(entrylist, entry) entry_list;
extern entry_list entries;
extern entry_t *running;
entry_t *entry_new (const wchar_t *);
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 */

761
functions.c Normal file
View file

@ -0,0 +1,761 @@
/*
* 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 <stdlib.h>
#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[] = {
{ L"help", khelp, L"display help screen" },
{ L"add", kadd, L"add a new entry and start the timer" },
{ L"add-old", kaddold, L"add a new entry and specify its duration" },
{ L"delete", kmarkdel, L"delete the current entry" },
{ L"undelete", kundel, L"undelete the current entry" },
{ L"quit", kquit, L"exit TTS" },
{ L"invoice", kinvoiced, L"toggle current entry as invoiced" },
{ L"billable", kbillable, L"toggle current entry as billable" },
{ L"mark", kmark, L"mark the current entry" },
{ L"unmarkall", kunmarkall, L"unmark all entries" },
{ L"startstop", ktoggle, L"start or stop the timer" },
{ L"edit-desc", keddesc, L"edit the current entry's description" },
{ L"edit-time", kedtime, L"edit the current entry's duration" },
{ L"showhide-inv", ktoggleinv, L"show or hide invoiced entries" },
{ L"copy", kcopy, L"copy the current entry's description to a new entry" },
{ L"add-time", kaddtime, L"add time to the current entry" },
{ L"sub-time", kdeltime, L"subtract time from the current entry" },
{ L"search", ksearch, L"search for an entry by name" },
{ L"sync", ksync, L"purge all deleted entries" },
{ L"prev", kup, L"move to the previous entry" },
{ L"next", kdown, L"move to the next entry" },
{ L"execute", kexec, L"execute a configuration command" },
{ L"merge", kmerge, L"merge marked entries into current entry" },
{ L"interrupt", kint, L"split current entry into new entry"},
{ }
};
static int
valid_description(d)
const wchar_t *d;
{
if (!d)
return 0;
while (iswspace(*d))
d++;
if (!*d)
return 0;
return 1;
}
void
kquit()
{
entry_t *en;
int ndel = 0;
TTS_TAILQ_FOREACH(en, &entries, en_entries) {
if (en->en_flags.efl_deleted)
ndel++;
}
if (ndel) {
wchar_t s[128];
swprintf(s, wsizeof(s), L"Purge %d deleted entries?", ndel);
if (yesno(s)) {
ksync();
}
}
doexit = 1;
}
void
kadd()
{
wchar_t *name;
entry_t *en;
name = prompt(L"Description:", NULL, NULL);
if (!valid_description(name)) {
free(name);
return;
}
en = entry_new(name);
entry_start(en);
curent = en;
save();
}
void
kaddold()
{
wchar_t *name;
entry_t *en;
name = prompt(L"Description:", NULL, NULL);
if (!valid_description(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;
TTS_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(L"No entries to delete.");
return;
}
curent->en_flags.efl_deleted = 1;
if (delete_advance)
cursadvance();
}
void
ksync()
{
entry_t *en, *ten;
TTS_TAILQ_FOREACH_SAFE(en, &entries, en_entries, ten) {
if (!en->en_flags.efl_deleted)
continue;
if (en == curent)
curent = NULL;
TTS_TAILQ_REMOVE(&entries, en, en_entries);
entry_free(en);
}
if (curent == NULL)
curent = TTS_TAILQ_FIRST(&entries);
save();
}
void
kup()
{
entry_t *prev = curent;
if (!curent)
return;
do {
if ((prev = TTS_TAILQ_PREV(prev, entrylist, en_entries)) == NULL)
break;
} while (!showinv && prev->en_flags.efl_invoiced);
if (prev == NULL) {
drawstatus(L"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 = TTS_TAILQ_NEXT(next, en_entries)) == NULL)
break;
} while (!showinv && next->en_flags.efl_invoiced);
if (next == NULL) {
drawstatus(L"Already at last entry.");
return;
}
curent = next;
if (!curent->en_flags.efl_visible)
pagestart++;
}
void
kinvoiced()
{
entry_t *en;
int anymarked = 0;
TTS_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(L"No entry selected.");
return;
}
curent->en_flags.efl_invoiced = !curent->en_flags.efl_invoiced;
save();
en = curent;
if (showinv) {
if (TTS_TAILQ_NEXT(curent, en_entries) != NULL)
curent = TTS_TAILQ_NEXT(curent, en_entries);
return;
}
/*
* Try to find the next uninvoiced request to move the cursor to.
*/
for (;;) {
if ((curent = TTS_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 = TTS_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;
TTS_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(L"No entry selected.");
return;
}
curent->en_flags.efl_nonbillable = !curent->en_flags.efl_nonbillable;
save();
if (bill_advance)
cursadvance();
}
void
keddesc()
{
wchar_t *new;
if (!curent) {
drawstatus(L"No entry selected.");
return;
}
if ((new = prompt(L"Description:", curent->en_desc, NULL)) == NULL)
return;
free(curent->en_desc);
curent->en_desc = new;
save();
}
void
kedtime()
{
time_t n;
if (!curent) {
drawstatus(L"No entry selected.");
return;
}
n = curent->en_secs;
if (curent->en_started)
n += time(NULL) - curent->en_started;
if ((n = prduration(L"Duration:", n)) == (time_t) -1) {
drawstatus(L"Invalid duration.");
return;
}
curent->en_secs = n;
if (curent->en_started)
time(&curent->en_started);
save();
}
void
ktoggleinv()
{
entry_t *en = curent;
showinv = !showinv;
drawstatus(L"%ls invoiced entries.", showinv ? L"Showing" : L"Hiding");
if (curent && !curent->en_flags.efl_invoiced)
return;
if (!curent) {
curent = TTS_TAILQ_FIRST(&entries);
return;
}
/*
* Try to find the next uninvoiced request to move the cursor to.
*/
for (;;) {
if ((curent = TTS_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 = TTS_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(L"No entry selected.");
return;
}
en = entry_new(curent->en_desc);
curent = en;
entry_start(en);
save();
}
void
kaddtime()
{
time_t secs;
if (!curent) {
drawstatus(L"No entry selected.");
return;
}
if ((secs = prduration(L"Time to add:", 0)) == (time_t) -1) {
drawstatus(L"Invalid time format.");
return;
}
curent->en_secs += secs;
save();
}
void
kdeltime()
{
time_t secs;
if (!curent) {
drawstatus(L"No entry selected.");
return;
}
if ((secs = prduration(L"Time to subtract:", 0)) == (time_t) -1) {
drawstatus(L"Invalid time format.");
return;
}
entry_account(curent);
if (curent->en_secs - secs < 0) {
drawstatus(L"Remaining time cannot be less than zero.");
return;
}
curent->en_secs -= secs;
save();
}
void
kmerge()
{
entry_t *en, *ten;
int nmarked = 0;
wchar_t pr[128];
wchar_t *ct;
int s = 0;
if (!curent) {
drawstatus(L"No entry selected.");
return;
}
/*
* Count number of marked entries and the summed time.
*/
TTS_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(L"No marked entries.");
return;
}
ct = maketime(s, time_format);
swprintf(pr, wsizeof(pr), L"Merge %d marked entries [%ls] into current entry",
nmarked, ct);
free(ct);
if (!yesno(pr))
return;
TTS_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;
TTS_TAILQ_REMOVE(&entries, en, en_entries);
entry_free(en);
}
save();
}
void
khelp()
{
WINDOW *hwin;
size_t nhelp = 0;
wchar_t **help;
#define HTITLE L" TTS keys "
size_t width = 0;
size_t i;
wint_t c;
binding_t *bi;
/* Count the number of bindings */
TTS_TAILQ_FOREACH(bi, &bindings, bi_entries)
nhelp++;
help = calloc(nhelp, sizeof(const wchar_t *));
i = 0;
TTS_TAILQ_FOREACH(bi, &bindings, bi_entries) {
wchar_t s[128], t[16];
if (bi->bi_key)
swprintf(t, wsizeof(t), L"%ls", bi->bi_key->ky_name);
else
swprintf(t, wsizeof(t), L"%lc", bi->bi_code);
if (bi->bi_macro) {
wchar_t *esc = escstr(bi->bi_macro);
swprintf(s, wsizeof(s), L"%-10ls execute macro: %ls",
t, esc);
free(esc);
} else
swprintf(s, wsizeof(s), L"%-10ls %ls (%ls)",
t, bi->bi_func->fn_desc, bi->bi_func->fn_name);
help[i] = wcsdup(s);
i++;
}
if (nhelp > (LINES - 6))
nhelp = LINES - 6;
for (i = 0; i < nhelp; i++)
if (wcslen(help[i]) > width)
width = wcslen(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);
waddwstr(hwin, HTITLE);
wattroff(hwin, A_REVERSE | A_BOLD);
for (i = 0; i < nhelp; i++) {
wmove(hwin, i + 2, 2);
waddwstr(hwin, help[i]);
}
wrefresh(hwin);
while (wget_wch(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(L"No entry selected.");
return;
}
curent->en_flags.efl_marked = !curent->en_flags.efl_marked;
if (mark_advance)
cursadvance();
}
void
kunmarkall()
{
entry_t *en;
TTS_TAILQ_FOREACH(en, &entries, en_entries)
en->en_flags.efl_marked = 0;
}
void
kint()
{
time_t duration;
entry_t *en;
wchar_t *name;
if (!itime) {
if (!running) {
drawstatus(L"No running entry.");
return;
}
itime = time(NULL);
return;
}
if (!running) {
drawstatus(L"No running entry.");
return;
}
name = prompt(L"Description (<ENTER> to abandon interrupt):", NULL, NULL);
if (!valid_description(name)) {
itime = 0;
free(name);
return;
}
duration = time(NULL) - itime;
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_t *lastsearch;
wchar_t *term;
entry_t *start, *cur;
if (!curent) {
drawstatus(L"No entries.");
return;
}
if ((term = prompt(L"Search:", NULL, NULL)) == NULL)
return;
if (!*term) {
free(term);
if (!lastsearch)
return;
term = lastsearch;
} else {
free(lastsearch);
lastsearch = term;
}
cur = start = curent;
for (;;) {
cur = TTS_TAILQ_NEXT(cur, en_entries);
if (cur == NULL) {
drawstatus(L"Search reached last entry, continuing from top.");
cur = TTS_TAILQ_FIRST(&entries);
}
if (cur == start) {
drawstatus(L"No matches.");
break;
}
if (wcsstr(cur->en_desc, term)) {
curent = cur;
if (!showinv && cur->en_flags.efl_invoiced)
showinv = 1;
return;
}
}
}
void
kexec()
{
wchar_t *cmd;
wchar_t **args;
command_t *cmds;
size_t nargs;
if ((cmd = prompt(L":", 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(L"Unknown command.");
tokfree(&args);
return;
}
cmds->cm_hdl(nargs, args);
tokfree(&args);
}
/*
* Return the function_t for the function called .name, or NULL if such a
* function doesn't exist.
*/
function_t *
find_func(name)
const wchar_t *name;
{
function_t *f;
for (f = funcs; f->fn_name; f++)
if (wcscmp(name, f->fn_name) == 0)
return f;
return NULL;
}

51
functions.h Normal file
View file

@ -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_t *fn_name;
void (*fn_hdl) (void);
const wchar_t *fn_desc;
} function_t;
extern function_t funcs[];
function_t *find_func(const wchar_t *name);
#endif /* !TTS_FUNCTIONS_H */

611
queue.h
View file

@ -1,611 +0,0 @@
/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* FreeBSD: release/9.0.0/sys/sys/queue.h 221843 2011-05-13 15:49:23Z mdf
*/
#ifndef TTS_QUEUE_H
#define TTS_QUEUE_H
/*
* This file defines four types of data structures: singly-linked lists,
* singly-linked tail queues, lists and tail queues.
*
* A singly-linked list is headed by a single forward pointer. The elements
* are singly linked for minimum space and pointer manipulation overhead at
* the expense of O(n) removal for arbitrary elements. New elements can be
* added to the list after an existing element or at the head of the list.
* Elements being removed from the head of the list should use the explicit
* macro for this purpose for optimum efficiency. A singly-linked list may
* only be traversed in the forward direction. Singly-linked lists are ideal
* for applications with large datasets and few or no removals or for
* implementing a LIFO queue.
*
* A singly-linked tail queue is headed by a pair of pointers, one to the
* head of the list and the other to the tail of the list. The elements are
* singly linked for minimum space and pointer manipulation overhead at the
* expense of O(n) removal for arbitrary elements. New elements can be added
* to the list after an existing element, at the head of the list, or at the
* end of the list. Elements being removed from the head of the tail queue
* should use the explicit macro for this purpose for optimum efficiency.
* A singly-linked tail queue may only be traversed in the forward direction.
* Singly-linked tail queues are ideal for applications with large datasets
* and few or no removals or for implementing a FIFO queue.
*
* A list is headed by a single forward pointer (or an array of forward
* pointers for a hash table header). The elements are doubly linked
* so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before
* or after an existing element or at the head of the list. A list
* may only be traversed in the forward direction.
*
* A tail queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or
* after an existing element, at the head of the list, or at the end of
* the list. A tail queue may be traversed in either direction.
*
* For details on the use of these macros, see the queue(3) manual page.
*
*
* SLIST LIST STAILQ TAILQ
* _HEAD + + + +
* _HEAD_INITIALIZER + + + +
* _ENTRY + + + +
* _INIT + + + +
* _EMPTY + + + +
* _FIRST + + + +
* _NEXT + + + +
* _PREV - - - +
* _LAST - - + +
* _FOREACH + + + +
* _FOREACH_SAFE + + + +
* _FOREACH_REVERSE - - - +
* _FOREACH_REVERSE_SAFE - - - +
* _INSERT_HEAD + + + +
* _INSERT_BEFORE - + - +
* _INSERT_AFTER + + + +
* _INSERT_TAIL - - + +
* _CONCAT - - + +
* _REMOVE_AFTER + - + -
* _REMOVE_HEAD + - + -
* _REMOVE + + + +
* _SWAP + + + +
*
*/
#ifdef QUEUE_MACRO_DEBUG
/* Store the last 2 places the queue element or head was altered */
struct qm_trace {
char * lastfile;
int lastline;
char * prevfile;
int prevline;
};
#define TRACEBUF struct qm_trace trace;
#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
#define QMD_SAVELINK(name, link) void **name = (void *)&(link)
#define QMD_TRACE_HEAD(head) do { \
(head)->trace.prevline = (head)->trace.lastline; \
(head)->trace.prevfile = (head)->trace.lastfile; \
(head)->trace.lastline = __LINE__; \
(head)->trace.lastfile = __FILE__; \
} while (0)
#define QMD_TRACE_ELEM(elem) do { \
(elem)->trace.prevline = (elem)->trace.lastline; \
(elem)->trace.prevfile = (elem)->trace.lastfile; \
(elem)->trace.lastline = __LINE__; \
(elem)->trace.lastfile = __FILE__; \
} while (0)
#else
#define QMD_TRACE_ELEM(elem)
#define QMD_TRACE_HEAD(head)
#define QMD_SAVELINK(name, link)
#define TRACEBUF
#define TRASHIT(x)
#endif /* QUEUE_MACRO_DEBUG */
/*
* Singly-linked List declarations.
*/
#define SLIST_HEAD(name, type) \
struct name { \
struct type *slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type) \
struct { \
struct type *sle_next; /* next element */ \
}
/*
* Singly-linked List functions.
*/
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_FOREACH(var, head, field) \
for ((var) = SLIST_FIRST((head)); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = SLIST_FIRST((head)); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
for ((varp) = &SLIST_FIRST((head)); \
((var) = *(varp)) != NULL; \
(varp) = &SLIST_NEXT((var), field))
#define SLIST_INIT(head) do { \
SLIST_FIRST((head)) = NULL; \
} while (0)
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_INSERT_HEAD(head, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.sle_next); \
if (SLIST_FIRST((head)) == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = SLIST_FIRST((head)); \
while (SLIST_NEXT(curelm, field) != (elm)) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_REMOVE_AFTER(curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)
#define SLIST_REMOVE_AFTER(elm, field) do { \
SLIST_NEXT(elm, field) = \
SLIST_NEXT(SLIST_NEXT(elm, field), field); \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
} while (0)
#define SLIST_SWAP(head1, head2, type) do { \
struct type *swap_first = SLIST_FIRST(head1); \
SLIST_FIRST(head1) = SLIST_FIRST(head2); \
SLIST_FIRST(head2) = swap_first; \
} while (0)
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type) \
struct name { \
struct type *stqh_first;/* first element */ \
struct type **stqh_last;/* addr of last next element */ \
}
#define STAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).stqh_first }
#define STAILQ_ENTRY(type) \
struct { \
struct type *stqe_next; /* next element */ \
}
/*
* Singly-linked Tail queue functions.
*/
#define STAILQ_CONCAT(head1, head2) do { \
if (!STAILQ_EMPTY((head2))) { \
*(head1)->stqh_last = (head2)->stqh_first; \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_INIT((head2)); \
} \
} while (0)
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = STAILQ_FIRST((head)); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_NEXT((tqelm), field) = (elm); \
} while (0)
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_FIRST((head)) = (elm); \
} while (0)
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY((head)) ? \
NULL : \
((struct type *)(void *) \
((char *)((head)->stqh_last) - __offsetof(struct type, field))))
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_REMOVE(head, elm, type, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = STAILQ_FIRST((head)); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
STAILQ_REMOVE_AFTER(head, curelm, field); \
} \
TRASHIT(*oldnext); \
} while (0)
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
if ((STAILQ_NEXT(elm, field) = \
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_SWAP(head1, head2, type) do { \
struct type *swap_first = STAILQ_FIRST(head1); \
struct type **swap_last = (head1)->stqh_last; \
STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_FIRST(head2) = swap_first; \
(head2)->stqh_last = swap_last; \
if (STAILQ_EMPTY(head1)) \
(head1)->stqh_last = &STAILQ_FIRST(head1); \
if (STAILQ_EMPTY(head2)) \
(head2)->stqh_last = &STAILQ_FIRST(head2); \
} while (0)
/*
* List declarations.
*/
#define LIST_HEAD(name, type) \
struct name { \
struct type *lh_first; /* first element */ \
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
#define LIST_ENTRY(type) \
struct { \
struct type *le_next; /* next element */ \
struct type **le_prev; /* address of previous next element */ \
}
/*
* List functions.
*/
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_FOREACH(var, head, field) \
for ((var) = LIST_FIRST((head)); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = LIST_FIRST((head)); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_INIT(head) do { \
LIST_FIRST((head)) = NULL; \
} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
QMD_LIST_CHECK_NEXT(listelm, field); \
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
LIST_NEXT((listelm), field)->field.le_prev = \
&LIST_NEXT((elm), field); \
LIST_NEXT((listelm), field) = (elm); \
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
} while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
QMD_LIST_CHECK_PREV(listelm, field); \
(elm)->field.le_prev = (listelm)->field.le_prev; \
LIST_NEXT((elm), field) = (listelm); \
*(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
} while (0)
#define LIST_INSERT_HEAD(head, elm, field) do { \
QMD_LIST_CHECK_HEAD((head), field); \
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
LIST_FIRST((head)) = (elm); \
(elm)->field.le_prev = &LIST_FIRST((head)); \
} while (0)
#define LIST_PREV(elm, field) ((elm)->field.le_prev ? *(elm)->field.le_prev : NULL)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#define LIST_REMOVE(elm, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.le_next); \
QMD_SAVELINK(oldprev, (elm)->field.le_prev); \
QMD_LIST_CHECK_NEXT(elm, field); \
QMD_LIST_CHECK_PREV(elm, field); \
if (LIST_NEXT((elm), field) != NULL) \
LIST_NEXT((elm), field)->field.le_prev = \
(elm)->field.le_prev; \
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
TRASHIT(*oldnext); \
TRASHIT(*oldprev); \
} while (0)
#define LIST_SWAP(head1, head2, type, field) do { \
struct type *swap_tmp = LIST_FIRST((head1)); \
LIST_FIRST((head1)) = LIST_FIRST((head2)); \
LIST_FIRST((head2)) = swap_tmp; \
if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
} while (0)
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
TRACEBUF \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
#define TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
TRACEBUF \
}
/*
* Tail queue functions.
*/
#if (defined(_KERNEL) && defined(INVARIANTS))
#define QMD_TAILQ_CHECK_HEAD(head, field) do { \
if (!TAILQ_EMPTY(head) && \
TAILQ_FIRST((head))->field.tqe_prev != \
&TAILQ_FIRST((head))) \
panic("Bad tailq head %p first->prev != head", (head)); \
} while (0)
#define QMD_TAILQ_CHECK_TAIL(head, field) do { \
if (*(head)->tqh_last != NULL) \
panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \
} while (0)
#define QMD_TAILQ_CHECK_NEXT(elm, field) do { \
if (TAILQ_NEXT((elm), field) != NULL && \
TAILQ_NEXT((elm), field)->field.tqe_prev != \
&((elm)->field.tqe_next)) \
panic("Bad link elm %p next->prev != elm", (elm)); \
} while (0)
#define QMD_TAILQ_CHECK_PREV(elm, field) do { \
if (*(elm)->field.tqe_prev != (elm)) \
panic("Bad link elm %p prev->next != elm", (elm)); \
} while (0)
#else
#define QMD_TAILQ_CHECK_HEAD(head, field)
#define QMD_TAILQ_CHECK_TAIL(head, headname)
#define QMD_TAILQ_CHECK_NEXT(elm, field)
#define QMD_TAILQ_CHECK_PREV(elm, field)
#endif /* (_KERNEL && INVARIANTS) */
#define TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TAILQ_INIT((head2)); \
QMD_TRACE_HEAD(head1); \
QMD_TRACE_HEAD(head2); \
} \
} while (0)
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_FOREACH(var, head, field) \
for ((var) = TAILQ_FIRST((head)); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST((head)); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
for ((var) = TAILQ_LAST((head), headname); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_INIT(head) do { \
TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TAILQ_FIRST((head)); \
QMD_TRACE_HEAD(head); \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
QMD_TAILQ_CHECK_NEXT(listelm, field); \
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else { \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
QMD_TRACE_HEAD(head); \
} \
TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
QMD_TRACE_ELEM(&(elm)->field); \
QMD_TRACE_ELEM(&listelm->field); \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
QMD_TAILQ_CHECK_PREV(listelm, field); \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
QMD_TRACE_ELEM(&(elm)->field); \
QMD_TRACE_ELEM(&listelm->field); \
} while (0)
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
QMD_TAILQ_CHECK_HEAD(head, field); \
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
TAILQ_FIRST((head))->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
QMD_TRACE_HEAD(head); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
QMD_TAILQ_CHECK_TAIL(head, field); \
TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
QMD_TRACE_HEAD(head); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_REMOVE(head, elm, field) do { \
QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \
QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \
QMD_TAILQ_CHECK_NEXT(elm, field); \
QMD_TAILQ_CHECK_PREV(elm, field); \
if ((TAILQ_NEXT((elm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else { \
(head)->tqh_last = (elm)->field.tqe_prev; \
QMD_TRACE_HEAD(head); \
} \
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
TRASHIT(*oldnext); \
TRASHIT(*oldprev); \
QMD_TRACE_ELEM(&(elm)->field); \
} while (0)
#define TAILQ_SWAP(head1, head2, type, field) do { \
struct type *swap_first = (head1)->tqh_first; \
struct type **swap_last = (head1)->tqh_last; \
(head1)->tqh_first = (head2)->tqh_first; \
(head1)->tqh_last = (head2)->tqh_last; \
(head2)->tqh_first = swap_first; \
(head2)->tqh_last = swap_last; \
if ((swap_first = (head1)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head1)->tqh_first; \
else \
(head1)->tqh_last = &(head1)->tqh_first; \
if ((swap_first = (head2)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head2)->tqh_first; \
else \
(head2)->tqh_last = &(head2)->tqh_first; \
} while (0)
#endif /* !TTS_QUEUE_H */

BIN
screenshot.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

277
str.c Normal file
View file

@ -0,0 +1,277 @@
/*
* 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 <stddef.h>
#include <wctype.h>
#include <time.h>
#include "str.h"
#include "tts.h"
size_t
tokenise(str, res)
const wchar_t *str;
wchar_t ***res;
{
int ntoks = 0;
const wchar_t *p, *q;
wchar_t *r;
*res = NULL;
p = str;
for (;;) {
ptrdiff_t sz;
int qskip = 0;
int isbsl = 0;
/* Skip leading whitespace */
while (iswspace(*p))
p++;
/* End of string - no more arguments */
if (!*p)
break;
q = p;
if (*q == '"') {
/* Quoted string - scan for end of string */
p++;
while (*++q) {
if (!isbsl && (*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 (!iswspace(*q) && *q)
q++;
}
/* Copy the argument (which is sz bytes long) into the result array */
sz = (q - p);
*res = realloc(*res, sizeof(wchar_t *) * (ntoks + 1));
(*res)[ntoks] = malloc(sizeof(wchar_t) * (sz + 1));
wmemcpy((*res)[ntoks], p, sz);
/* Handle \ escapes */
for (r = (*res)[ntoks]; r < ((*res)[ntoks] + sz);) {
if (!isbsl) {
if (*r == '\\') {
wmemmove(r, r + 1, sz - (r - (*res)[ntoks]));
sz--;
isbsl = 1;
continue;
}
r++;
continue;
}
switch (*r) {
case 't': *r = '\t'; break;
case 'n': *r = '\n'; break;
case 'r': *r = '\r'; break;
case 'v': *r = '\v'; break;
case '\\': *r = '\\'; break;
}
isbsl = 0;
r++;
}
(*res)[ntoks][sz] = 0;
ntoks++;
if (qskip)
q += qskip;
while (iswspace(*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_t *) * (ntoks + 1));
(*res)[ntoks] = NULL;
return ntoks;
}
void
tokfree(vec)
wchar_t ***vec;
{
wchar_t **p;
for (p = (*vec); *p; p++)
free(*p);
free(*vec);
}
time_t
parsetime(tm)
const wchar_t *tm;
{
int h = 0, m = 0, s = 0;
time_t i = 0, r = 0;
/* The empty string is not a valid duration */
if (!*tm)
return (time_t) -1;
/* Check for "hh:mm:ss" or "mm:ss" */
if (swscanf(tm, L"%d:%d:%d", &h, &m, &s) == 3)
return (h * 60 * 60) + (m * 60) + s;
if (swscanf(tm, L"%d:%d", &m, &s) == 2)
return (m * 60) + s;
/*
* The string could either be a format like 3h10m, or a simle number like 47
* (meaning seconds), which is also handled here. This is effectively an
* implementation of atoi with special meaning for 'h', 'm' and 's' characters.
*
* Note that we make no attempt to handle overflow.
*/
for (; *tm; tm++) {
switch (*tm) {
case L'h':
r += i * (60 * 60);
i = 0;
continue;
case L'm':
r += i * 60;
i = 0;
continue;
case L's':
r += i;
i = 0;
continue;
case L' ':
continue;
}
if (wcschr(L"0123456789", *tm) == NULL)
return (time_t) -1;
i *= 10;
i += *tm - L'0';
}
return r + i;
}
wchar_t *
maketime(tm, fmt)
time_t tm;
{
wchar_t res[64] = {};
wchar_t t[16];
if (fmt == TIME_HMS) {
int h, m, s;
time_to_hms(tm, h, m, s);
swprintf(t, wsizeof(t), L"%02d:%02d:%02d",
h, m, s);
return wcsdup(t);
}
if (fmt == TIME_HM) {
int h, m, s;
time_to_hms(tm, h, m, s);
swprintf(t, wsizeof(t), L"%02d:%02d", h, m);
return wcsdup(t);
}
if (tm >= (60 * 60)) {
swprintf(t, wsizeof(t), L"%2dh ", tm / (60 * 60));
wcslcat(res, t, wsizeof(res));
tm %= (60 * 60);
}
if (tm >= 60) {
swprintf(t, wsizeof(t), L"%2dm ", tm / 60);
wcslcat(res, t, wsizeof(res));
tm %= 60;
}
if (fmt == TIME_AHMS && tm) {
swprintf(t, wsizeof(t), L"%2ds ", tm);
wcslcat(res, t, wsizeof(res));
}
res[wcslen(res) - 1] = '\0';
return wcsdup(res);
}
wchar_t *
escstr(s)
const wchar_t *s;
{
wchar_t *ret, *p;
if ((ret = calloc(sizeof(wchar_t), wcslen(s) * 2 + 1)) == NULL)
return NULL;
for (p = ret; *s; s++) {
switch (*s) {
case '\\':
*p++ = L'\\';
*p++ = L'\\';
continue;
case '\n':
*p++ = L'\\';
*p++ = L'n';
continue;
case '\t':
*p++ = L'\\';
*p++ = L't';
continue;
case '\v':
*p++ = L'\\';
*p++ = L'v';
continue;
case '\r':
*p++ = L'\\';
*p++ = L'r';
continue;
case '"':
*p++ = L'\\';
*p++ = L'"';
continue;
}
*p++ = *s;
}
return ret;
}

36
str.h Normal file
View file

@ -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_STR_H
#define TTS_STR_H
#include <stdlib.h>
#include "wide.h"
#define TIME_HMS 0 /* HH:MM:SS */
#define TIME_HM 1 /* HH:MM */
#define TIME_AHMS 2 /* 1h10m37s */
#define TIME_AHM 3 /* 1h10w */
#define TIMEFMT_FOR_EDIT(f) \
( (f) == TIME_HMS ? TIME_HMS \
: (f) == TIME_HM ? TIME_HMS \
: (f) == TIME_AHMS ? TIME_AHMS \
: (f) == TIME_AHM ? TIME_AHMS \
: 0)
size_t tokenise (const wchar_t *, wchar_t ***result);
void tokfree (wchar_t ***);
time_t parsetime (const wchar_t *);
wchar_t *maketime (time_t, int format);
wchar_t *escstr (const wchar_t *);
#endif /* !TTS_STR_H */

146
style.c Normal file
View file

@ -0,0 +1,146 @@
/*
* 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 "style.h"
#include "ui.h"
#include "wide.h"
short default_fg, default_bg;
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 attrname_t attrnames[] = {
{ L"normal", 0 },
{ L"bold", WA_BOLD },
{ L"reverse", WA_REVERSE },
{ L"blink", WA_BLINK },
{ L"dim", WA_DIM },
{ L"underline", WA_UNDERLINE },
{ L"standout", WA_STANDOUT }
};
static colour_t colours[] = {
{ L"black", COLOR_BLACK },
{ L"red", COLOR_RED },
{ L"green", COLOR_GREEN },
{ L"yellow", COLOR_YELLOW },
{ L"blue", COLOR_BLUE },
{ L"magenta", COLOR_MAGENTA },
{ L"cyan", COLOR_CYAN },
{ L"white", COLOR_WHITE }
};
int
attr_find(name, result)
const wchar_t *name;
attr_t *result;
{
size_t i;
for (i = 0; i < sizeof(attrnames) / sizeof(*attrnames); i++) {
if (wcscmp(attrnames[i].an_name, name) == 0) {
*result = attrnames[i].an_value;
return 0;
}
}
return -1;
}
int
colour_find(name, result)
const wchar_t *name;
short *result;
{
size_t i;
for (i = 0; i < sizeof(colours) / sizeof(*colours); i++) {
if (wcscmp(colours[i].co_name, name) == 0) {
*result = colours[i].co_value;
return 0;
}
}
return -1;
}
void
style_clear(sy)
style_t *sy;
{
init_pair(sy->sy_pair, default_fg, default_bg);
sy->sy_attrs = 0;
}
int
style_set(sy, fg, bg)
style_t *sy;
const wchar_t *fg, *bg;
{
sy->sy_attrs = 0;
init_pair(sy->sy_pair, default_fg, default_bg);
return style_add(sy, fg, bg);
}
int
style_add(sy, fg, bg)
style_t *sy;
const wchar_t *fg, *bg;
{
attr_t at;
short colfg, colbg = default_bg;
if (colour_find(fg, &colfg) == 0) {
if (bg && (colour_find(bg, &colbg) == -1))
return -1;
init_pair(sy->sy_pair, colfg, colbg);
return 0;
}
if (attr_find(fg, &at) == -1)
return -1;
sy->sy_attrs |= at;
return 0;
}
void
apply_styles()
{
wbkgd(statwin, style_bg(sy_status));
wattr_on(statwin, style_fg(sy_status), NULL);
drawstatus(L"");
wbkgd(titwin, style_bg(sy_header));
wattr_on(titwin, style_fg(sy_header), NULL);
drawheader();
}
void
style_defaults(void)
{
init_pair(1, default_fg, default_bg);
init_pair(2, default_fg, default_bg);
init_pair(3, default_fg, default_bg);
init_pair(4, default_fg, default_bg);
init_pair(5, default_fg, default_bg);
init_pair(6, default_fg, default_bg);
style_set(&sy_header, L"reverse", NULL);
style_set(&sy_status, L"normal", NULL);
style_set(&sy_entry, L"normal", NULL);
style_set(&sy_selected, L"normal", NULL);
style_set(&sy_running, L"bold", NULL);
style_set(&sy_date, L"underline", NULL);
apply_styles();
}

54
style.h Normal file
View file

@ -0,0 +1,54 @@
/*
* 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"
#include "wide.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) ((wint_t) ' ' | COLOR_PAIR((s).sy_pair) | ((s).sy_attrs & ~WA_UNDERLINE))
typedef struct attrname {
const wchar_t *an_name;
attr_t an_value;
} attrname_t;
typedef struct colour {
const wchar_t *co_name;
short co_value;
} colour_t;
extern style_t sy_header,
sy_status,
sy_entry,
sy_running,
sy_selected,
sy_date;
extern short default_fg, default_bg;
int attr_find (const wchar_t *name, attr_t *result);
int colour_find (const wchar_t *name, short *result);
void style_clear (style_t *);
int style_set (style_t *, const wchar_t *fg, const wchar_t *bg);
int style_add (style_t *, const wchar_t *fg, const wchar_t *bg);
void style_defaults (void);
void apply_styles (void);
#endif /* !TTS_STYLE_H */

163
tailq.h Normal file
View file

@ -0,0 +1,163 @@
/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)queue.h 8.5 (Berkeley) 8/20/94
* FreeBSD: release/9.0.0/sys/sys/queue.h 221843 2011-05-13 15:49:23Z mdf
*/
#ifndef TTS_TAILQ_H
#define TTS_TAILQ_H
/*
* Tail queue declarations.
*/
#define TTS_TAILQ_HEAD(name, type) \
struct name { \
struct type *tqh_first; /* first element */ \
struct type **tqh_last; /* addr of last next element */ \
}
#define TTS_TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
#define TTS_TAILQ_ENTRY(type) \
struct { \
struct type *tqe_next; /* next element */ \
struct type **tqe_prev; /* address of previous next element */ \
}
#define TTS_TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TTS_TAILQ_INIT((head2)); \
} \
} while (0)
#define TTS_TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TTS_TAILQ_FIRST(head) ((head)->tqh_first)
#define TTS_TAILQ_FOREACH(var, head, field) \
for ((var) = TTS_TAILQ_FIRST((head)); \
(var); \
(var) = TTS_TAILQ_NEXT((var), field))
#define TTS_TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TTS_TAILQ_FIRST((head)); \
(var) && ((tvar) = TTS_TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TTS_TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TTS_TAILQ_LAST((head), headname); \
(var); \
(var) = TTS_TAILQ_PREV((var), headname, field))
#define TTS_TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar)\
for ((var) = TTS_TAILQ_LAST((head), headname); \
(var) && ((tvar) = TTS_TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TTS_TAILQ_INIT(head) do { \
TTS_TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TTS_TAILQ_FIRST((head)); \
} while (0)
#define TTS_TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
if ((TTS_TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
TTS_TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TTS_TAILQ_NEXT((elm), field); \
else { \
(head)->tqh_last = &TTS_TAILQ_NEXT((elm), field); \
} \
TTS_TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TTS_TAILQ_NEXT((listelm), field); \
} while (0)
#define TTS_TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TTS_TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TTS_TAILQ_NEXT((elm), field); \
} while (0)
#define TTS_TAILQ_INSERT_HEAD(head, elm, field) do { \
if ((TTS_TAILQ_NEXT((elm), field) = TTS_TAILQ_FIRST((head))) != NULL) \
TTS_TAILQ_FIRST((head))->field.tqe_prev = \
&TTS_TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TTS_TAILQ_NEXT((elm), field); \
TTS_TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TTS_TAILQ_FIRST((head)); \
} while (0)
#define TTS_TAILQ_INSERT_TAIL(head, elm, field) do { \
TTS_TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TTS_TAILQ_NEXT((elm), field); \
} while (0)
#define TTS_TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TTS_TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TTS_TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TTS_TAILQ_REMOVE(head, elm, field) do { \
if ((TTS_TAILQ_NEXT((elm), field)) != NULL) \
TTS_TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else { \
(head)->tqh_last = (elm)->field.tqe_prev; \
} \
*(elm)->field.tqe_prev = TTS_TAILQ_NEXT((elm), field); \
} while (0)
#define TTS_TAILQ_SWAP(head1, head2, type, field) do { \
struct type *swap_first = (head1)->tqh_first; \
struct type **swap_last = (head1)->tqh_last; \
(head1)->tqh_first = (head2)->tqh_first; \
(head1)->tqh_last = (head2)->tqh_last; \
(head2)->tqh_first = swap_first; \
(head2)->tqh_last = swap_last; \
if ((swap_first = (head1)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head1)->tqh_first; \
else \
(head1)->tqh_last = &(head1)->tqh_first; \
if ((swap_first = (head2)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head2)->tqh_first; \
else \
(head2)->tqh_last = &(head2)->tqh_first; \
} while (0)
#endif /* !TTS_TAILQ_H */

2729
tts.c

File diff suppressed because it is too large Load diff

78
tts.h Normal file
View file

@ -0,0 +1,78 @@
/*
* 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 <signal.h>
#include "wide.h"
#include "tailq.h"
#include "entry.h"
extern char const *tts_version;
extern time_t laststatus;
/*
* Configuration options.
*/
extern int show_billable;
extern int delete_advance;
extern int mark_advance;
extern int bill_advance;
extern int bill_increment;
extern wchar_t *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_t *he_text;
TTS_TAILQ_ENTRY(histent) he_entries;
} histent_t;
typedef TTS_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_t const *);
extern history_t *searchhist;
extern history_t *prompthist;
#ifndef HAVE_WCSLCPY
size_t wcslcat(wchar_t *s1, const wchar_t *s2, size_t n);
#endif
#ifndef HAVE_WCSLCAT
size_t wcslcpy(wchar_t *s1, const wchar_t *s2, size_t n);
#endif
extern int time_format;
#endif /* !TTS_H */

30
tts_curses.h Normal file
View file

@ -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 <ncursesw/curses.h>
#elif defined HAVE_NCURSESW_H
# include <ncursesw.h>
#elif defined HAVE_NCURSES_CURSES_H
# include <ncurses/curses.h>
#elif defined HAVE_NCURSES_H
# include <ncurses.h>
#elif defined HAVE_CURSES_H
# include <curses.h>
#else
# error "SVR4 or XSI compatible curses header file required"
#endif
#endif /* !TTS_CURSES_H */

View file

@ -11,6 +11,15 @@
#set mark_advance 1 #set mark_advance 1
#set delete_advance 1 #set delete_advance 1
# Select the time format used in the display; value can be from 0 to 3.
#
# 0: 10:03:37 (default)
# 1: 10:03
# 2: 10h 3m 37s
# 3: 10h 3m
#
#set time_format 0
#### Billing options #### Billing options
# #
# If set, show billable time in each daily summary. # If set, show billable time in each daily summary.
@ -33,7 +42,7 @@
#set bill_advance 0 #set bill_advance 0
#### Bindings #### Bindings and macros
# #
# Use the 'bind' command to (re)define keybindings. Type '?' while TTS is # Use the 'bind' command to (re)define keybindings. Type '?' while TTS is
# running for a full list of key bindings. # running for a full list of key bindings.
@ -45,6 +54,17 @@
bind j next bind j next
bind k prev bind k prev
# Macros work in a similar way to bindings, except the second argument is a
# string which will be executed as if it was typed. For example, the
# following macro would add a new entry called "test", and set its timer to
# 30 minutes.
#macro t "atest\n+30m\n"
# Macros can also prompt for input from the user using $[Prompt string]; the
# $[...] will be replaced with the user's input. For example, this macro will
# prompt for the name of a new entry, then set its duration to 30m:
#macro t "a$[Description:]\n+30m\n"
#### Styling #### Styling
# #
# You can style UI elements with the 'style' command. Its syntax is: # You can style UI elements with the 'style' command. Its syntax is:
@ -101,8 +121,8 @@ bind k prev
# #
style header yellow,bold blue style header yellow,bold blue
style status yellow,bold blue style status yellow,bold blue
style date white,underline black
style entry white black style entry white black
style selected yellow,bold red style selected yellow,bold red
# Use bold *and* underline, because we already bolded 'selected' above. # Use bold *and* underline, because we already bolded 'selected' above.
style running bold,underline style running white,bold,underline black
style date underline,bold

666
ui.c Normal file
View file

@ -0,0 +1,666 @@
/*
* 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 <string.h>
#include <stdlib.h>
#include "ui.h"
#include "tts.h"
#include "style.h"
#include "str.h"
WINDOW *titwin, *statwin, *listwin;
int in_curses;
void
ui_init(void)
{
initscr();
in_curses = 1;
start_color();
#ifdef HAVE_USE_DEFAULT_COLORS
use_default_colors();
#endif
cbreak();
noecho();
nonl();
nodelay(stdscr, TRUE);
pair_content(0, &default_fg, &default_bg);
refresh();
intrflush(stdscr, TRUE);
keypad(stdscr, TRUE);
leaveok(stdscr, TRUE);
titwin = newwin(1, 0, 0, 0);
intrflush(titwin, FALSE);
keypad(titwin, TRUE);
leaveok(titwin, TRUE);
statwin = newwin(1, 0, LINES - 1, 0);
intrflush(statwin, FALSE);
keypad(statwin, TRUE);
leaveok(statwin, TRUE);
listwin = newwin(LINES - 2, 0, 1, 0);
intrflush(listwin, FALSE);
keypad(listwin, TRUE);
leaveok(listwin, TRUE);
curs_set(0);
}
/*
* 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 = TTS_TAILQ_FIRST(&entries);
return;
}
/*
* Try to find the next suitable entry to move the cursor to.
*/
for (en = TTS_TAILQ_NEXT(curent, en_entries); en; en = TTS_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 = TTS_TAILQ_PREV(curent, entrylist, en_entries); en;
en = TTS_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
drawheader()
{
wchar_t title[64];
swprintf(title, wsizeof(title), L"TTS %s - Type '?' for help",
tts_version);
wmove(titwin, 0, 0);
waddwstr(titwin, title);
if (itime > 0) {
wchar_t str[128], *tm;
time_t passed = time(NULL) - itime;
tm = maketime(passed, time_format);
swprintf(str, wsizeof(str), L" *** MARK INTERRUPT: %ls ***", tm);
free(tm);
wattron(titwin, A_BOLD);
waddwstr(titwin, str);
wattroff(titwin, A_BOLD);
}
wclrtoeol(titwin);
wrefresh(titwin);
}
void
vdrawstatus(msg, ap)
const wchar_t *msg;
va_list ap;
{
wchar_t s[1024];
vswprintf(s, wsizeof(s), msg, ap);
wmove(statwin, 0, 0);
waddwstr(statwin, s);
wclrtoeol(statwin);
wrefresh(statwin);
time(&laststatus);
}
void
drawstatus(const wchar_t *msg, ...)
{
va_list ap;
va_start(ap, msg);
vdrawstatus(msg, ap);
va_end(ap);
}
int
yesno(msg)
const wchar_t *msg;
{
WINDOW *pwin;
wint_t c;
pwin = newwin(1, COLS, LINES - 1, 0);
keypad(pwin, TRUE);
wattron(pwin, A_BOLD);
wattr_on(pwin, style_fg(sy_status), NULL);
wbkgd(pwin, style_bg(sy_status));
wmove(pwin, 0, 0);
waddwstr(pwin, msg);
waddwstr(pwin, L" [y/N]? ");
wattroff(pwin, A_BOLD);
while (input_char(pwin, &c) == ERR
#ifdef KEY_RESIZE
|| (c == KEY_RESIZE)
#endif
)
;
delwin(pwin);
wtouchln(statwin, 0, 1, 1);
wrefresh(statwin);
return (c == 'Y' || c == 'y') ? 1 : 0;
}
wchar_t *
prompt(msg, def, hist)
const wchar_t *msg, *def;
history_t *hist;
{
WINDOW *pwin;
wchar_t input[256];
size_t pos = 0;
histent_t *histpos = NULL;
if (hist == NULL)
hist = prompthist;
memset(input, 0, sizeof(input));
if (def) {
wcsncpy(input, def, wsizeof(input) - 1);
pos = wcslen(input);
}
pwin = newwin(1, COLS, LINES - 1, 0);
keypad(pwin, TRUE);
wattr_on(pwin, style_fg(sy_status), NULL);
wbkgd(pwin, style_bg(sy_status));
wattron(pwin, A_BOLD);
wmove(pwin, 0, 0);
waddwstr(pwin, msg);
wattroff(pwin, A_BOLD);
curs_set(1);
for (;;) {
wint_t c;
int i, inpos;
wmove(pwin, 0, wcslen(msg) + 1);
for (i = 0; i < wcslen(input); i++) {
if (input[i] == L'\t')
waddwstr(pwin, L" ");
else {
wchar_t s[] = { input[i], '\0' };
waddwstr(pwin, s);
}
}
i--;
wclrtoeol(pwin);
for (i = 0, inpos = 0; i < wcslen(input) && i < pos; i++)
if (input[i] == L'\t')
inpos += 8;
else
inpos++;
wmove(pwin, 0, wcslen(msg) + 1 + inpos);
wrefresh(pwin);
if (input_char(pwin, &c) == ERR)
continue;
switch (c) {
case '\n':
case '\r':
goto end;
case KEY_BACKSPACE:
case 0x7F:
case 0x08:
if (pos) {
if (pos == wcslen(input))
input[--pos] = 0;
else {
int i = wcslen(input);
pos--;
wmemcpy(input + pos, input + pos + 1, wcslen(input) - pos);
input[i] = 0;
}
}
break;
case KEY_DC:
if (pos < wcslen(input)) {
int i = wcslen(input);
wmemcpy(input + pos, input + pos + 1, wcslen(input) - pos);
input[i] = 0;
}
break;
case KEY_LEFT:
if (pos)
pos--;
break;
case KEY_RIGHT:
if (pos < wcslen(input))
pos++;
break;
case KEY_HOME:
case 0x01: /* ^A */
pos = 0;
break;
case KEY_END:
case 0x05: /* ^E */
pos = wcslen(input);
break;
case 0x07: /* ^G */
case 0x1B: /* ESC */
curs_set(0);
delwin(pwin);
return NULL;
case 0x15: /* ^U */
input[0] = 0;
pos = 0;
break;
#ifdef KEY_RESIZE
case KEY_RESIZE:
break;
#endif
case KEY_UP:
if (histpos == NULL) {
if ((histpos = TTS_TAILQ_LAST(&hist->hi_ents, hentlist)) == NULL) {
beep();
break;
}
} else {
if (TTS_TAILQ_PREV(histpos, hentlist, he_entries) == NULL) {
beep();
break;
} else
histpos = TTS_TAILQ_PREV(histpos, hentlist, he_entries);
}
wcsncpy(input, histpos->he_text, wsizeof(input) - 1);
pos = wcslen(input);
break;
case KEY_DOWN:
if (histpos == NULL) {
beep();
break;
}
if (TTS_TAILQ_NEXT(histpos, he_entries) == NULL) {
beep();
break;
} else
histpos = TTS_TAILQ_NEXT(histpos, he_entries);
wcsncpy(input, histpos->he_text, wsizeof(input) - 1);
pos = wcslen(input);
break;
default:
if (pos != wcslen(input)) {
wmemmove(input + pos + 1, input + pos, wcslen(input) - pos);
input[pos++] = c;
} else {
input[pos++] = c;
input[pos] = 0;
}
break;
}
}
end: ;
curs_set(0);
delwin(pwin);
wtouchln(statwin, 0, 1, 1);
wrefresh(statwin);
hist_add(hist, input);
return wcsdup(input);
}
void
errbox(const wchar_t *msg, ...)
{
va_list ap;
va_start(ap, msg);
verrbox(msg, ap);
va_end(ap);
}
void
verrbox(msg, ap)
const wchar_t *msg;
va_list ap;
{
wchar_t text[4096];
WINDOW *ewin;
#define ETITLE L" Error "
#define ECONT L" <OK> "
int width;
wint_t c;
vswprintf(text, wsizeof(text), msg, ap);
width = wcslen(text);
ewin = newwin(6, width + 4,
(LINES / 2) - ((1 + 2)/ 2),
(COLS / 2) - ((width + 2) / 2));
leaveok(ewin, TRUE);
wborder(ewin, 0, 0, 0, 0, 0, 0, 0, 0);
wattron(ewin, A_REVERSE | A_BOLD);
wmove(ewin, 0, (width / 2) - (wsizeof(ETITLE) - 1)/2);
waddwstr(ewin, ETITLE);
wattroff(ewin, A_REVERSE | A_BOLD);
wmove(ewin, 2, 2);
waddwstr(ewin, text);
wattron(ewin, A_REVERSE | A_BOLD);
wmove(ewin, 4, (width / 2) - ((wsizeof(ECONT) - 1) / 2));
waddwstr(ewin, ECONT);
wattroff(ewin, A_REVERSE | A_BOLD);
for (;;) {
if (wget_wch(ewin, &c) == ERR)
continue;
if (c == '\r')
break;
}
delwin(ewin);
}
void
drawentries()
{
int i, nlines;
int cline = 0;
time_t lastday = 0;
entry_t *en;
chtype oldbg;
getmaxyx(listwin, nlines, i);
TTS_TAILQ_FOREACH(en, &entries, en_entries)
en->en_flags.efl_visible = 0;
en = TTS_TAILQ_FIRST(&entries);
for (i = 0; i < pagestart; i++)
if ((en = TTS_TAILQ_NEXT(en, en_entries)) == NULL)
return;
for (; en; en = TTS_TAILQ_NEXT(en, en_entries)) {
time_t n;
wchar_t flags[10], stime[16], *p;
attr_t attrs = 0;
wchar_t *etime;
if (!showinv && en->en_flags.efl_invoiced)
continue;
oldbg = getbkgd(listwin);
if (lastday != entry_day(en)) {
struct tm *lt;
wchar_t lbl[128];
wchar_t *itime = maketime(entry_time_for_day(entry_day(en), 1, 0), time_format),
*ntime = maketime(entry_time_for_day(entry_day(en), 0, 0), time_format),
*btime = maketime(entry_time_for_day(entry_day(en), 2, bill_increment), time_format),
*ttime = maketime(entry_time_for_day(entry_day(en), 1, 0) +
entry_time_for_day(entry_day(en), 0, 0), time_format);
wchar_t hdrtext[256];
int n = 0;
oldbg = getbkgd(listwin);
wbkgdset(listwin, style_bg(sy_entry));
wattr_on(listwin, style_fg(sy_entry), NULL);
wmove(listwin, cline, 0);
wclrtoeol(listwin);
wbkgdset(listwin, oldbg);
wattr_off(listwin, style_fg(sy_entry), NULL);
if (++cline >= nlines)
break;
lastday = entry_day(en);
lt = localtime(&lastday);
wcsftime(lbl, wsizeof(lbl), L"%A, %d %B %Y", lt);
swprintf(hdrtext, wsizeof(hdrtext), L"%-30ls [", lbl);
if (*itime) {
wcslcat(hdrtext, L"I:", wsizeof(hdrtext));
wcslcat(hdrtext, itime, wsizeof(hdrtext));
n++;
}
free(itime);
if (*ntime) {
if (n++)
wcslcat(hdrtext, L" ", wsizeof(hdrtext));
wcslcat(hdrtext, L"N:", wsizeof(hdrtext));
wcslcat(hdrtext, ntime, wsizeof(hdrtext));
}
free(ntime);
if (*ttime) {
if (n++)
wcslcat(hdrtext, L" ", wsizeof(hdrtext));
wcslcat(hdrtext, L"T:", wsizeof(hdrtext));
wcslcat(hdrtext, ttime, wsizeof(hdrtext));
}
free(ttime);
if (*btime) {
if (n++)
wcslcat(hdrtext, L" ", wsizeof(hdrtext));
wcslcat(hdrtext, L"B:", wsizeof(hdrtext));
wcslcat(hdrtext, btime, wsizeof(hdrtext));
}
free(btime);
wcslcat(hdrtext, L"]", wsizeof(hdrtext));
wattr_on(listwin, style_fg(sy_date), NULL);
wbkgdset(listwin, style_bg(sy_date));
wmove(listwin, cline, 0);
waddwstr(listwin, hdrtext);
wclrtoeol(listwin);
wattr_off(listwin, style_fg(sy_date), NULL);
wbkgdset(listwin, oldbg);
if (++cline >= nlines) {
wbkgdset(listwin, oldbg);
wattr_off(listwin, style_fg(sy_date), NULL);
break;
}
oldbg = getbkgd(listwin);
wbkgdset(listwin, style_bg(sy_entry));
wattr_on(listwin, style_fg(sy_entry), NULL);
wmove(listwin, cline, 0);
wclrtoeol(listwin);
wbkgdset(listwin, oldbg);
wattr_off(listwin, style_fg(sy_entry), NULL);
if (++cline >= nlines)
break;
}
en->en_flags.efl_visible = 1;
wmove(listwin, cline, 0);
attrs = style_fg(sy_entry);
if (en->en_started && en == curent)
attrs = style_fg(sy_selected) |
(style_fg(sy_running) & (
WA_STANDOUT | WA_UNDERLINE |
WA_REVERSE | WA_BLINK | WA_DIM |
WA_BOLD));
else if (en->en_started)
attrs = style_fg(sy_running);
else if (en == curent)
attrs = style_fg(sy_selected);
wbkgdset(listwin, ' ' | (attrs & ~WA_UNDERLINE));
wattr_on(listwin, attrs, NULL);
if (en == curent) {
waddwstr(listwin, L" -> ");
} else
waddwstr(listwin, L" ");
n = en->en_secs;
if (en->en_started)
n += time(NULL) - en->en_started;
etime = maketime(n, time_format);
swprintf(stime, wsizeof(stime), L"%8ls%c ",
*etime ? etime : L"0m", (itime && (en == running)) ? '*' : ' ');
free(etime);
waddwstr(listwin, stime);
memset(flags, 0, sizeof(flags));
p = flags;
if (en->en_flags.efl_marked)
*p++ = 'M';
else
*p++ = ' ';
if (en->en_flags.efl_invoiced)
*p++ = 'I';
else
*p++ = ' ';
if (!en->en_flags.efl_nonbillable)
*p++ = 'B';
else
*p++ = ' ';
if (en->en_flags.efl_deleted)
*p++ = 'D';
else
*p++ = ' ';
if (*flags) {
wchar_t s[10];
swprintf(s, wsizeof(s), L"%-5ls ", flags);
waddwstr(listwin, s);
} else
waddwstr(listwin, L" ");
waddwstr(listwin, en->en_desc);
wclrtoeol(listwin);
wbkgdset(listwin, oldbg);
wattr_off(listwin, attrs, NULL);
if (++cline >= nlines)
return;
}
oldbg = getbkgd(listwin);
wattr_on(listwin, style_fg(sy_entry), NULL);
wbkgdset(listwin, style_bg(sy_entry));
for (; cline < nlines; cline++) {
wmove(listwin, cline, 0);
wclrtoeol(listwin);
}
wattr_off(listwin, style_fg(sy_entry), NULL);
wbkgdset(listwin, oldbg);
}
time_t
prduration(pr, def)
wchar_t *pr;
time_t def;
{
wchar_t *defstr = NULL;
wchar_t *tstr;
time_t ret;
defstr = maketime(def, TIMEFMT_FOR_EDIT(time_format));
if ((tstr = prompt(pr, defstr, NULL)) == NULL) {
free(defstr);
return -1;
}
free(defstr);
if (!*tstr) {
drawstatus(L"No duration entered");
free(tstr);
return -1;
}
if ((ret = parsetime(tstr)) == (time_t) -1) {
free(tstr);
drawstatus(L"Invalid time format.");
return -1;
}
free(tstr);
return ret;
}

38
ui.h Normal file
View file

@ -0,0 +1,38 @@
/*
* 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 <stdarg.h>
#include "tts.h"
#include "tts_curses.h"
#include "wide.h"
extern WINDOW *titwin, *statwin, *listwin;
extern int in_curses;
extern int showinv;
void ui_init (void);
void cursadvance (void);
void drawstatus (const wchar_t *msg, ...);
void vdrawstatus (const wchar_t *msg, va_list);
void drawheader (void);
void drawentries (void);
wchar_t *prompt (wchar_t const *, wchar_t const *, history_t *);
time_t prduration (wchar_t *prompt, time_t def);
int yesno (wchar_t const *);
void errbox (wchar_t const *, ...);
void verrbox (wchar_t const *, va_list);
#endif /* !TTS_UI_H */

28
variable.h Normal file
View file

@ -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_t 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_t *name);
#endif /* !TTS_VARIABLE_H */

99
wcslcpy.c Normal file
View file

@ -0,0 +1,99 @@
/* $NetBSD: strlcpy.c,v 1.3 2007/06/04 18:19:27 christos Exp $ */
/* $OpenBSD: strlcpy.c,v 1.7 2003/04/12 21:56:39 millert Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
* FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <assert.h>
#include <string.h>
#include <wchar.h>
#include "config.h"
/*
* Copy src to string dst of size siz. At most siz-1 characters
* will be copied. Always NUL terminates (unless siz == 0).
* Returns wcslen(src); if retval >= siz, truncation occurred.
*/
#ifndef HAVE_WCSLCPY
size_t
wcslcpy(dst, src, siz)
wchar_t *dst;
wchar_t const *src;
size_t siz;
{
wchar_t *d = dst;
const wchar_t *s = src;
size_t n = siz;
/* Copy as many bytes as will fit */
if (n != 0 && --n != 0) {
do {
if ((*d++ = *s++) == 0)
break;
} while (--n != 0);
}
/* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) {
if (siz != 0)
*d = '\0'; /* NUL-terminate dst */
while (*s++)
;
}
return(s - src - 1); /* count does not include NUL */
}
#endif
/*
* Appends src to string dst of size siz (unlike strncat, siz is the
* full size of dst, not space left). At most siz-1 characters
* will be copied. Always NUL terminates (unless siz <= wcslen(dst)).
* Returns wcslen(src) + MIN(siz, wcslen(initial dst)).
* If retval >= siz, truncation occurred.
*/
#ifndef HAVE_WCSLCAT
size_t
wcslcat(wchar_t *dst, const wchar_t *src, size_t siz)
{
wchar_t *d = dst;
const wchar_t *s = src;
size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst;
n = siz - dlen;
if (n == 0)
return(dlen + wcslen(s));
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return(dlen + (s - src)); /* count does not include NUL */
}
#endif

26
wide.c Normal file
View file

@ -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 */

24
wide.h Normal file
View file

@ -0,0 +1,24 @@
/*
* 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 <wchar.h>
#include "config.h"
#include "tts_curses.h"
#define wsizeof(s) (sizeof(s) / sizeof(wchar_t))
int input_char (WINDOW *, wint_t *);
void input_macro (wchar_t *);
#endif /* !TTS_WIDE_H */