]> git.neil.brown.name Git - freerunner.git/commitdiff
alarm: newly added
authorNeilBrown <neilb@suse.de>
Sun, 6 Feb 2011 09:26:11 +0000 (20:26 +1100)
committerNeilBrown <neilb@suse.de>
Sun, 6 Feb 2011 09:26:11 +0000 (20:26 +1100)
Both C and Python code here.
The C code is maintained, the python was too slot

Signed-off-by: NeilBrown <neilb@suse.de>
alarm/Makefile [new file with mode: 0644]
alarm/alarm.c [new file with mode: 0644]
alarm/cal.c [new file with mode: 0644]
alarm/cal.py [new file with mode: 0644]
alarm/clock.py [new file with mode: 0644]
alarm/listsel.c [new file with mode: 0644]
alarm/listsel.h [new file with mode: 0644]
alarm/notes [new file with mode: 0644]

diff --git a/alarm/Makefile b/alarm/Makefile
new file mode 100644 (file)
index 0000000..66795b6
--- /dev/null
@@ -0,0 +1,22 @@
+
+CFLAGS= -O2 `pkg-config gtk+-2.0 --cflags`
+CFLAGS= -ggdb `pkg-config gtk+-2.0 --cflags`
+LDFLAGS= `pkg-config gtk+-2.0 --libs`
+all: cal alarm
+
+alarm: alarm.o
+       $(CC) -o alarm alarm.o
+
+alarm.o: alarm.c
+       $(CC) -c alarm.c
+
+cal : cal.o listsel.o
+       $(CC) -o cal cal.o listsel.o $(LDFLAGS)
+
+listsel.o: listsel.c listsel.h
+
+listsel : listsel.o
+       $(CC) -o listsel listsel.o $(LDFLAGS)
+
+clean:
+       rm -f cal.o listsel.o alarm.o
\ No newline at end of file
diff --git a/alarm/alarm.c b/alarm/alarm.c
new file mode 100644 (file)
index 0000000..d134a15
--- /dev/null
@@ -0,0 +1,370 @@
+
+/*
+ * generate alarm messages as required.
+ * Alarm information is stored in /etc/alarms
+ * format is:
+ *   date:recurrence:message
+ *
+ * date is yyyy-mm-dd-hh-mm-ss
+ *  recurrence is currently nndays
+ * message is any text
+ *
+ * When the time comes, a txt message is generated
+ *
+ * We use dnotify to watch the file
+ *
+ * We never report any event that is before the timestamp
+ * on the file - that guards against replaying lots of 
+ * events if the clock gets set backwards.
+ */
+
+#define _XOPEN_SOURCE
+#define _BSD_SOURCE
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+#include <linux/rtc.h>
+#include <sys/dir.h>
+
+struct event {
+       struct event *next;
+       time_t when;
+       long recur;
+       char *mesg;
+};
+
+void event_free(struct event *ev)
+{
+       while (ev) {
+               struct event *n = ev->next;
+               free(ev->mesg);
+               free(ev);
+               ev = n;
+       }
+}
+
+void update_event(struct event *ev, time_t now)
+{
+       /* make sure 'when' is in the future if possible */
+       while (ev->when < now && ev->recur > 0)
+               ev->when += ev->recur;
+}
+
+struct event *event_parse(char *line)
+{
+       struct event *rv;
+       char *recur, *mesg;
+       struct tm tm;
+       long rsec;
+       recur = strchr(line, ':');
+       if (!recur)
+               return NULL;
+       *recur++ = 0;
+       mesg = strchr(recur, ':');
+       if (!mesg)
+               return NULL;
+       *mesg++ = 0;
+       line = strptime(line, "%Y-%m-%d-%H-%M-%S", &tm);
+       if (!line)
+               return NULL;
+       rsec = atoi(recur);
+       if (rsec < 0)
+               return NULL;
+       rv = malloc(sizeof(*rv));
+       rv->next = NULL;
+       tm.tm_isdst = -1;
+       rv->when = mktime(&tm);
+       rv->recur = rsec;
+       rv->mesg = strdup(mesg);
+       return rv;
+}
+
+struct event *event_load_soonest(char *file, time_t now)
+{
+       /* load the file and return only the soonest event at or after now */
+       char line[2048];
+       struct stat stb;
+       struct event *rv = NULL, *ev;
+       FILE *f;
+
+       f = fopen(file, "r");
+       if (!f)
+               return NULL;
+       if (fstat(fileno(f), &stb) == 0 &&
+           stb.st_mtime > now)
+               now = stb.st_mtime;
+
+       while (fgets(line, sizeof(line), f) != NULL) {
+               char *eol = line + strlen(line);
+               if (eol > line && eol[-1] == '\n')
+                       eol[-1] = 0;
+
+               ev = event_parse(line);
+               if (!ev)
+                       continue;
+               update_event(ev, now);
+               if (ev->when < now) {
+                       event_free(ev);
+                       continue;
+               }
+               if (rv == NULL) {
+                       rv = ev;
+                       continue;
+               }
+               if (rv->when < ev->when) {
+                       event_free(ev);
+                       continue;
+               }
+               event_free(rv);
+               rv = ev;
+       }
+       fclose(f);
+       return rv;
+}
+
+struct event *event_load_soonest_dir(char *dir, time_t now)
+{
+       DIR *d = opendir(dir);
+       struct dirent *de;
+       struct event *rv = NULL;
+
+       if (!d)
+               return NULL;
+       while ((de = readdir(d)) != NULL) {
+               char *p = NULL;
+               struct event *e;
+               if (de->d_ino == 0)
+                       continue;
+               if (de->d_name[0] == '.')
+                       continue;
+               asprintf(&p, "%s/%s", dir, de->d_name);
+               e = event_load_soonest(p, now);
+               free(p);
+               if (e == NULL)
+                       ;
+               else if (rv == NULL)
+                       rv = e;
+               else if (rv->when < e->when)
+                       event_free(e);
+               else {
+                       event_free(rv);
+                       rv = e;
+               }
+       }
+       closedir(d);
+       return rv;
+}
+
+void event_deliver(struct event *ev)
+{
+       char line[2048];
+       char file[1024];
+       struct tm *tm;
+       char *z;
+       char *m;
+       int f;
+       time_t now;
+
+       time(&now);
+       tm = gmtime(&now);
+       strftime(line, 2048, "%Y%m%d-%H%M%S", tm);
+       tm = localtime(&ev->when);
+       strftime(line+strlen(line), 1024, " ALARM %Y%m%d-%H%M%S", tm);
+       z = line + strlen(line);
+       sprintf(z, "%+02d alarm ", tm->tm_gmtoff/60/15);
+
+       z = line + strlen(line);
+       m = ev->mesg;
+
+       while (*m) {
+               switch (*m) {
+               default:
+                       *z++ = *m;
+                       break;
+               case ' ':
+               case '\t':
+               case '\n':
+                       sprintf(z, "%%%02x", *m);
+                       z += 3;
+                       break;
+               }
+               m++;
+       }
+       *z++ = '\n';
+       *z = 0;
+       
+       sprintf(file, "/media/card/SMS/%.6s", line);
+       f = open(file, O_WRONLY | O_APPEND | O_CREAT, 0600);
+       if (f >= 0) {
+               write(f, line, strlen(line));
+               close(f);
+               f = open("/media/card/SMS/newmesg", O_WRONLY | O_APPEND | O_CREAT,
+                        0600);
+               if (f >= 0) {
+                       write(f, line, 15);
+                       write(f, "\n", 1);
+                       close(f);
+               }
+               f = open("/var/run/alert/alarm", O_WRONLY | O_CREAT, 0600);
+               if (f) {
+                       write(f, "new\n", 4);
+                       close(f);
+               }
+       }
+}
+
+static int read_alarm(struct rtc_wkalrm *alarm, int fd)
+{
+        int res;
+
+        return ioctl(fd, RTC_WKALM_RD, alarm);
+}
+
+static void read_time(struct rtc_time *tm, int fd)
+{
+        int res;
+
+        res = ioctl(fd, RTC_RD_TIME, tm);
+        if (res < 0) {
+                perror("ioctl(RTC_RD_TIME)");
+                exit(1);
+        }
+}
+
+static void write_alarm(const struct rtc_wkalrm *alarm, int fd)
+{
+        int res;
+
+        res = ioctl(fd, RTC_WKALM_SET, alarm);
+        if (res < 0) {
+                perror("ioctl(RTC_WKALM_SET)");
+                exit(1);
+        }
+}
+
+
+
+void set_alarm(time_t then)
+{
+       struct rtc_wkalrm alarm;
+       struct tm *tm, tm2;
+       time_t now;
+       int fd = open("/dev/rtc0", O_RDWR);
+
+       if (read_alarm(&alarm, fd) == 0) {
+               alarm.enabled = 0;
+               write_alarm(&alarm, fd);
+       }
+       read_time(&alarm.time, fd);
+       memset(&tm2, 0, sizeof(tm2));
+       tm2.tm_sec = alarm.time.tm_sec;
+        tm2.tm_min = alarm.time.tm_min;
+        tm2.tm_hour = alarm.time.tm_hour;
+        tm2.tm_mday = alarm.time.tm_mday;
+        tm2.tm_mon = alarm.time.tm_mon;
+        tm2.tm_year = alarm.time.tm_year;
+        tm2.tm_isdst = -1;
+        now = timegm(&tm2);
+       now += (then - time(0));
+       tm = gmtime(&now);
+       alarm.time.tm_sec = tm->tm_sec;
+       alarm.time.tm_min = tm->tm_min;
+       alarm.time.tm_hour = tm->tm_hour;
+       alarm.time.tm_mday = tm->tm_mday;
+       alarm.time.tm_mon = tm->tm_mon;
+       alarm.time.tm_year = tm->tm_year;
+       alarm.enabled = 1;
+       write_alarm(&alarm, fd);
+       close(fd);
+}
+
+struct timespec to;
+void catchio(int sig)
+{
+       to.tv_sec = 0;
+       /* in case we enter select */
+       alarm(1);
+       return;
+}
+void catchalarm(int sig)
+{
+       return;
+}
+
+main(int argc, char *argv[])
+{
+       struct event *ev;
+       time_t now;
+       int efd;
+       int sdfd;
+       int sfd;
+       sigset_t set;
+
+       signal(SIGIO, catchio);
+       signal(SIGALRM, catchalarm);
+       efd = open("/etc/alarms", O_DIRECTORY|O_RDONLY);
+       if (efd < 0)
+               exit(1);
+       sdfd = open("/var/lock/suspend", O_DIRECTORY|O_RDONLY);
+       if (sdfd < 0)
+               exit(1);
+
+       sfd = open("/var/lock/suspend/suspend", O_RDONLY);
+       if (sfd < 0)
+               exit(1);
+       flock(sfd, LOCK_SH);
+       
+       time(&now);
+
+       sigemptyset(&set);
+       sigaddset(&set, SIGIO);
+       sigprocmask(SIG_BLOCK, &set, NULL);
+       sigdelset(&set, SIGIO);
+       for(;;) {
+               struct stat stb;
+               fcntl(efd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_RENAME);
+               fcntl(sdfd, F_NOTIFY, DN_MODIFY);
+               ev = event_load_soonest_dir("/etc/alarms", now);
+
+               fstat(sfd, &stb);
+               if (stb.st_size > 0) {
+                       /* suspend has been requested */
+                       int s2;
+                       if (ev)
+                               set_alarm(ev->when-5);
+                       s2 = open("/var/lock/suspend/next_suspend", O_RDONLY);
+                       flock(s2, LOCK_SH);
+                       flock(sfd, LOCK_UN);
+                       /* suspend should happen now. we know that it has
+                        * because sfd gets unlinked
+                        */
+                       while (fstat(sfd, &stb) == 0 &&
+                              stb.st_nlink > 0)
+                               sleep(4);
+                       close(sfd);
+                       sfd = s2;
+               }
+
+               to.tv_nsec = 0;
+               if (ev) {
+                       to.tv_sec = 0;
+                       if (ev->when > time(0))
+                               to.tv_sec = ev->when - time(0);
+               } else
+                       to.tv_sec = 3600;
+               pselect(0, NULL, NULL, NULL, &to, &set);
+               
+               if (ev && time(0) >= ev->when) {
+                       event_deliver(ev);
+                       now = ev->when+1;
+               }
+               event_free(ev);
+       }
+}
diff --git a/alarm/cal.c b/alarm/cal.c
new file mode 100644 (file)
index 0000000..dccdce7
--- /dev/null
@@ -0,0 +1,1214 @@
+#define _XOPEN_SOURCE
+#define _GNU_SOURCE
+
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string.h>
+#include <malloc.h>
+#include <math.h>
+#include <time.h>
+
+#include <gtk/gtk.h>
+
+#include "listsel.h"
+
+struct event {
+       struct event *next;
+       time_t when, first;
+       long recur;
+       char *mesg;
+};
+
+
+char *days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+
+GtkWidget *calblist[42], *month, *date_display, *date_time_display;
+GtkWidget *window, *clockw, *cal, *reasonw, *timerw;
+GtkWidget *browse_buttons, *event_buttons, *move_buttons, *move_event;
+GtkWidget *timer_display, *time_display;
+GtkWidget *today_btn, *undelete_btn;
+GtkWidget *timers_list;
+GtkTextBuffer *reason_buffer;
+time_t dlist[42], chosen_date;
+int prev_mday, hour, minute;
+
+struct event *evlist, *active_event;
+struct event *deleted_event;
+struct list_entry_text *alarm_entries;
+time_t alarm_date = 0;
+int evcnt = -1;
+int moving = 0;
+struct sellist *alarm_selector;
+
+char *filename = NULL;
+
+PangoLayout *layout;
+GdkGC *colour = NULL;
+
+int size(struct list_entry *i, int *width, int*height)
+{
+       PangoRectangle ink, log;
+       struct list_entry_text *item = (void*)i;
+
+       if (i->height) {
+               *width = item->true_width;
+               *height = i->height;
+               return 0;
+       }
+       pango_layout_set_text(layout, item->text, -1);
+       pango_layout_get_extents(layout, &ink, &log);
+       *width = log.width / PANGO_SCALE;
+       *height = log.height / PANGO_SCALE;
+       item->true_width = i->width = *width;
+       i->height = *height;
+       return 0;
+}
+
+int render(struct list_entry *i, int selected, GtkWidget *d)
+{
+       PangoRectangle ink, log;
+       struct list_entry_text *item = (void*)i;
+       int x;
+       GdkColor col;
+
+       pango_layout_set_text(layout, item->text, -1);
+
+       if (colour == NULL) {
+               colour = gdk_gc_new(gtk_widget_get_window(d));
+               gdk_color_parse("purple", &col);
+               gdk_gc_set_rgb_fg_color(colour, &col);
+       }
+       if (selected) {
+               gdk_color_parse("pink", &col);
+               gdk_gc_set_rgb_fg_color(colour, &col);
+               gdk_draw_rectangle(gtk_widget_get_window(d),
+                                  colour, TRUE,
+                                  item->head.x, item->head.y,
+                                  item->head.width, item->head.height);
+               gdk_color_parse("purple", &col);
+               gdk_gc_set_rgb_fg_color(colour, &col);
+       }
+#if 0
+       x = (i->width - item->true_width)/2;
+       if (x < 0)
+               x = 0;
+#else
+       x = 0;
+#endif
+       gdk_draw_layout(gtk_widget_get_window(d),
+                       colour,
+                       item->head.x+x, item->head.y, layout);
+       return 0;
+}
+
+
+
+void set_cal(time_t then);
+
+void set_date(GtkButton *button, int num)
+{
+       set_cal(dlist[num]);
+}
+
+void prev_year(GtkButton *button)
+{
+       set_cal(chosen_date - 365*24*3600);
+}
+void next_year(GtkButton *button)
+{
+       set_cal(chosen_date + 365*24*3600);
+}
+
+void finish(void)
+{
+       gtk_main_quit();
+}
+
+void set_time(GtkButton *button, int num)
+{
+       struct tm now;
+       time_t then;
+       char buf[100];
+       int hr24, hr12;
+       char m = 'A';
+       if (num >= 100)
+               hour = num/100;
+       else
+               minute = num;
+
+       hr24 = hour;
+       if (hour == 24)
+               hr24 = 0;
+       hr12 = hour;
+       if (hr12 > 12) {
+               hr12 -= 12;
+               m = 'P';
+       }
+       if (hr12 == 12)
+               m = 'P' + 'A' - m;
+
+       then = chosen_date + hour * 3600 + minute * 60;
+       localtime_r(&then, &now);
+       strftime(buf, sizeof(buf), "%a, %d %B %Y, %H:%M", &now);
+       gtk_label_set_text(GTK_LABEL(date_display), buf);
+       gtk_label_set_text(GTK_LABEL(date_time_display), buf);
+#if 0
+       sprintf(buf, "%02d:%02dhrs\n%2d:%02d%cM", hr24, minute,
+               hr12, minute, m);
+       gtk_label_set_text(GTK_LABEL(time_label), buf);
+#endif
+}
+
+GtkWidget *add_button(GtkWidget **blist, char *name,
+               PangoFontDescription *fd,
+               GCallback handle)
+{
+       GtkWidget *l, *b;
+       if (*blist == NULL) {
+               *blist = gtk_hbox_new(TRUE, 0);
+               gtk_widget_show(*blist);
+               gtk_widget_set_size_request(*blist, -1, 80);
+       }
+       
+       l = *blist;
+
+       b = gtk_button_new_with_label(name);
+       gtk_widget_show(b);
+       pango_font_description_set_size(fd, 12 * PANGO_SCALE);
+       gtk_widget_modify_font(GTK_BIN(b)->child, fd);
+       gtk_container_add(GTK_CONTAINER(l), b);
+       g_signal_connect((gpointer)b, "clicked", handle, NULL);
+       return b;
+}
+int delay = 0;
+int show_time(void *d);
+int timer_tick = -1;
+void add_time(int mins);
+
+void load_timers(void);
+void select_timer(void)
+{
+       gtk_container_remove(GTK_CONTAINER(window), cal);
+       show_time(NULL);
+       delay = 0;
+       add_time(0);
+       load_timers();
+       gtk_container_add(GTK_CONTAINER(window), timerw);
+       timer_tick = g_timeout_add(1000, show_time, NULL);
+}
+
+void events_select(void)
+{
+       /* the selected event becomes current and can be moved */
+       if (!active_event)
+               return;
+       set_cal(active_event->when);
+}
+
+void move_confirm(void)
+{
+       struct tm now;
+       time_t then;
+       char buf[100];
+
+       if (active_event) {
+               then = active_event->when;
+               localtime_r(&then, &now);
+               hour = now.tm_hour;
+               minute = now.tm_min;
+       } else
+               hour = minute = 0;
+       then = chosen_date + hour * 3600 + minute * 60;
+       localtime_r(&then, &now);
+       strftime(buf, sizeof(buf), "%a, %d %B %Y, %H:%M", &now);
+       gtk_label_set_text(GTK_LABEL(date_display), buf);
+       gtk_label_set_text(GTK_LABEL(date_time_display), buf);
+
+       gtk_container_remove(GTK_CONTAINER(window), cal);
+       gtk_container_add(GTK_CONTAINER(window), clockw);
+}
+
+void events_move(void)
+{
+       if (!active_event)
+               return;
+
+       moving = 1;
+       set_cal(active_event->when);
+
+       gtk_widget_hide(event_buttons);
+       gtk_widget_show(move_buttons);
+
+       gtk_widget_hide(alarm_selector->drawing);
+       gtk_widget_show(move_event);
+
+       gtk_text_buffer_set_text(reason_buffer, active_event->mesg, -1);
+}
+
+void events_new(void)
+{
+       gtk_text_buffer_set_text(reason_buffer, "New Event", -1);
+       move_confirm();
+}
+
+void move_abort(void)
+{
+       gtk_widget_hide(move_buttons);
+       if (active_event) {
+               gtk_widget_hide(browse_buttons);
+               gtk_widget_show(event_buttons);
+       } else {
+               gtk_widget_hide(event_buttons);
+               gtk_widget_show(browse_buttons);
+       }
+
+       gtk_widget_show(alarm_selector->drawing);
+       gtk_widget_hide(move_event);
+       moving = 0;
+       if (active_event)
+               set_cal(active_event->when);
+       else
+               set_cal(chosen_date);
+
+}
+
+void cal_today(void)
+{
+       /* jump to today */
+       time_t now;
+       time(&now);
+       set_cal(now);
+       if (deleted_event) {
+               gtk_widget_hide(today_btn);
+               gtk_widget_show(undelete_btn);
+       }
+}
+
+void cal_restore(void)
+{
+       gtk_container_remove(GTK_CONTAINER(window), clockw);
+       gtk_container_add(GTK_CONTAINER(window), cal);
+}
+
+void clock_confirm(void)
+{
+       gtk_container_remove(GTK_CONTAINER(window), clockw);
+       gtk_container_add(GTK_CONTAINER(window), reasonw);
+}
+
+/********************************************************************/
+
+
+void event_free(struct event *ev)
+{
+       while (ev) {
+               struct event *n = ev->next;
+               free(ev->mesg);
+               free(ev);
+               ev = n;
+       }
+}
+
+void update_event(struct event *ev, time_t now)
+{
+       /* make sure 'when' is in the future if possible */
+       ev->when = ev->first;
+       while (ev->when < now && ev->recur > 0)
+               ev->when += ev->recur;
+}
+
+void update_events(struct event *ev, time_t now)
+{
+       while (ev) {
+               update_event(ev, now);
+               ev = ev->next;
+       }
+}
+
+void sort_events(struct event **evtp)
+{
+       /* sort events list in *evtp by ->when
+        * use merge sort
+        */
+       int cnt=0;
+
+       struct event *ev[2];
+       ev[0] = *evtp;
+       ev[1] = NULL;
+
+       do {
+               struct event **evp[2], *e[2];
+               int current = 0;
+               time_t prev = 0;
+               int next = 0;
+               cnt++;
+               evp[0] = &ev[0];
+               evp[1] = &ev[1];
+               e[0] = ev[0];
+               e[1] = ev[1];
+
+               /* take least of e[0] and e[1]
+                * if it is larger than prev, add to
+                * evp[current], else swap current then add
+                */
+               while (e[0] || e[1]) {
+                       if (e[next] == NULL ||
+                           (e[1-next] != NULL && 
+                            !((prev <= e[1-next]->when)
+                              ^(e[1-next]->when <= e[next]->when)
+                              ^(e[next]->when <= prev)))
+                               )
+                               next = 1 - next;
+
+                       if (e[next]->when < prev)
+                               current = 1 - current;
+                       prev = e[next]->when;
+                       *evp[current] = e[next];
+                       evp[current] = &e[next]->next;
+                       e[next] = e[next]->next;
+               }
+               *evp[0] = NULL;
+               *evp[1] = NULL;
+       } while (ev[0] && ev[1]);
+       if (ev[0])
+               *evtp = ev[0];
+       else
+               *evtp = ev[1];
+}
+
+struct event *event_parse(char *line)
+{
+       struct event *rv;
+       char *recur, *mesg;
+       struct tm tm;
+       long rsec;
+       recur = strchr(line, ':');
+       if (!recur)
+               return NULL;
+       *recur++ = 0;
+       mesg = strchr(recur, ':');
+       if (!mesg)
+               return NULL;
+       *mesg++ = 0;
+       line = strptime(line, "%Y-%m-%d-%H-%M-%S", &tm);
+       if (!line)
+               return NULL;
+       rsec = atoi(recur);
+       if (rsec < 0)
+               return NULL;
+       rv = malloc(sizeof(*rv));
+       rv->next = NULL;
+       tm.tm_isdst = -1;
+       rv->when = mktime(&tm);
+       rv->first = rv->when;
+       rv->recur = rsec;
+       rv->mesg = strdup(mesg);
+       return rv;
+}
+
+struct event *event_load_all(char *file)
+{
+       /* load the file and return a linked list */
+       char line[2048];
+       struct event *rv = NULL, *ev;
+       FILE *f;
+
+       f = fopen(file, "r");
+       if (!f)
+               return NULL;
+       while (fgets(line, sizeof(line), f) != NULL) {
+               char *eol = line + strlen(line);
+               if (eol > line && eol[-1] == '\n')
+                       eol[-1] = 0;
+
+               ev = event_parse(line);
+               if (!ev)
+                       continue;
+               ev->next = rv;
+               rv = ev;
+       }
+       return rv;
+}
+
+void event_save_all(char *file, struct event *list)
+{
+       FILE *f;
+       char *tmp = strdup(file);
+       char *c;
+
+       c = tmp + strlen(tmp);
+       while (c > tmp && c[-1] != '/')
+               c--;
+       if (*c) *c = '.';
+
+       f = fopen(tmp, "w");
+       while (list) {
+               struct event *ev = list;
+               struct tm *tm;
+               char buf[100];
+               list = ev->next;
+
+               tm = localtime(&ev->first);
+               strftime(buf, sizeof(buf), "%Y-%m-%d-%H-%M-%S", tm);
+               fprintf(f, "%s:%d:%s\n", buf, ev->recur, ev->mesg);
+       }
+       fflush(f);
+       fsync(fileno(f));
+       fclose(f);
+       rename(tmp, file);
+}
+
+
+void load_timers(void)
+{
+       time_t now = time(0);
+       struct event *timers = event_load_all("/etc/alarms/timer");
+       struct event *t;
+       char buf[1024];
+       int len = 0;
+
+       update_events(timers, now);
+       sort_events(&timers);
+       strcpy(buf,"");
+       for (t = timers ; t; t = t->next) {
+               struct tm *tm;
+               if (t->when < now)
+                       continue;
+               tm = localtime(&t->when);
+               strftime(buf+len, sizeof(buf)-len, "%H:%M\n", tm);
+               len += strlen(buf+len);
+       }
+       event_free(timers);
+       gtk_label_set_text(GTK_LABEL(timers_list), buf);
+}
+
+
+struct list_entry *alarms_item(void *list, int n)
+{
+       struct event *ev;
+
+       if (alarm_date != chosen_date) {
+               int i;
+               struct tm *tm, today;
+
+               if (evlist == NULL) {
+                       filename = "/home/neilb/ALARMS";
+                       evlist = event_load_all(filename);
+                       if (evlist == NULL) {
+                               filename = "/etc/alarms/cal";
+                               evlist = event_load_all(filename);
+                       }
+               }
+               update_events(evlist, chosen_date);
+               sort_events(&evlist);
+               evcnt = 0;
+               ev = evlist;
+               while (ev && ev->when < chosen_date)
+                       ev = ev->next;
+               for ( ; ev ; ev = ev->next)
+                       evcnt++;
+               free(alarm_entries);
+               alarm_entries = calloc(evcnt, sizeof(alarm_entries[0]));
+               ev = evlist;
+               while (ev && ev->when < chosen_date)
+                       ev = ev->next;
+               tm = localtime(&chosen_date);
+               today = *tm;
+               for (i=0; ev ; i++, ev = ev->next) {
+                       char buf[50], o, c;
+                       struct tm *tm = localtime(&ev->when);
+                       if (tm->tm_mday == today.tm_mday &&
+                           tm->tm_mon == today.tm_mon &&
+                           tm->tm_year == today.tm_year)
+                               strftime(buf, 50, "%H:%M\t", tm);
+                       else if (tm->tm_year == today.tm_year ||
+                                (tm->tm_year == today.tm_year + 1 &&
+                                 tm->tm_mon < today.tm_mon)
+                               )
+                               strftime(buf, 50, "%b%d\t", tm);
+                       else
+                               strftime(buf, 50, "%Y\t", tm);
+
+                       o = ' ';c=' ';
+                       if (ev->when < time(0)) {
+                               o = '(';
+                               c = ')';
+                       }
+                       asprintf(&alarm_entries[i].text, "%c%c %s %s%c",
+                                o,
+                                ev->recur ? '*' : ' ',
+                                buf, ev->mesg, c);
+                       alarm_entries[i].bg = "white";
+                       alarm_entries[i].fg = "blue";
+                       alarm_entries[i].underline = 0;
+               }
+               alarm_date = chosen_date;
+       }
+       if (n >= evcnt)
+               return NULL;
+
+       return &alarm_entries[n].head;
+}
+
+
+void events_delete(void)
+{
+       struct event **evp;
+       if (!active_event)
+               return;
+
+       for (evp = &evlist; *evp; evp = &(*evp)->next) {
+               if (*evp != active_event)
+                       continue;
+               *evp = active_event->next;
+               break;
+       }
+       if (deleted_event) {
+               free(deleted_event->mesg);
+               free(deleted_event);
+       }
+       deleted_event = active_event;
+       active_event = NULL;
+       event_save_all(filename, evlist);
+       alarm_date = 0;
+       move_abort();
+}
+
+void events_undelete(void)
+{
+       if (!deleted_event)
+               return;
+       active_event = deleted_event;
+       deleted_event = NULL;
+       active_event->next = evlist;
+       evlist = active_event;
+       event_save_all(filename, evlist);
+       alarm_date = 0;
+       move_abort();
+}
+
+void alarms_selected(void *list, int s)
+{
+       struct event *e;
+
+
+       if (s < 0 || s >= evcnt)
+               return;
+       e = evlist;
+       while (e && e->when < chosen_date)
+               e = e->next;
+       while (s && e) {
+               s--;
+               e = e->next;
+       }
+       active_event = e;
+
+       gtk_widget_hide(browse_buttons);
+       gtk_widget_show(event_buttons);
+}
+
+struct list_handlers alarms_han = {
+       .getitem = alarms_item,
+       .get_size = size,
+       .render = render,
+       .selected = alarms_selected,
+};
+
+
+GtkWidget *create_cal_window(void)
+{
+       GtkWidget *v, *t, *l;
+       GtkWidget *h;
+       GtkWidget *blist = NULL;
+       int i,r,c;
+       PangoFontDescription *desc;
+       struct sellist *sl;
+
+       desc = pango_font_description_new();
+       pango_font_description_set_size(desc, 11 * PANGO_SCALE);
+
+       v = gtk_vbox_new(FALSE, 0);
+       gtk_widget_show(v);
+
+       h = gtk_hbox_new(FALSE, 0);
+       gtk_container_add_with_properties(GTK_CONTAINER(v), h, "expand", 0, NULL);
+       gtk_widget_show(h);
+
+       l = gtk_button_new_with_label(" << ");
+       gtk_widget_modify_font(GTK_BIN(l)->child, desc);
+       gtk_button_set_relief(GTK_BUTTON(l), GTK_RELIEF_NONE);
+       g_signal_connect((gpointer)l, "clicked",
+                        G_CALLBACK(prev_year), NULL);
+       gtk_container_add(GTK_CONTAINER(h), l);
+       gtk_widget_show(l);
+       gtk_widget_set(l, "can-focus", 0, NULL);
+
+       l = gtk_label_new("Month 9999");
+       gtk_widget_modify_font(l, desc);
+       gtk_container_add(GTK_CONTAINER(h), l);
+       gtk_widget_show(l);
+       month = l;
+
+
+       l = gtk_button_new_with_label(" >> ");
+       gtk_widget_modify_font(GTK_BIN(l)->child, desc);
+       gtk_button_set_relief(GTK_BUTTON(l), GTK_RELIEF_NONE);
+       g_signal_connect((gpointer)l, "clicked",
+                        G_CALLBACK(next_year), NULL);
+       gtk_container_add(GTK_CONTAINER(h), l);
+       gtk_widget_show(l);
+       gtk_widget_set(l, "can-focus", 0, NULL);
+
+
+       t = gtk_table_new(7, 7, FALSE);
+       gtk_widget_show(t);
+
+       gtk_container_add_with_properties(GTK_CONTAINER(v), t, "expand", 0, NULL);
+
+       for (i=0; i<7; i++) {
+               l = gtk_label_new(days[i]);
+               gtk_widget_modify_font(l, desc);
+               gtk_widget_show(l);
+               gtk_table_attach(GTK_TABLE(t), l, i, i+1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
+       }
+
+       for (r=1; r<7; r++)
+               for (c=0; c<7; c++) {
+                       int p = (r-1)*7+c;
+                       l = gtk_button_new_with_label(" 99 ");
+                       gtk_widget_modify_font(GTK_BIN(l)->child, desc);
+                       gtk_button_set_relief(GTK_BUTTON(l), GTK_RELIEF_NONE);
+                       gtk_table_attach(GTK_TABLE(t), l, c,c+1, r,r+1, GTK_FILL, GTK_FILL, 0, 0);
+                       gtk_widget_show(l);
+                       g_signal_connect((gpointer)l, "clicked",
+                                        G_CALLBACK(set_date), (void*)(long)p);
+                       calblist[p] = l;
+                       dlist[p] = 0;
+               }
+
+       /* list of alarms from this date */
+       sl = listsel_new(NULL, &alarms_han);
+       pango_font_description_set_size(desc, 15 * PANGO_SCALE);
+       gtk_widget_modify_font(sl->drawing, desc);
+       gtk_container_add(GTK_CONTAINER(v), sl->drawing);
+       gtk_widget_show(sl->drawing);
+       alarm_selector = sl;
+
+       l = gtk_text_view_new_with_buffer(reason_buffer);
+       gtk_widget_modify_font(l, desc);
+       gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(l), GTK_WRAP_WORD_CHAR);
+       gtk_widget_hide(l);
+       gtk_container_add(GTK_CONTAINER(v), l);
+       move_event = l;
+
+
+       add_button(&blist, "timer", desc, select_timer);
+       add_button(&blist, "new", desc, events_new);
+       today_btn = add_button(&blist, "today", desc, cal_today);
+       undelete_btn = add_button(&blist, "undelete", desc, events_undelete);
+       gtk_widget_hide(undelete_btn);
+       gtk_box_pack_end(GTK_BOX(v), blist, FALSE, FALSE, 0);
+       browse_buttons = blist;
+       blist = NULL;
+
+       add_button(&blist, "go to", desc, events_select);
+       add_button(&blist, "change", desc, events_move);
+       add_button(&blist, "delete", desc, events_delete);
+       gtk_box_pack_end(GTK_BOX(v), blist, FALSE, FALSE, 0);
+       event_buttons = blist;
+       blist = NULL;
+
+       add_button(&blist, "confirm", desc, move_confirm);
+       add_button(&blist, "abort", desc, move_abort);
+       gtk_widget_hide(blist);
+       gtk_box_pack_end(GTK_BOX(v), blist, FALSE, FALSE, 0);
+
+       move_buttons = blist;
+
+       return v;
+}
+
+void set_cal(time_t then)
+{
+       struct tm now, first, *tm;
+       int d, x;
+       char buf[400];
+
+       localtime_r(&then, &now);
+
+       then -= now.tm_sec;
+       then -= now.tm_min*60;
+       then -= now.tm_hour*3600;
+       chosen_date = then;
+
+       localtime_r(&then, &now);
+
+       tm = localtime(&then);
+
+       strftime(buf, sizeof(buf), "%a, %d %B %Y", &now);
+       gtk_label_set_text(GTK_LABEL(date_display), buf);
+       
+       /* previous month */
+       while (tm->tm_mon == now.tm_mon) {
+               then -= 22*3600;
+               tm = localtime(&then);
+       }
+       /* Start of week */
+       while (tm->tm_wday != 0) {
+               then -= 22*3600;
+               tm = localtime(&then);
+       }
+       first = *tm;
+
+       if (abs(dlist[0] - then) > 48*3600) {
+               strftime(buf, 40, "%B %Y", &now);
+               gtk_label_set_text(GTK_LABEL(month), buf);
+       }
+
+       for (d=0; d<42; d++) {
+               char *bg = "", *fg = "black";
+
+               if (tm->tm_mon != now.tm_mon)
+                       fg = "grey";
+               else if (tm->tm_mday == now.tm_mday)
+                       bg = "background=\"green\"";
+               sprintf(buf, "<span %s foreground=\"%s\"> %02d </span>",
+                       bg, fg, tm->tm_mday);
+               if (abs(dlist[d] - then) > 48*3600 ||
+                   tm->tm_mday == now.tm_mday ||
+                   tm->tm_mday == prev_mday)
+                       gtk_label_set_markup(GTK_LABEL(GTK_BIN(calblist[d])->child), buf);
+               dlist[d] = then;
+               x = tm->tm_mday;
+               while (x == tm->tm_mday) {
+                       then += 22*3600;
+                       tm = localtime(&then);
+               }
+       }
+       prev_mday = now.tm_mday;
+
+       alarm_selector->selected = -1;
+       if (!moving) {
+               active_event = NULL;
+               gtk_widget_hide(event_buttons);
+               gtk_widget_show(browse_buttons);
+       }
+       g_signal_emit_by_name(alarm_selector->drawing, "configure-event",
+                             NULL, alarm_selector);
+
+
+       gtk_widget_show(today_btn);
+       gtk_widget_hide(undelete_btn);
+}
+
+void center_me(GtkWidget *label, void *xx, int pos)
+{
+       /* I have just been realised.  Find size and
+        * adjust position
+        */
+       GtkWidget *button = gtk_widget_get_parent(label);
+       GtkWidget *parent = gtk_widget_get_parent(button);
+       GtkAllocation alloc;
+       int x = pos / 10000;
+       int y = pos % 10000;
+
+       gtk_widget_get_allocation(button, &alloc);
+       printf("move %d %d/%d by %d/%d from %d/%d\n", pos, x, y,
+              alloc.width/2, alloc.height/2, alloc.x, alloc.y);
+       x -= alloc.width / 2;
+       y -= alloc.height / 2;
+       if (x != alloc.x || y != alloc.y) {
+       printf("move %d %d/%d by %d/%d from %d/%d\n", pos, x, y,
+              alloc.width/2, alloc.height/2, alloc.x, alloc.y);
+               gtk_fixed_move(GTK_FIXED(parent), button, x, y);
+       }
+}
+
+
+void add_nums(GtkWidget *f, int radius, int start, int end, int step,
+             int div, int scale,
+             char *colour, PangoFontDescription *desc)
+{
+       int i;
+       for (i=start; i<end; i+= step) {
+               char buf[400];
+               double a, s, c, r;
+               int x, y;
+               GtkWidget *l;
+               long scaled;
+               sprintf(buf, "<span background=\"%s\">%02d</span>", colour, i);
+               l = gtk_button_new_with_label(" 99 ");
+               gtk_widget_modify_font(GTK_BIN(l)->child, desc);
+               gtk_label_set_markup(GTK_LABEL(GTK_BIN(l)->child),
+                                    buf);
+               gtk_widget_show(l);
+               scaled = i * scale;
+               g_signal_connect((gpointer)l, "clicked",
+                                G_CALLBACK(set_time), (void*)scaled);
+               a = i * 2.0 * M_PI / div;
+               s = sin(a); c= cos(a);
+               r = (double)radius;
+               if (fabs(s) < fabs(c))
+                       r = r / fabs(c);
+               else
+                       r = r / fabs(s);
+               x = 210 + (int)(r * s) - r/18;
+               y = 210 - (int)(r * c) - r/18;
+               gtk_fixed_put(GTK_FIXED(f), l, x, y);
+       }
+}
+               
+GtkWidget *create_clock_window(void)
+{
+       PangoFontDescription *desc;
+       GtkWidget *f, *l, *v;
+       GtkWidget *blist = NULL;
+
+       desc = pango_font_description_new();
+       
+       v = gtk_vbox_new(FALSE, 0);
+       gtk_widget_show(v);
+
+       l = gtk_label_new(" Date "); /* Date for which time is being chosen */
+       pango_font_description_set_size(desc, 12 * PANGO_SCALE);
+       gtk_widget_modify_font(l, desc);
+       gtk_container_add_with_properties(GTK_CONTAINER(v), l, "expand", 0, NULL);
+       gtk_widget_show(l);
+       date_display = l;
+
+       f = gtk_fixed_new();
+       gtk_widget_show(f);
+       gtk_container_add(GTK_CONTAINER(v), f);
+
+       pango_font_description_set_size(desc, 17 * PANGO_SCALE);
+       add_nums(f, 200, 6, 18, 1, 12, 100, "light green", desc);
+       pango_font_description_set_size(desc, 15 * PANGO_SCALE);
+       add_nums(f, 140, 1, 6, 1, 12, 100, "light blue", desc);
+       add_nums(f, 140,18,25, 1, 12, 100, "light blue", desc);
+       pango_font_description_set_size(desc, 12 * PANGO_SCALE);
+       add_nums(f, 95, 0, 60, 5, 60, 1, "pink", desc);
+
+#if 0
+       l = gtk_label_new("09:00\n9:00AM");
+       hour = 9;
+       minute = 0;
+       pango_font_description_set_size(desc, 12 * PANGO_SCALE);
+       gtk_widget_modify_font(l, desc);
+       gtk_widget_show(l);
+       gtk_fixed_put(GTK_FIXED(f), l, 170,180);
+       time_label = l;
+#endif
+
+       add_button(&blist, "confirm", desc, clock_confirm);
+       add_button(&blist, "back", desc, cal_restore);
+       gtk_box_pack_end(GTK_BOX(v), blist, FALSE, FALSE, 0);
+
+       return v;
+}
+
+char *reasons[] = {"awake", "go", "meet", "buy", "call", "doctor", "dentist", "ok", "thanks", "coming"};
+struct list_entry_text *reason_entries;
+int selected_reason = -1;
+int num_reasons;
+
+struct list_entry *item(void *list, int n)
+{
+       if (n < num_reasons)
+               return &reason_entries[n].head;
+       else
+               return NULL;
+}
+void selected(void *list, int n)
+{
+       selected_reason = n;
+}
+
+struct list_handlers reason_han =  {
+       .getitem = item,
+       .get_size = size,
+       .render = render,
+       .selected = selected,
+};
+
+void reason_back(void)
+{
+       gtk_container_remove(GTK_CONTAINER(window), reasonw);
+       gtk_container_add(GTK_CONTAINER(window), clockw);
+}
+
+void reason_confirm(void)
+{
+       struct event *ev;
+       GtkTextIter start, finish;
+
+       if (!active_event) {
+               ev = malloc(sizeof(*ev));
+               ev->recur = 0;
+               ev->next = evlist;
+               evlist = ev;
+       } else {
+               ev = active_event;
+               free(ev->mesg);
+       }
+       ev->when = ev->first = chosen_date + hour*3600 + minute*60;
+       gtk_text_buffer_get_bounds(reason_buffer, &start, &finish);
+       ev->mesg = gtk_text_buffer_get_text(reason_buffer,
+                                           &start, &finish, FALSE);
+       gtk_container_remove(GTK_CONTAINER(window), reasonw);
+       gtk_container_add(GTK_CONTAINER(window), cal);
+
+       event_save_all(filename, evlist);
+
+       active_event = ev;
+       alarm_date = 0; /* force refresh */
+       move_abort();
+}
+
+void reason_select(void)
+{
+       if (selected_reason < 0 ||
+           selected_reason >= num_reasons)
+               return;
+       gtk_text_buffer_delete_selection(reason_buffer, TRUE, TRUE);
+       gtk_text_buffer_insert_at_cursor(reason_buffer, " ", 1);
+       gtk_text_buffer_insert_at_cursor(reason_buffer, reasons[selected_reason],
+                                        strlen(reasons[selected_reason]));
+}
+
+GtkWidget *create_reason_window(void)
+{
+       /* The "reason" window allows the reason for the alarm
+        * to be set.
+        * It has:
+        *  a label to show date and time
+        *  a text editing widget containing the reason
+        *  a selectable list of canned reasons
+        *  buttons for: confirm select clear
+        *
+        * confirm adds the alarm, or not if the reason is clear
+        * select adds the selected canned reason to the editor
+        * clear clears the reason
+        */
+
+       PangoFontDescription *desc;
+       GtkWidget *v, *blist=NULL, *l;
+       struct sellist *sl;
+       int i, num;
+
+       desc = pango_font_description_new();
+       v = gtk_vbox_new(FALSE, 0);
+       gtk_widget_show(v);
+
+       l = gtk_label_new(" Date and Time ");
+       pango_font_description_set_size(desc, 10 * PANGO_SCALE);
+       gtk_widget_modify_font(l, desc);
+       gtk_widget_show(l);
+       gtk_container_add_with_properties(GTK_CONTAINER(v), l, "expand", 0, NULL);
+       date_time_display = l;
+
+       l = gtk_text_view_new_with_buffer(reason_buffer);
+       gtk_widget_modify_font(l, desc);
+       gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(l), GTK_WRAP_WORD_CHAR);
+       gtk_widget_show(l);
+       gtk_container_add(GTK_CONTAINER(v), l);
+       gtk_text_buffer_set_text(reason_buffer, "Sample Reason", -1);
+
+       num = sizeof(reasons)/sizeof(reasons[0]);
+       reason_entries = calloc(num+1, sizeof(reason_entries[0]));
+       for (i=0; i<num; i++) {
+               reason_entries[i].text = reasons[i];
+               reason_entries[i].bg = "white";
+               reason_entries[i].fg = "blue";
+               reason_entries[i].underline = 0;
+       }
+       num_reasons = num;
+
+       sl = listsel_new(NULL, &reason_han);
+       pango_font_description_set_size(desc, 15 * PANGO_SCALE);
+       gtk_widget_modify_font(sl->drawing, desc);
+       gtk_container_add(GTK_CONTAINER(v), sl->drawing);
+       gtk_widget_show(sl->drawing);
+
+       add_button(&blist, "confirm", desc, reason_confirm);
+       add_button(&blist, "select", desc, reason_select);
+       add_button(&blist, "back", desc, reason_back);
+       gtk_box_pack_end(GTK_BOX(v), blist, FALSE, FALSE, 0);
+       return v;
+}
+
+void add_time(int mins)
+{
+       time_t now;
+       char buf[30];
+       struct tm *tm;
+       delay += mins;
+       if (delay <= 0)
+               delay = 0;
+       now = time(0) + (delay?:1)*60;
+       tm = localtime(&now);
+       sprintf(buf, "+%d:%02d - ",
+               delay/60, delay%60);
+       strftime(buf+strlen(buf),
+                sizeof(buf)-strlen(buf),
+                "%H:%M", tm);
+       gtk_label_set_text(GTK_LABEL(timer_display), buf);
+
+}
+void add_time_60(void) { add_time(60); }
+void add_time_10(void) { add_time(10); }
+void add_time_1(void) { add_time(1); }
+void del_time_60(void) { add_time(-60); }
+void del_time_10(void) { add_time(-10); }
+void del_time_1(void) { add_time(-1); }
+
+int show_time(void *d)
+{
+       time_t now;
+       char buf[30];
+       struct tm *tm;
+       now = time(0);
+       tm = localtime(&now);
+       strftime(buf, sizeof(buf), "%H:%M:%S", tm);
+       gtk_label_set_text(GTK_LABEL(time_display), buf);
+}
+
+void to_cal(void)
+{
+       gtk_container_remove(GTK_CONTAINER(window), timerw);
+       g_source_remove(timer_tick);
+       gtk_container_add(GTK_CONTAINER(window), cal);
+}
+void set_timer(void)
+{
+       FILE *f = fopen("/etc/alarms/timer", "a");
+       char buf[100];
+       time_t now = time(0) + delay*60;
+       struct tm *tm = localtime(&now);
+
+       strftime(buf, sizeof(buf), "%Y-%m-%d-%H-%M-%S::TIMER\n", tm);
+       if (f) {
+               fputs(buf, f);
+               fclose(f);
+       }
+       to_cal();
+}
+void clear_timers(void)
+{
+       unlink("/etc/alarms/timer");
+       to_cal();
+}
+
+GtkWidget *create_timer_window(void)
+{
+       /* The timer window lets you set a one-shot alarm
+        * some number of minutes in the future.
+        * There are buttons for +1hr +10 +1 and - all those
+        * The timer window also shows a large digital clock with seconds
+        */
+
+       PangoFontDescription *desc;
+       GtkWidget *v, *blist, *l;
+
+       desc = pango_font_description_new();
+       v = gtk_vbox_new(FALSE, 0);
+       gtk_widget_show(v);
+
+       l = gtk_label_new("  Timer ");
+       pango_font_description_set_size(desc, 25 * PANGO_SCALE);
+       gtk_widget_modify_font(l, desc);
+       gtk_widget_show(l);
+       gtk_container_add_with_properties(GTK_CONTAINER(v), l, "expand", 0, NULL);
+       
+       l = gtk_label_new(" 99:99:99 ");
+       pango_font_description_set_size(desc, 40 * PANGO_SCALE);
+       gtk_widget_modify_font(l, desc);
+       gtk_widget_show(l);
+       gtk_container_add_with_properties(GTK_CONTAINER(v), l, "expand", 0, NULL);
+       time_display = l;
+
+       l = gtk_label_new(" ");
+       pango_font_description_set_size(desc, 10 * PANGO_SCALE);
+       gtk_widget_modify_font(l, desc);
+       gtk_widget_show(l);
+       gtk_container_add_with_properties(GTK_CONTAINER(v), l, "expand", 1, NULL);
+       timers_list = l;
+
+
+       /* now from the bottom up */
+       blist = NULL;
+       pango_font_description_set_size(desc, 10 * PANGO_SCALE);
+       add_button(&blist, "Return", desc, to_cal);
+       add_button(&blist, "Set", desc, set_timer);
+       add_button(&blist, "Clear All", desc, clear_timers);
+       gtk_box_pack_end(GTK_BOX(v), blist, FALSE, FALSE, 0);
+
+       blist = NULL;
+       pango_font_description_set_size(desc, 10 * PANGO_SCALE);
+       add_button(&blist, "-1hr", desc, del_time_60);
+       add_button(&blist, "-10m", desc, del_time_10);
+       add_button(&blist, "-1m", desc, del_time_1);
+       gtk_box_pack_end(GTK_BOX(v), blist, FALSE, FALSE, 0);
+
+       
+       l = gtk_label_new("+1:34 - 99:99 ");
+       pango_font_description_set_size(desc, 15 * PANGO_SCALE);
+       gtk_widget_modify_font(l, desc);
+       gtk_widget_show(l);
+       
+       gtk_box_pack_end(GTK_BOX(v), l, FALSE, FALSE, 0);
+       timer_display = l;
+
+       blist = NULL;
+       pango_font_description_set_size(desc, 10 * PANGO_SCALE);
+       add_button(&blist, "+1hr", desc, add_time_60);
+       add_button(&blist, "+10m", desc, add_time_10);
+       add_button(&blist, "+1m", desc, add_time_1);
+       gtk_box_pack_end(GTK_BOX(v), blist, FALSE, FALSE, 0);
+
+       return v;
+}      
+
+main(int argc, char *argv[])
+{
+       GtkSettings *set;
+
+       gtk_set_locale();
+       gtk_init_check(&argc, &argv);
+
+       set = gtk_settings_get_default();
+       gtk_settings_set_long_property(set, "gtk-xft-dpi", 151 * PANGO_SCALE, "code");
+
+
+       window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+       gtk_window_set_default_size(GTK_WINDOW(window), 480, 640);
+       {
+       PangoContext *context;
+       PangoFontDescription *desc;
+       desc = pango_font_description_new();
+
+       pango_font_description_set_size(desc, 12 * PANGO_SCALE);
+       gtk_widget_modify_font(window, desc);
+       context = gtk_widget_get_pango_context(window);
+       layout = pango_layout_new(context);
+       }
+
+       reason_buffer = gtk_text_buffer_new(NULL);
+       cal = create_cal_window();
+       clockw = create_clock_window();
+       reasonw = create_reason_window();
+       timerw = create_timer_window();
+       gtk_container_add(GTK_CONTAINER(window), cal);
+       g_signal_connect((gpointer)window, "destroy",
+                        G_CALLBACK(finish), NULL);
+       gtk_widget_show(window);
+       gtk_widget_ref(cal);
+       gtk_widget_ref(clockw);
+       gtk_widget_ref(reasonw);
+       gtk_widget_ref(timerw);
+       set_cal(time(0));
+
+
+
+       gtk_main();
+}
diff --git a/alarm/cal.py b/alarm/cal.py
new file mode 100644 (file)
index 0000000..a1b732b
--- /dev/null
@@ -0,0 +1,149 @@
+
+import gtk
+import pango
+import time
+
+def choose_date(b, l):
+    print "choose", l
+    global selected, firstdate
+    t = firstdate
+    tm = time.mktime(t)
+    selected = t
+    for i in range(0,l):
+        while t[0:3] == selected[0:3]:
+            tm += 22*3600
+            t = time.localtime(tm)
+        selected = t
+    set_cal()
+        
+
+w = gtk.Window()
+v = gtk.VBox()
+v.show()
+t = gtk.Table(7, 7)
+w.add(v)
+
+fd = pango.FontDescription('sans 10')
+fd.set_absolute_size(28 * pango.SCALE)
+
+labels=[]
+c = 0
+for d in ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']:
+    l = gtk.Label(d)
+    l.modify_font(fd)
+    l.show()
+    t.attach(l, c, c+1, 0, 1, xpadding=3, ypadding=3)
+    c += 1
+for r in range(1,7):
+    for c in range(0,7):
+        l = gtk.Button(' 99 ')
+        l.set_relief(gtk.RELIEF_NONE)
+        l.child.modify_font(fd)
+        t.attach(l,c,c+1,r,r+1, xpadding=3, ypadding=3)
+        l.child.set_markup('<span background="red"> %02d </span>' % len(labels))
+        l.connect('clicked', choose_date, len(labels))
+        labels.append(l)
+        l.show()
+
+t.show()
+
+h = gtk.HBox()
+
+l = gtk.Label('Month')
+l.show()
+fd.set_absolute_size(25 * pango.SCALE)
+l.modify_font(fd)
+monthlabel = l
+
+
+def cal_fore(w):
+    global selected
+    t = selected
+    tm = time.mktime(t)
+    while t[1] == selected[1]:
+        tm += 22*3600
+        t = time.localtime(tm)
+    selected = t
+    set_cal()
+    
+def cal_back(w):
+    global selected
+    t = selected
+    tm = time.mktime(t)
+    while t[1] == selected[1]:
+        tm -= 22*3600
+        t = time.localtime(tm)
+    selected = t
+    set_cal()
+
+isize= gtk.icon_size_register('mine', 40, 40)
+backbutton = gtk.Button()
+img = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, isize)
+img.set_size_request(80, -1)
+img.show()
+backbutton.connect('clicked', cal_back)
+backbutton.add(img)
+backbutton.show()
+
+forebutton = gtk.Button()
+img = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, isize)
+img.set_size_request(80, -1)
+img.show()
+forebutton.connect('clicked', cal_fore)
+forebutton.add(img)
+forebutton.show()
+h.pack_start(backbutton, expand=False)
+h.pack_start(monthlabel)
+h.pack_start(forebutton, expand=False)
+v.pack_start(h, expand = False)
+h.show()
+
+b = gtk.Label(" ")
+b.show()
+v.pack_start(t, expand = False)
+v.pack_start(b, expand = True)
+
+w.show()
+
+def set_cal():
+    global selected, firstdate
+    nowt = selected
+    now = time.mktime(nowt)
+    # find end of previous month
+    t = nowt
+    while t[1] == nowt[1]:
+        now -= 22*3600
+        t = time.localtime(now)
+    # and start of week
+    while t[6] != 6:
+        now -= 22*3600
+        t = time.localtime(now)
+    firstdate = t
+
+    d = 0
+    while d < 6*7:
+        if t[1] == nowt[1]:
+            col = 'black'
+        else:
+            col = 'grey'
+        #print d, col, t
+        if nowt[0:3] == t[0:3]:
+            labels[d].child.set_markup('<span background="green" foreground="%s"> %02d </span>' % (col, t[2]))
+            #labels[d].child.set_text(" %02d "% t[2])
+        else:
+            labels[d].child.set_markup('<span foreground="%s"> %02d </span>' % (col, t[2]))
+            #labels[d].child.set_text("_%02d_"% t[2])
+            pass
+        x = t[2]
+        while x == t[2]:
+            now += 22*3600
+            t = time.localtime(now)
+        d += 1
+    monthlabel.set_markup(time.strftime('%B %Y', nowt))
+    
+selected = time.localtime()
+firstdate = selected
+set_cal()
+
+gtk.main()
+
diff --git a/alarm/clock.py b/alarm/clock.py
new file mode 100644 (file)
index 0000000..c57e4ad
--- /dev/null
@@ -0,0 +1,57 @@
+# try to draw clock labels.
+
+import gtk
+import math
+import pango
+
+w = gtk.Window()
+f = gtk.Fixed()
+w.add(f)
+f.show()
+
+fd = pango.FontDescription('sans 10')
+fd.set_absolute_size(40 * pango.SCALE)
+r = 220
+off=20
+for i in range(6,18):
+    l = gtk.Label()
+    l.set_markup('<span background="red">%2d</span>'%i)
+    l.modify_font(fd)
+    l.show()
+    a = i/6.0 * math.pi
+    x = math.sin(a)*r + 240 - off
+    y = -math.cos(a)*r + 320 - off
+    f.put(l, int(x), int(y))
+
+fd.set_absolute_size(25 * pango.SCALE)
+r = 170
+off = 12
+for i in range(1,6) + range(18,25):
+    l = gtk.Label("%d"%i)
+    l.set_markup('<span background="blue" foreground="white">%2d</span>'%i)
+    l.modify_font(fd)
+    l.show()
+    a = i/6.0 * math.pi
+    x = math.sin(a)*r + 240 - off
+    y = -math.cos(a)*r + 320 - off
+    f.put(l, int(x), int(y))
+
+fd.set_absolute_size(20 * pango.SCALE)
+r = 120
+off = 8
+for i in range(0,60,5):
+    l = gtk.Label("%d"%i)
+    l.set_markup('<span background="green">%2d</span>'%i)
+    l.modify_font(fd)
+    l.show()
+    a = i/30.0 * math.pi
+    x = math.sin(a)*r + 240 - off
+    y = -math.cos(a)*r + 320 - off
+    f.put(l, int(x), int(y))
+
+w.show()
+gtk.main()
+
+
+          
+    
diff --git a/alarm/listsel.c b/alarm/listsel.c
new file mode 100644 (file)
index 0000000..d6e20f5
--- /dev/null
@@ -0,0 +1,579 @@
+/*
+ * selectable, auto-scrolling list widget
+ *
+ * We display a list of texts and allow one to be selected,
+ * normally with a tap.
+ * The selected text is highlighted and the list auto-scrolls to
+ * ensure it is not too close to the edge, so no other scroll
+ * function is needed.
+ *
+ * The list is defined by a function that takes a number
+ * and returns the element.
+ * This element will point to a table of functions that
+ * measure the size of the entry and render it.  Some standard
+ * functions are provided to help with this.
+ * Each element has space to store current size/location for
+ * tap lookups.
+ */
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <string.h>
+#include <malloc.h>
+
+#include <gtk/gtk.h>
+
+#include "listsel.h"
+
+void calc_layout_no(struct sellist *list)
+{
+       /* choose an appropriate 'top', 'last' and cols
+        * set x,y,width,height on each entry in that range.
+        *
+        * Starting at 'sel' (or zero) we walk backwards
+        * towards 'top' assessing size.
+        * If we hit 'top' while in first 1/3 of display
+        * we target being just above bottom third
+        * If we don't reach 'top' while above bottom
+        * 1/3, then we target just below the top 1/3,
+        * having recorded where 'top' would be (When we first
+        * reached 1/3 from 'sel')
+        * Once we have chosen top, we move to end to find
+        * bottom.  If we hit end before bottom,
+        * we need to move top backwards if possible.
+        *
+        * So:
+        * - walk back from 'sel' until find
+        *    'top' or distance above sel exceeds 2/3
+        *    record location where distance was last < 1/3
+        * - if found top and distance < 1/3 and top not start,
+        *    step back while that is still < 1/3
+        * - elif distance exceeds 2/3, set top et al to the
+        *   found 1/3 position
+        * - move down from 'sel' until total distance is
+        *   height, or hit end.
+        * - if hit end, move top backwards while total distance
+        *   still less than height.
+        *
+        * columns add a complication as we don't know how
+        * many columns to use until we know the max width of
+        * the display choices.
+        * I think we assume cols based on selected entry,
+        * and as soon as we find something that decreases
+        * col count, start again from top.
+        *
+        * That doesn't work so well, particularly with multiple
+        * columns - we don't see the col-breaks properly.
+        * To see them, we really need to chose a firm starting
+        * place.
+        * So:
+        *  Start with 'top' at beginning and walk along.
+        *   If we hit end of list before bottom and top!=0,
+        *   walk backwards from end above bottom to set top.
+        *  If we find 'sel' after first 1/3 and before last - good.
+        *  If before first third, set 'sel' above last third
+        *  and walk back until find beginning or start, set top there.
+        *  If after last third, set 'sel' past first third,
+        *  walk back to top, set top there.
+        *
+        */
+       int top, last;
+       int sel;
+       int i;
+       int cols = 1, col, row;
+       struct list_entry *e;
+       int w, h;
+       int dist, pos, colwidth;
+       int altdist, alttop;
+
+       sel = list->selected;
+       if (sel < 0)
+               sel = 0;
+       e = list->han->getitem(list->list, sel);
+       if (e == NULL && sel > 0) {
+               sel = 0;
+               e = list->han->getitem(list->list, sel);
+       }
+       if (e == NULL)
+               /* list is empty */
+               return;
+
+       top = list->top;
+       if (top < 0)
+               top = 0;
+       if (top > sel)
+               top = sel;
+
+ retry_cols:
+       // did_scroll = 0;
+ retry_scroll:
+       /* lay things out from 'top' */
+       i = top; col = 0; row = 0;
+       while (col < cols) {
+               e = list->han->getitem(list->list, i);
+               if (e == NULL)
+                       break;
+               list->han->get_size(e, &w, &h);
+               e->x = col * list->width / cols;
+               e->y = row;
+               e->width = list->width / cols;
+               row += h;
+               if (row > list->height) {
+                       col++;
+                       e->x = col * list->width / cols;
+                       e->y = 0;
+                       row = h;
+               }
+               e->need_draw = 1;
+       }
+       
+       list->han->get_size(e, &w, &h);
+       cols = list->width / w;
+       if (cols == 0)
+               cols = 1;
+ col_retry:
+       dist = 0;
+       i = sel;
+       e = list->han->getitem(list->list, i);
+       list->han->get_size(e, &w, &h);
+       dist += h;
+
+       while (i > top && dist < (cols-1) * list->height + 2 * list->height/3) {
+               //printf("X i=%d dist=%d cols=%d lh=%d at=%d\n", i, dist, cols, list->height, alttop);
+               i--;
+               e = list->han->getitem(list->list, i);
+               list->han->get_size(e, &w, &h);
+               dist += h;
+               if (cols > 1 && w*cols > list->width) {
+                       cols --;
+                       goto col_retry;
+               }
+               if (dist * 3 < list->height) {
+                       alttop = i;
+                       altdist = dist;
+               }
+       }
+       if (i == top) {
+               if (dist*3 < list->height) {
+                       /* try to move to other end */
+                       while (i > 0 &&
+                              dist < (cols-1)*list->height * list->height*2/3) {
+                               i--;
+                               e = list->han->getitem(list->list, i);
+                               list->han->get_size(e, &w, &h);
+                               dist += h;
+                               if (cols > 1 && w*cols > list->width) {
+                                       cols--;
+                                       goto col_retry;
+                               }
+                               top = i;
+                       }
+               }
+       } else {
+               /* too near bottom */
+               top = alttop;
+               dist = altdist;
+       }
+
+       i = sel;
+       e = list->han->getitem(list->list, i);
+       list->han->get_size(e, &w, &h);
+       while (dist + h <= list->height * cols) {
+               dist += h;
+               i++;
+               //printf("Y i=%d dist=%d cols=%d\n", i, dist, cols);
+               e = list->han->getitem(list->list, i);
+               if (e == NULL)
+                       break;
+               list->han->get_size(e, &w, &h);
+               if (cols > 1 && w*cols > list->width) {
+                       cols --;
+                       goto col_retry;
+               }
+       }
+       if (e == NULL) {
+               /* near end, maybe move top up */
+               i = top;
+               while (i > 0) {
+                       i--;
+                       e = list->han->getitem(list->list, i);
+                       list->han->get_size(e, &w, &h);
+                       if (dist + h >= list->height * cols)
+                               break;
+                       dist += h;
+                       if (cols > 1 && w * cols > list->width) {
+                               cols--;
+                               goto col_retry;
+                       }
+                       top = i;
+               }
+       }
+
+       /* OK, we are going with 'top' and 'cols'. */
+       list->cols = cols;
+       list->top = top;
+       list->selected = sel;
+       /* set x,y,width,height for each
+        * width and height are set - adjust width and set x,y
+        */
+       col = 0;
+       pos = 0;
+       i = top;
+       colwidth = list->width / cols;
+       last = top;
+       while (col < cols) {
+               e = list->han->getitem(list->list, i);
+               if (e == NULL)
+                       break;
+               e->x = col * colwidth;
+               e->y = pos;
+               //printf("Set %d,%d  %d,%d\n", e->x, e->y, e->height, list->height);
+               pos += e->height;
+               if (pos > list->height) {
+                       pos = 0;
+                       col++;
+                       continue;
+               }
+               e->width = colwidth;
+               e->need_draw = 1;
+               last = i;
+               i++;
+       }
+       list->last = last;
+}
+
+void calc_layout(struct sellist *list)
+{
+       /* Choose 'top', and set 'last' and 'cols'
+        * so that 'selected' is visible in width/height,
+        * and is not in a 'bad' position.
+        * If 'sel' is in first column and ->y < height/3
+        * and 'top' is not zero, that is bad, we set top to zero.
+        * If 'sel' is in last column and ->Y > height/3
+        * and 'last' is not end of list, that is bad, we set
+        *  top to 'last'
+        * If 'sel' is before 'top', that is bad, set 'top' to 'sel'.
+        * If 'sel' is after 'last', that is bad, set 'top' to 'sel'.
+        */
+       int top = list->top;
+       int sel = list->selected;
+       int cols = 10000;
+       int col, row, pos, last;
+       struct list_entry *sele;
+       int did_jump = 0;
+
+ retry_cols:
+       /* make sure 'top' and 'sel' are valid */
+       top = list->top;
+       sel = list->selected;
+       did_jump = 0;
+       if (top < 0 || list->han->getitem(list->list, top) == NULL)
+               top = 0;
+       if (sel < 0 || list->han->getitem(list->list, sel) == NULL)
+               sel = 0;
+
+
+ retry:
+       //printf("try with top=%d cols=%d\n", top, cols);
+       pos = top; col=0; row=0; last= -1;
+       sele = NULL;
+       while(1) {
+               int w, h;
+               struct list_entry *e;
+
+               e = list->han->getitem(list->list, pos);
+               if (e == NULL)
+                       break;
+               if (pos == sel)
+                       sele = e;
+               list->han->get_size(e, &w, &h);
+               if (cols > 1 && w * cols > list->width) {
+                       /* too many columns */
+                       cols = list->width / w;
+                       if (cols <= 0)
+                               cols = 1;
+                       goto retry_cols;
+               }
+               e->x = col * list->width / cols;
+               e->y = row;
+               e->width = list->width / cols;
+               e->need_draw = 1;
+               row += h;
+               if (row > list->height && e->y > 0) {
+                       col ++;
+                       if (col == cols)
+                               break;
+                       e->x = col * list->width / cols;
+                       e->y = 0;
+                       row = h;
+               }
+               last = pos;
+               pos++;
+       }
+       /* Check if this is OK */
+       if (sele == NULL) {
+               //printf("no sel: %d %d\n", sel, top);
+               if (last <= top)
+                       goto out;
+               if (sel < top)
+                       top--;
+               else
+                       top++;
+               goto retry;
+       }
+       //printf("x=%d y=%d hi=%d lh=%d top=%d lw=%d cols=%d\n",
+       //       sele->x, sele->y, sele->height, list->height, top, list->width, cols);
+       if (sele->x == 0 && sele->y + sele->height < list->height / 3
+           && top > 0) {
+               if (did_jump)
+                       top --;
+               else {
+                       top = 0;
+                       did_jump = 1;
+               }
+               goto retry;
+       }
+       if (sele->x == (cols-1) * list->width / cols &&
+           sele->y + sele->height > list->height * 2 / 3 &&
+           col == cols) {
+               if (did_jump)
+                       top ++;
+               else {
+                       top = last;
+                       did_jump = 1;
+               }
+               goto retry;
+       }
+       if (col < cols && top > 0 &&
+           (col < cols - 1 || row < list->height / 2)) {
+               top --;
+               goto retry;
+       }
+ out:
+       list->top = top;
+       list->last = last;
+       list->cols = cols;
+       //printf("top=%d last=%d cols=%d\n", top, last, cols);
+}
+
+
+void configure(GtkWidget *draw, void *event, struct sellist *list)
+{
+       GtkAllocation alloc;
+
+       gtk_widget_get_allocation(draw, &alloc);
+#if 0
+       if (list->width == alloc.width &&
+           list->height == alloc.height)
+               return;
+#endif
+       list->width = alloc.width;
+       list->height = alloc.height;
+       calc_layout(list);
+       gtk_widget_queue_draw(draw);
+}
+
+void expose(GtkWidget *draw, GdkEventExpose *event, struct sellist *list)
+{
+       int i;
+       /* Draw all fields in the event->area */
+
+       for (i = list->top; i <= list->last; i++) {
+               struct list_entry *e = list->han->getitem(list->list, i);
+               if (e->x > event->area.x + event->area.width)
+                       /* to the right */
+                       continue;
+               if (e->x + e->width < event->area.x)
+                       /* to the left */
+                       continue;
+               if (e->y > event->area.y + event->area.height)
+                       /* below */
+                       continue;
+               if (e->y + e->height < event->area.y)
+                       /* above */
+                       continue;
+
+               e->need_draw = 1;
+               if (event->count == 0 && e->need_draw == 1) {
+                       e->need_draw = 0;
+                       list->han->render(e, i == list->selected, list->drawing);
+               }
+       }
+}
+
+void tap(GtkWidget *draw, GdkEventButton *event, struct sellist *list)
+{
+       int i;
+       for (i=list->top; i <= list->last; i++) {
+               struct list_entry *e = list->han->getitem(list->list, i);
+               if (event->x >= e->x &&
+                   event->x < e->x + e->width &&
+                   event->y >= e->y &&
+                   event->y < e->y + e->height)
+               {
+                       //printf("found item %d\n", i);
+                       list->selected = i;
+                       calc_layout(list);
+                       gtk_widget_queue_draw(list->drawing);
+                       list->han->selected(list, i);
+                       break;
+               }
+       }
+}
+
+void *listsel_new(void *list, struct list_handlers *han)
+{
+       struct sellist *rv;
+
+       rv = malloc(sizeof(*rv));
+       rv->list = list;
+       rv->han = han;
+       rv->drawing = gtk_drawing_area_new();
+       rv->top = 0;
+       rv->selected = -1;
+       rv->width = rv->height = 0;
+       rv->cols = 1;
+
+       g_signal_connect((gpointer)rv->drawing, "expose-event",
+                        G_CALLBACK(expose), rv);
+       g_signal_connect((gpointer)rv->drawing, "configure-event",
+                        G_CALLBACK(configure), rv);
+
+       gtk_widget_add_events(rv->drawing,
+                             GDK_BUTTON_PRESS_MASK|
+                             GDK_BUTTON_RELEASE_MASK);
+       g_signal_connect((gpointer)rv->drawing, "button_release_event",
+                        G_CALLBACK(tap), rv);
+
+       return rv;
+}
+
+
+#ifdef MAIN
+
+struct list_entry_text *entries;
+int entcnt;
+PangoFontDescription *fd;
+PangoLayout *layout;
+GdkGC *colour;
+
+/*
+ * gdk_color_parse("green", GdkColor *col);
+ * gdk_colormap_alloc_color(GdkColormap, GdkColor, writeable?, bestmatch?)
+ * gdk_colormap_get_system
+ *
+ * gdk_gc_new(drawable)
+ * gdk_gc_set_foreground(gc, color)
+ * gdk_gc_set_background(gc, color)
+ * gdk_gc_set_rgb_fg_color(gc, color)
+ */
+struct list_entry *item(void *list, int n)
+{
+       if (n < entcnt)
+               return &entries[n].head;
+       else
+               return NULL;
+}
+int size(struct list_entry *i, int *width, int*height)
+{
+       PangoRectangle ink, log;
+       struct list_entry_text *item = (void*)i;
+
+       if (i->height) {
+               *width = item->true_width;
+               *height = i->height;
+               return 0;
+       }
+       //printf("calc for %s\n", item->text);
+       pango_layout_set_text(layout, item->text, -1);
+       pango_layout_get_extents(layout, &ink, &log);
+       //printf("%s %d %d\n", item->text, log.width, log.height);
+       *width = log.width / PANGO_SCALE;
+       *height = log.height / PANGO_SCALE;
+       item->true_width = i->width = *width;
+       i->height = *height;
+       return 0;
+}
+
+int render(struct list_entry *i, int selected, GtkWidget *d)
+{
+       PangoRectangle ink, log;
+       struct list_entry_text *item = (void*)i;
+       int x;
+       GdkColor col;
+
+       pango_layout_set_text(layout, item->text, -1);
+
+       if (colour == NULL) {
+               colour = gdk_gc_new(gtk_widget_get_window(d));
+               gdk_color_parse("purple", &col);
+               gdk_gc_set_rgb_fg_color(colour, &col);
+       }
+       if (selected) {
+               gdk_color_parse("pink", &col);
+               gdk_gc_set_rgb_fg_color(colour, &col);
+               gdk_draw_rectangle(gtk_widget_get_window(d),
+                                  colour, TRUE,
+                                  item->head.x, item->head.y,
+                                  item->head.width, item->head.height);
+               gdk_color_parse("purple", &col);
+               gdk_gc_set_rgb_fg_color(colour, &col);
+       }
+       x = (i->width - item->true_width)/2;
+       if (x < 0)
+               x = 0;
+       gdk_draw_layout(gtk_widget_get_window(d),
+                       colour,
+                       item->head.x+x, item->head.y, layout);
+       return 0;
+}
+void selected(void *list, int n)
+{
+       printf("got %d\n", n);
+}
+
+struct list_handlers myhan =  {
+       .getitem = item,
+       .get_size = size,
+       .render = render,
+       .selected = selected,
+};
+
+main(int argc, char *argv[])
+{
+       int i;
+       GtkWidget *w;
+       struct sellist *l;
+       PangoContext *context;
+
+       entries = malloc(sizeof(*entries) * argc);
+       memset(entries, 0, sizeof(*entries)*argc);
+       for (i=1; i<argc; i++) {
+               entries[i-1].text = argv[i];
+               entries[i-1].bg = "white";
+               entries[i-1].fg = "blue";
+               entries[i-1].underline = 0;
+       }
+       entcnt = i-1;
+
+       gtk_set_locale();
+       gtk_init_check(&argc, &argv);
+
+       fd = pango_font_description_new();
+       pango_font_description_set_size(fd, 35 * PANGO_SCALE);
+
+       w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+       gtk_window_set_default_size(GTK_WINDOW(w), 480, 640);
+
+       gtk_widget_modify_font(w, fd);
+       context = gtk_widget_get_pango_context(w);
+       layout = pango_layout_new(context);
+
+       l = listsel_new(NULL, &myhan);
+       gtk_container_add(GTK_CONTAINER(w), l->drawing);
+       gtk_widget_show(l->drawing);
+       gtk_widget_show(w);
+
+       gtk_main();
+}
+#endif
diff --git a/alarm/listsel.h b/alarm/listsel.h
new file mode 100644 (file)
index 0000000..0933deb
--- /dev/null
@@ -0,0 +1,39 @@
+
+struct sellist {
+       void *list;
+       struct list_handlers *han;
+       GtkWidget *drawing;
+
+       int top;        /* Index of first element displayed */
+       int selected;   /* Index of currently selected element */
+       int last;       /* Index of last displayed element */
+
+       int width, height; /* Pixel size of widget */
+       int cols;       /* Columns */
+};
+
+struct list_handlers {
+       struct list_entry *(*getitem)(void *list, int n);
+       int (*get_size)(struct list_entry *item, int *width, int *height);
+       int (*render)(struct list_entry *item, int selected,
+                     GtkWidget *d);
+       void (*selected)(void *list, int element);
+};
+
+struct list_entry {
+       int x, y, width, height;
+       int need_draw;
+};
+
+struct list_entry_text {
+       struct list_entry head;
+       char *text;
+       
+       int true_width;
+       char *bg, *fg;
+       int underline;
+};
+
+extern void *listsel_new(void *list, struct list_handlers *han);
+
+
diff --git a/alarm/notes b/alarm/notes
new file mode 100644 (file)
index 0000000..0178b33
--- /dev/null
@@ -0,0 +1,161 @@
+TODO:
+ - delete events
+DONE - highlight "before" and "after" 'now' via colour
+ - edit recurrance
+ - display multiple occurances?
+ - handle newline chars
+ - when editing recurring event, reset to 'start' time? or create sub event??
+
+ - when changing an event, we don't need the final 'description' page.
+DONE - time keeps moving around!
+
+Recurrance options:
+  daily, weekly, forthnightly, monthly, yearly
+  1 2 3 4
+
+
+I need an alarm clock.
+ffalarms looks nice, but it uses fso, so not for me.
+
+I want two separate parts: the background service and the UI.
+
+Service makes sure we wake up (and don't suspend) and
+sounds an alert or delivers a message or whatever.
+
+UI lists events, edits events, and creates/deletes events.
+
+They communicate through the events file which the UI edits
+and through the sms delivery system.
+
+All times are localtime
+
+time:recurrance:message
+
+Possibly want 'style' for silent alarms or insistent alarms.
+
+For UI I need:
+
+ - list of current alarms
+      Buttons for "edit", "delete", "new"
+
+ - new or edit go to 
+   calendar for setting date of alarm - defaults to today
+   buttons for next, prev, today, select nexty, prevy, back
+   maybe press-and-hold 'next' goes to next year.
+   or there is a month/year toggle
+
+    month/year    prev     next
+    back        today      select
+
+ - then
+   clock face for hour and minute
+     outer circle: 6am to 6pm
+     Inner:  6pm to 6am
+     innner: minutes:
+
+
+             11   12   13
+              23  24   1
+         10 22    00     2 14
+            
+          9 21 45    15  3 15
+            
+          8 20    30     4 16
+             19   18   5
+              7   6    17
+
+      buttons for 'back', 'select'
+
+  - then
+    list of possible messages, or text entry
+
+
+But: I want the calendar to be more generally accessible.  Maybe it could be used
+as a lookup option.
+So: 
+  display calendar:
+  dates with events are highlighted
+Buttons for:
+  new show
+
+So: a design.
+
+ - We use /etc/alarms as our database for now.  Might enhance that later
+ - First window is calendar.  Has buttons for:
+  + view : shows list of events on or after that month 
+  + new : create an event on that day, shows clock,
+    or 'time' if we are moving an event
+  + today : jumps to today
+
+ - view window:
+    selectable auto-scrolling list of events, shows date, time, message, recurrance
+
+   Buttons:
+     delete:  flags event as deleted (in recurrence)
+     undelete: undeletes all deleted events (they get purged after a day)
+     move: go to calendar to choose new date, then time
+
+ - clock window
+    allows time to be selected.  Shows date at top
+   buttons:
+     back  - go back to calendar
+     confirm - go to 'reason' page
+
+ - reason page:
+    date and time at top
+    selectable scrolling list of 'reasons'
+    entry box to type in a new reason
+   Buttons:
+        clear, confirm, abort
+              
+
+------------------------------------------
+Revision of UI after some experimentation.
+
+Browse mode:
+  Displays calendar with one day selected.
+  Below calendar are a list of events on or after that day.  This is an
+  auto-scrolling selectable list.
+  Today's events have time, future events have date, or just year.
+  Date changes change date and update list of events.
+
+  Buttons:
+    select: jump day to selected event
+    move:  jump day to selected event and switch to 'move' mode
+    new:  create new event on selected day and switch to 'move' mode
+    today:  jump to today
+
+Move mode:
+  Same calendar display as Browse mode
+  Only text of selected event is displayed below
+  date changes change the start date of the event
+
+  Buttons:
+    abort:  cancel any pending changes and return to browse mode
+    confirm:   switch to time-set mode
+
+time-set mode:
+  Display clock-face for selecting hour and minute (to 5 minute resolution)
+  date and text of message are above
+
+  selecting a time changes the time of the event
+
+  Buttons:
+    Back:  go back to move mode
+    confirm: switch to message-set mode
+
+message-set mode:
+  Date and time at top
+  Editable message box in top half of display
+  selectable list of common words in bottom half
+
+  Buttons:
+   back: go back
+   select: add the selected word
+   confirm: add/move the message.
+
+
+BUT: what about recurring alarms?  somehow we need to select:
+ - daily, weekly, monthly yearly,
+   1 2 3 4 5 6
+   1st 2nd 3rd 4th 5th