--- /dev/null
+#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();
+}
--- /dev/null
+/*
+ * 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