From b79848315ca77f35c2cdc69e909569007f03da44 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Sun, 6 Feb 2011 20:26:11 +1100 Subject: [PATCH] alarm: newly added Both C and Python code here. The C code is maintained, the python was too slot Signed-off-by: NeilBrown --- alarm/Makefile | 22 + alarm/alarm.c | 370 +++++++++++++++ alarm/cal.c | 1214 +++++++++++++++++++++++++++++++++++++++++++++++ alarm/cal.py | 149 ++++++ alarm/clock.py | 57 +++ alarm/listsel.c | 579 ++++++++++++++++++++++ alarm/listsel.h | 39 ++ alarm/notes | 161 +++++++ 8 files changed, 2591 insertions(+) create mode 100644 alarm/Makefile create mode 100644 alarm/alarm.c create mode 100644 alarm/cal.c create mode 100644 alarm/cal.py create mode 100644 alarm/clock.py create mode 100644 alarm/listsel.c create mode 100644 alarm/listsel.h create mode 100644 alarm/notes diff --git a/alarm/Makefile b/alarm/Makefile new file mode 100644 index 0000000..66795b6 --- /dev/null +++ b/alarm/Makefile @@ -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 index 0000000..d134a15 --- /dev/null +++ b/alarm/alarm.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 index 0000000..dccdce7 --- /dev/null +++ b/alarm/cal.c @@ -0,0 +1,1214 @@ +#define _XOPEN_SOURCE +#define _GNU_SOURCE + +#include +#include + +#include +#include +#include +#include + +#include + +#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, " %02d ", + 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%02d", 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; idrawing, 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 index 0000000..a1b732b --- /dev/null +++ b/alarm/cal.py @@ -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(' %02d ' % 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(' %02d ' % (col, t[2])) + #labels[d].child.set_text(" %02d "% t[2]) + else: + labels[d].child.set_markup(' %02d ' % (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 index 0000000..c57e4ad --- /dev/null +++ b/alarm/clock.py @@ -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('%2d'%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('%2d'%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('%2d'%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 index 0000000..d6e20f5 --- /dev/null +++ b/alarm/listsel.c @@ -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 +#include + +#include +#include + +#include + +#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; idrawing); + 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 index 0000000..0933deb --- /dev/null +++ b/alarm/listsel.h @@ -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 index 0000000..0178b33 --- /dev/null +++ b/alarm/notes @@ -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 -- 2.39.5