Add to git.
[monolith.git] / calendar / ml_calendar_day.c
1 /* Monolith calendar widget.
2  * - by Richard W.M. Jones <rich@annexia.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  *
18  * $Id: ml_calendar_day.c,v 1.7 2003/02/22 15:34:25 rich Exp $
19  */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <assert.h>
26
27 #ifdef HAVE_TIME_H
28 #include <time.h>
29 #endif
30
31 #ifdef HAVE_STRING_H
32 #include <string.h>
33 #endif
34
35 #include <pool.h>
36 #include <pstring.h>
37
38 #include <pthr_dbi.h>
39
40 #include <monolith.h>
41 #include <ml_widget.h>
42 #include <ml_window.h>
43 #include <ml_dialog.h>
44 #include <ml_iframe.h>
45 #include <ml_button.h>
46 #include <ml_table_layout.h>
47 #include <ml_flow_layout.h>
48 #include <ml_form_layout.h>
49 #include <ml_text_label.h>
50 #include <ml_form.h>
51 #include <ml_form_input.h>
52 #include <ml_form_text.h>
53 #include <ml_form_textarea.h>
54 #include <ml_form_select.h>
55 #include <ml_form_submit.h>
56
57 #include "ml_calendar_lib.h"
58 #include "ml_calendar_day.h"
59
60 static void repaint (void *, ml_session, const char *, io_handle);
61
62 struct ml_widget_operations calendar_day_ops =
63   {
64     repaint: repaint
65   };
66
67 struct ml_calendar_day
68 {
69   struct ml_widget_operations *ops;
70   pool pool;                    /* Pool for allocations. */
71   ml_session session;           /* Current session. */
72   const char *conninfo;         /* Database connection. */
73   ml_calendar calendar;         /* Parent calendar. */
74   int yyyy, mm, dd;             /* Current date. */
75
76   ml_iframe iframe;             /* The iframe widget. */
77   ml_table_layout tbl;          /* Top-level layout. */
78   ml_button b[24][2];           /* One button for each half hour of the day. */
79
80   /* This is the new event form: */
81   ml_form_select f_resid;       /* Which calendar. */
82   vector f_resids;              /* List of calendars. */
83   ml_form_select f_start;       /* Start time. */
84   ml_form_select f_length;      /* Length. */
85   ml_form_text f_subject;       /* Subject (title). */
86   ml_form_textarea f_body;      /* Body / note. */
87
88   /* The above fields are also used on the edit event form, plus these: */
89   int eventid;                  /* Event ID we are editing. */
90   ml_form_submit edit, delete;  /* Which button was pressed. */
91 };
92
93 /* This is the parameter to the time_button_press function. */
94 struct time_button_data
95 {
96   int hh, mm;
97   ml_calendar_day w;
98 };
99
100 /* This is the parameter to the event_button_press function. */
101 struct event_button_data
102 {
103   int eventid;
104   ml_calendar_day w;
105 };
106
107 static pool cal_day_pool;       /* Pool for global allocations. */
108 static vector start_times;      /* List of start times (for forms). */
109 static vector lengths;          /* List of lengths (for forms). */
110 static ml_text_label spacer;    /* Used for spacing things out. */
111
112 /* The global start_times vector contains these. */
113 struct start_times_entry
114 {
115   const char *text;             /* Text label. */
116   int hh, mm;                   /* Actual start time. */
117 };
118
119 /* The global lengths vector contains these. */
120 struct lengths_entry
121 {
122   const char *text;             /* Text label. */
123   int hh, mm;                   /* Actual length in hours, mins. */
124 };
125
126 static void calendar_day_init (void) __attribute__ ((constructor));
127 static void calendar_day_stop (void) __attribute__ ((destructor));
128
129 static void make_window (ml_session session, void *vw);
130 static void update_table (ml_calendar_day w);
131 static void time_button_press (ml_session session, void *vdata);
132 static void add_event (ml_session session, void *vw);
133 static void event_button_press (ml_session session, void *vdata);
134 static void edit_event (ml_session session, void *vw);
135
136 static void
137 calendar_day_init ()
138 {
139   struct start_times_entry se;
140   struct lengths_entry le;
141   int hh, mm;
142
143   cal_day_pool = new_subpool (global_pool);
144   start_times = new_vector (cal_day_pool, struct start_times_entry);
145   lengths = new_vector (cal_day_pool, struct lengths_entry);
146
147   /* Populate the start_times array. */
148   for (hh = 0; hh < 24; ++hh)
149     for (mm = 0; mm < 60; mm += 15)
150       {
151         se.text = psprintf (cal_day_pool, "%02d:%02d", hh, mm);
152         se.hh = hh;
153         se.mm = mm;
154         vector_push_back (start_times, se);
155       }
156
157   /* Populate the lengths array. */
158   le.text = "&frac14 hour"; le.hh = 0; le.mm = 15;
159   vector_push_back (lengths, le);
160   le.text = "&frac12 hour"; le.hh = 0; le.mm = 30;
161   vector_push_back (lengths, le);
162   le.text = "1 hour"; le.hh = 1; le.mm = 0;
163   vector_push_back (lengths, le);
164   le.text = "1&frac12 hours"; le.hh = 1; le.mm = 30;
165   vector_push_back (lengths, le);
166   le.text = "2 hours"; le.hh = 2; le.mm = 0; vector_push_back (lengths, le);
167   le.text = "3 hours"; le.hh = 3; le.mm = 0; vector_push_back (lengths, le);
168   le.text = "4 hours"; le.hh = 4; le.mm = 0; vector_push_back (lengths, le);
169   le.text = "5 hours"; le.hh = 5; le.mm = 0; vector_push_back (lengths, le);
170   le.text = "6 hours"; le.hh = 6; le.mm = 0; vector_push_back (lengths, le);
171   le.text = "7 hours"; le.hh = 7; le.mm = 0; vector_push_back (lengths, le);
172   le.text = "8 hours"; le.hh = 8; le.mm = 0; vector_push_back (lengths, le);
173   le.text = "9 hours"; le.hh = 9; le.mm = 0; vector_push_back (lengths, le);
174   le.text = "10 hours"; le.hh = 10; le.mm = 0; vector_push_back (lengths, le);
175   le.text = "11 hours"; le.hh = 11; le.mm = 0; vector_push_back (lengths, le);
176   le.text = "12 hours"; le.hh = 12; le.mm = 0; vector_push_back (lengths, le);
177
178   /* This is just used to space events out. */
179   spacer = new_ml_text_label (cal_day_pool, " ");
180 }
181
182 static void
183 calendar_day_stop ()
184 {
185   delete_pool (cal_day_pool);
186 }
187
188 ml_calendar_day
189 new_ml_calendar_day (pool pool, ml_session session, const char *conninfo,
190                      ml_calendar calendar)
191 {
192   ml_calendar_day w = pmalloc (pool, sizeof *w);
193   struct time_button_data *data;
194   int hh, mm, i;
195
196   w->ops = &calendar_day_ops;
197   w->pool = pool;
198   w->session = session;
199   w->conninfo = conninfo;
200   w->calendar = calendar;
201
202   /* The parent calendar must call ml_calendar_day_set_date to set these
203    * before using this widget.
204    */
205   w->yyyy = w->mm = w->dd = -1;
206
207   /* The widget is really an iframe. */
208   w->iframe = new_ml_iframe (pool, make_window, session, w, 0);
209   ml_widget_set_property (w->iframe, "height", 500);
210   ml_widget_set_property (w->iframe, "width", 400);
211
212   /* Create the top-level widgets. */
213   w->tbl = new_ml_table_layout (pool, 48, 2);
214
215   /* Create the buttons and populate them in the table widget. */
216   for (hh = 0; hh < 24; ++hh)
217     for (mm = i = 0; mm <= 30; mm += 30, i++)
218       {
219         data = pmalloc (pool, sizeof *data);
220         data->hh = hh;
221         data->mm = mm;
222         data->w = w;
223
224         if (i == 0)
225           w->b[hh][0] = new_ml_button (pool, psprintf (pool, "%02d:00", hh));
226         else
227           w->b[hh][1] = new_ml_button (pool, "---");
228         ml_button_set_callback (w->b[hh][i], time_button_press, session, data);
229         ml_button_set_popup (w->b[hh][i], "ml_calendar_day_add_event");
230         ml_button_set_popup_size (w->b[hh][i], 640, 300);
231         ml_widget_set_property (w->b[hh][i], "button.style", "link");
232         if (hh >= 9 && hh < 18)
233           ml_widget_set_property (w->b[hh][i], "class", "ml_calendar_hour");
234         else
235           ml_widget_set_property (w->b[hh][i], "class",
236                                   "ml_calendar_hour_off_peak");
237         ml_table_layout_pack (w->tbl, w->b[hh][i], hh*2+i, 0);
238       }
239
240   return w;
241 }
242
243 void
244 ml_calendar_day_set_date (ml_calendar_day w, int yyyy, int mm, int dd)
245 {
246   w->yyyy = yyyy;
247   w->mm = mm;
248   w->dd = dd;
249
250   update_table (w);
251 }
252
253 /* Create the internal window.
254  * XXX This could be more efficiently written. It creates a window each
255  * time the widget is redrawn.
256  */
257 static void
258 make_window (ml_session session, void *vw)
259 {
260   ml_calendar_day w = (ml_calendar_day) vw;
261   ml_window win;
262
263   win = new_ml_window (session, w->pool);
264   ml_window_pack (win, w->tbl);
265   ml_window_scroll_to (win, 0, 360);
266 }
267
268 /* This form is used for adding events to the calendar. */
269 static void
270 time_button_press (ml_session session, void *vdata)
271 {
272   struct time_button_data *data = (struct time_button_data *) vdata;
273   ml_calendar_day w = data->w;
274   db_handle dbh;
275   st_handle sth;
276   ml_window win;
277   ml_form form;
278   ml_form_layout tbl;
279   ml_form_submit submit;
280   int i, resid;
281   const char *resname;
282
283   win = new_ml_window (w->session, w->pool);
284   form = new_ml_form (w->pool);
285   tbl = new_ml_form_layout (w->pool);
286
287   /* Set the callback on the form. */
288   ml_form_set_callback (form, add_event, session, w);
289   ml_widget_set_property (form, "method", "GET");
290
291   /* Pull out the list of calendar names from the database, and
292    * use this to populate the calendar drop-down menu.
293    */
294   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
295
296   w->f_resid = new_ml_form_select (w->pool, form);
297   w->f_resids = new_vector (w->pool, int);
298
299   sth = st_prepare_cached
300     (dbh,
301      "select c.resid, r.name from ml_calendar c, ml_resources r "
302      "where c.resid in (@) and c.resid = r.resid order by 2",
303      DBI_VECTOR_INT);
304   st_execute (sth, _ml_calendar_get_calendars (w->calendar));
305
306   st_bind (sth, 0, resid, DBI_INT);
307   st_bind (sth, 1, resname, DBI_STRING);
308
309   while (st_fetch (sth))
310     {
311       vector_push_back (w->f_resids, resid);
312       ml_form_select_push_back (w->f_resid, resname);
313       /* XXX Colours. */
314     }
315   ml_form_layout_pack (tbl, "Calendar: ", w->f_resid);
316
317   put_db_handle (dbh);
318
319   /* Start time. */
320   w->f_start = new_ml_form_select (w->pool, form);
321
322   for (i = 0; i < vector_size (start_times); ++i)
323     {
324       struct start_times_entry se;
325
326       vector_get (start_times, i, se);
327       ml_form_select_push_back (w->f_start, se.text);
328       if (se.hh == data->hh && se.mm == data->mm)
329         ml_form_select_set_selection (w->f_start,
330                                       ml_form_select_size (w->f_start) - 1);
331     }
332   ml_form_layout_pack (tbl, "Start time: ", w->f_start);
333
334   /* Length. */
335   w->f_length = new_ml_form_select (w->pool, form);
336
337   for (i = 0; i < vector_size (lengths); ++i)
338     {
339       struct lengths_entry le;
340
341       vector_get (lengths, i, le);
342       ml_form_select_push_back (w->f_length, le.text);
343     }
344
345   ml_form_select_set_selection (w->f_length, 1);
346   ml_form_layout_pack (tbl, "Length: ", w->f_length);
347
348   /* Subject/title. */
349   w->f_subject = new_ml_form_text (w->pool, form);
350   ml_widget_set_property (w->f_subject, "form.text.size", 50);
351   ml_form_layout_pack (tbl, "Title: ", w->f_subject);
352
353   /* Body. */
354   w->f_body = new_ml_form_textarea (w->pool, form, 5, 50);
355   ml_form_layout_pack (tbl, "Notes: ", w->f_body);
356
357   /* Submit button. */
358   /* XXX Cancel button. */
359   submit = new_ml_form_submit (w->pool, form, "Save");
360   ml_form_layout_pack (tbl, 0, submit);
361
362   /* Pack everything into the window. */
363   ml_form_pack (form, tbl);
364   ml_window_pack (win, form);
365 }
366
367 /* When the form is submitted to save a new event, this function is called. */
368 static void
369 add_event (ml_session session, void *vw)
370 {
371   ml_calendar_day w = (ml_calendar_day) vw;
372   int userid;
373   const char *subject, *body;
374   int si, li;
375   int i, resid;
376   struct start_times_entry se;
377   struct lengths_entry le;
378   db_handle dbh;
379   st_handle sth;
380
381   /* Only logged-in users can post. */
382   /* XXX Resource-level security - later. */
383   userid = ml_session_userid (session);
384   if (!userid)
385     {
386       ml_error_window (w->pool, session,
387                        "You must be logged in to add events to the calendar.",
388                        ML_DIALOG_CLOSE_BUTTON);
389       return;
390     }
391
392   /* Verify that the user has at least given a subject line. If not, just
393    * return, which redisplays the form.
394    */
395   subject = ml_form_input_get_value (w->f_subject);
396   if (!subject || strlen (subject) == 0)
397     return;
398
399   /* Verify the remaining fields are in range. */
400   i = ml_form_select_get_selection (w->f_resid);
401   si = ml_form_select_get_selection (w->f_start);
402   li = ml_form_select_get_selection (w->f_length);
403   if (i < 0 || i >= vector_size (w->f_resids) ||
404       si < 0 || si >= vector_size (start_times) ||
405       li < 0 || li >= vector_size (lengths))
406     return;
407
408   /* Pull the other fields from the form. */
409   body = ml_form_input_get_value (w->f_body);
410   vector_get (w->f_resids, i, resid);
411   vector_get (start_times, si, se);
412   vector_get (lengths, li, le);
413
414   /* Save it in the database. */
415   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
416
417   sth = st_prepare_cached
418     (dbh,
419      "insert into ml_calendar_events (resid, start_time, length, subject, "
420      "body, author, original_ip) "
421      "values (?, ?, ?, ?, ?, ?, ?)",
422      DBI_INT, DBI_STRING, DBI_STRING, DBI_STRING, DBI_STRING,
423      DBI_INT, DBI_STRING);
424   st_execute (sth,
425               resid,
426               psprintf (w->pool, "%04d/%02d/%02d %02d:%02d:00",
427                         w->yyyy, w->mm, w->dd, se.hh, se.mm),
428               psprintf (w->pool, "%d hours %d mins", le.hh, le.mm),
429               subject, body,
430               userid,
431               ml_session_get_peernamestr (session));
432
433   /* Commit change to the database. */
434   db_commit (dbh);
435   put_db_handle (dbh);
436
437   /* Print confirmation page. */
438   ml_ok_window (w->pool, session,
439                 "That event was added to the calendar.",
440                 ML_DIALOG_CLOSE_BUTTON|ML_DIALOG_CLOSE_RELOAD_OPENER);
441   update_table (w);
442 }
443
444 static void
445 event_button_press (ml_session session, void *vdata)
446 {
447   struct event_button_data *data = (struct event_button_data *) vdata;
448   ml_calendar_day w = data->w;
449   db_handle dbh;
450   st_handle sth;
451   ml_window win;
452   ml_form form;
453   ml_form_layout tbl;
454   ml_flow_layout buttons;
455   int i, t, resid;
456   const char *resname, *subject, *body;
457   struct dbi_timestamp start_time;
458   struct dbi_interval length;
459
460   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
461
462   /* Pull out the current values of the fields from the database. */
463   sth = st_prepare_cached
464     (dbh,
465      "select resid, start_time, length, subject, body "
466      "  from ml_calendar_events "
467      " where id = ? and resid in (@)", DBI_INT, DBI_VECTOR_INT);
468   st_execute (sth, data->eventid, _ml_calendar_get_calendars (w->calendar));
469
470   st_bind (sth, 0, resid, DBI_INT);
471   st_bind (sth, 1, start_time, DBI_TIMESTAMP);
472   st_bind (sth, 2, length, DBI_INTERVAL);
473   st_bind (sth, 3, subject, DBI_STRING);
474   st_bind (sth, 4, body, DBI_STRING);
475
476   if (!st_fetch (sth))
477     {
478       ml_error_window (w->pool, session,
479                        "I cannot find that calendar event. Perhaps it has "
480                        "been deleted by another user, or it is in a calendar "
481                        "which you can no longer see.",
482                        ML_DIALOG_CLOSE_BUTTON);
483       return;
484     }
485
486   /* Save the event ID in the widget structure. We need it in edit_event. */
487   w->eventid = data->eventid;
488
489   win = new_ml_window (session, w->pool);
490   form = new_ml_form (w->pool);
491   tbl = new_ml_form_layout (w->pool);
492
493   /* Set the callback on the form. */
494   ml_form_set_callback (form, edit_event, session, w);
495   ml_widget_set_property (form, "method", "GET");
496
497   /* Pull out the list of calendar names from the database, and
498    * use this to populate the calendar drop-down menu.
499    */
500   w->f_resid = new_ml_form_select (w->pool, form);
501   w->f_resids = new_vector (w->pool, int);
502
503   sth = st_prepare_cached
504     (dbh,
505      "select c.resid, r.name from ml_calendar c, ml_resources r "
506      "where c.resid in (@) and c.resid = r.resid order by 2",
507      DBI_VECTOR_INT);
508   st_execute (sth, _ml_calendar_get_calendars (w->calendar));
509
510   st_bind (sth, 0, t, DBI_INT);
511   st_bind (sth, 1, resname, DBI_STRING);
512
513   while (st_fetch (sth))
514     {
515       vector_push_back (w->f_resids, resid);
516       ml_form_select_push_back (w->f_resid, resname);
517       /* XXX Colours. */
518       if (t == resid)
519         ml_form_select_set_selection (w->f_resid,
520                                       ml_form_select_size (w->f_resid) - 1);
521     }
522   ml_form_layout_pack (tbl, "Calendar: ", w->f_resid);
523
524   /* Start time. */
525   w->f_start = new_ml_form_select (w->pool, form);
526
527   for (i = 0; i < vector_size (start_times); ++i)
528     {
529       struct start_times_entry se;
530
531       vector_get (start_times, i, se);
532       ml_form_select_push_back (w->f_start, se.text);
533       /* XXX This doesn't work for events spanning across day boundaries. */
534       if (se.hh == start_time.hour && se.mm == start_time.min)
535         ml_form_select_set_selection (w->f_start,
536                                       ml_form_select_size (w->f_start) - 1);
537     }
538   ml_form_layout_pack (tbl, "Start time: ", w->f_start);
539
540   /* Length. */
541   w->f_length = new_ml_form_select (w->pool, form);
542
543   for (i = 0; i < vector_size (lengths); ++i)
544     {
545       struct lengths_entry le;
546
547       vector_get (lengths, i, le);
548       ml_form_select_push_back (w->f_length, le.text);
549       if (le.hh == length.hours && le.mm == length.mins)
550         ml_form_select_set_selection (w->f_length,
551                                       ml_form_select_size (w->f_length) - 1);
552     }
553
554   ml_form_layout_pack (tbl, "Length: ", w->f_length);
555
556   /* Subject/title. */
557   w->f_subject = new_ml_form_text (w->pool, form);
558   ml_widget_set_property (w->f_subject, "form.text.size", 50);
559   if (subject)
560     ml_form_input_set_value (w->f_subject, subject);
561   ml_form_layout_pack (tbl, "Title: ", w->f_subject);
562
563   /* Body. */
564   w->f_body = new_ml_form_textarea (w->pool, form, 5, 50);
565   if (body)
566     ml_form_input_set_value (w->f_body, body);
567   ml_form_layout_pack (tbl, "Notes: ", w->f_body);
568
569   /* Submit buttons. */
570   buttons = new_ml_flow_layout (w->pool);
571   w->edit = new_ml_form_submit (w->pool, form, "Edit");
572   ml_flow_layout_pack (buttons, w->edit);
573   w->delete = new_ml_form_submit (w->pool, form, "Delete");
574   ml_flow_layout_pack (buttons, w->delete);
575
576   ml_form_layout_pack (tbl, 0, buttons);
577
578   /* Pack everything into the window. */
579   ml_form_pack (form, tbl);
580   ml_window_pack (win, form);
581 }
582
583 /* When the edit or delete button is pressed, this function is called. */
584 static void
585 edit_event (ml_session session, void *vw)
586 {
587   ml_calendar_day w = (ml_calendar_day) vw;
588   int userid;
589   int edit_pressed, delete_pressed;
590   const char *s, *subject, *body;
591   int si, li;
592   int i, resid;
593   struct start_times_entry se;
594   struct lengths_entry le;
595   db_handle dbh;
596   st_handle sth;
597
598   /* Only logged-in users can post. */
599   /* XXX Resource-level security - later. */
600   userid = ml_session_userid (session);
601   if (!userid)
602     {
603       ml_error_window (w->pool, session,
604                        "You must be logged in to change "
605                        "events in the calendar.",
606                        ML_DIALOG_CLOSE_BUTTON);
607       return;
608     }
609
610   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
611
612   /* Which submit button was pressed? */
613   s = ml_form_input_get_value (w->edit);
614   edit_pressed = s && strlen (s) > 0;
615
616   s = ml_form_input_get_value (w->delete);
617   delete_pressed = s && strlen (s) > 0;
618
619   assert ((edit_pressed || delete_pressed) &&
620           !(edit_pressed && delete_pressed));
621
622   /* Delete button pressed? */
623   if (delete_pressed)
624     {
625       sth = st_prepare_cached
626         (dbh,
627          "delete from ml_calendar_events "
628          "where id = ? and resid in (@)",
629          DBI_INT, DBI_VECTOR_INT);
630       st_execute (sth, w->eventid, _ml_calendar_get_calendars (w->calendar));
631
632       /* Commit change to the database. */
633       db_commit (dbh);
634       put_db_handle (dbh);
635
636       /* Print confirmation page. */
637       ml_ok_window (w->pool, session,
638                     "That event was deleted.",
639                     ML_DIALOG_CLOSE_BUTTON|ML_DIALOG_CLOSE_RELOAD_OPENER);
640       update_table (w);
641       return;
642     }
643
644   /* Verify that the user has at least given a subject line. If not, just
645    * return, which redisplays the form.
646    */
647   subject = ml_form_input_get_value (w->f_subject);
648   if (!subject || strlen (subject) == 0)
649     return;
650
651   /* Verify the remaining fields are in range. */
652   i = ml_form_select_get_selection (w->f_resid);
653   si = ml_form_select_get_selection (w->f_start);
654   li = ml_form_select_get_selection (w->f_length);
655   if (i < 0 || i >= vector_size (w->f_resids) ||
656       si < 0 || si >= vector_size (start_times) ||
657       li < 0 || li >= vector_size (lengths))
658     return;
659
660   /* Pull the other fields from the form. */
661   body = ml_form_input_get_value (w->f_body);
662   vector_get (w->f_resids, i, resid);
663   vector_get (start_times, si, se);
664   vector_get (lengths, li, le);
665
666   /* Update the database. */
667   sth = st_prepare_cached
668     (dbh,
669      "update ml_calendar_events "
670      "set resid = ?, start_time = ?, length = ?, subject = ?, body = ?, "
671      "last_modified_date = current_timestamp "
672      "where id = ? and resid in (@)",
673      DBI_INT, DBI_STRING, DBI_STRING, DBI_STRING, DBI_STRING,
674      DBI_INT, DBI_VECTOR_INT);
675   st_execute (sth,
676               resid,
677               psprintf (w->pool, "%04d/%02d/%02d %02d:%02d:00",
678                         w->yyyy, w->mm, w->dd, se.hh, se.mm),
679               psprintf (w->pool, "%d hours %d mins", le.hh, le.mm),
680               subject, body,
681               w->eventid, _ml_calendar_get_calendars (w->calendar));
682
683   /* Commit change to the database. */
684   db_commit (dbh);
685   put_db_handle (dbh);
686
687   /* Print confirmation page. */
688   ml_ok_window (w->pool, session,
689                 "That event was edited.",
690                 ML_DIALOG_CLOSE_BUTTON|ML_DIALOG_CLOSE_RELOAD_OPENER);
691   update_table (w);
692 }
693
694 /* Update the table from the database. */
695 static void
696 update_table (ml_calendar_day w)
697 {
698   db_handle dbh;
699   st_handle sth;
700   const vector resids = _ml_calendar_get_calendars (w->calendar);
701   int eventid, resid, start_time_hh, start_time_mm, end_time_hh, end_time_mm;
702   const char *subject, *body, *start_of_day;
703   ml_button b;
704   ml_flow_layout flow[48] = { 0 };
705   int start, end, i;
706   struct event_button_data *data;
707   const char *title;
708
709   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
710
711   /* Read the events from the database. */
712   /* SQL/PostgreSQL could do with having some way to name subexpressions
713    * in a query ...
714    */
715   start_of_day = psprintf (w->pool,
716                            "%04d/%02d/%02d 00:00:00",
717                            w->yyyy, w->mm, w->dd);
718
719   sth = st_prepare_cached
720     (dbh,
721      "select id, resid, subject, body, "
722      "       date_part ('hour', start_time - date ?), "
723      "       date_part ('min', start_time - date ?), "
724      "       date_part ('hour', start_time + length - date ?), "
725      "       date_part ('min', start_time + length - date ?) "
726      "  from ml_calendar_events "
727      " where resid in (@) "
728      "   and length <> '1 day' "
729      "   and start_time + length >= date ? "
730      "   and start_time < date ? + interval '1 day'"
731      " order by 1",
732      DBI_STRING, DBI_STRING, DBI_STRING, DBI_STRING,
733      DBI_VECTOR_INT,
734      DBI_STRING, DBI_STRING);
735
736   st_execute (sth, start_of_day, start_of_day, start_of_day, start_of_day,
737               resids, start_of_day, start_of_day);
738
739   st_bind (sth, 0, eventid, DBI_INT);
740   st_bind (sth, 1, resid, DBI_INT);
741   st_bind (sth, 2, subject, DBI_STRING);
742   st_bind (sth, 3, body, DBI_STRING);
743   st_bind (sth, 4, start_time_hh, DBI_INT);
744   st_bind (sth, 5, start_time_mm, DBI_INT);
745   st_bind (sth, 6, end_time_hh, DBI_INT);
746   st_bind (sth, 7, end_time_mm, DBI_INT);
747
748   while (st_fetch (sth))
749     {
750       data = pmalloc (w->pool, sizeof *data);
751       data->eventid = eventid;
752       data->w = w;
753
754       /* Create the tooltip (title) for this button. */
755       title = psprintf (w->pool, "%02d:%02d - %02d:%02d: %s",
756                         start_time_hh, start_time_mm,
757                         end_time_hh, end_time_mm,
758                         body ? body : "");
759
760       /* Create the button. */
761       b = new_ml_button (w->pool, subject);
762       ml_widget_set_property (b, "button.style", "link");
763       ml_widget_set_property (b, "color", _ml_calendar_colour (resid));
764       ml_widget_set_property (b, "title", title);
765       ml_button_set_callback (b, event_button_press, w->session, data);
766       ml_button_set_popup (b, "ml_calendar_day_edit_event");
767       ml_button_set_popup_size (b, 640, 300);
768
769       /* Work out the start / end indexes. */
770       if (start_time_hh < 0)
771         start = 0;
772       else
773         {
774           assert (start_time_hh <= 24);
775           assert (0 <= start_time_mm && start_time_mm < 60);
776
777           start = start_time_hh * 2 + start_time_mm / 30;
778         }
779
780       if (end_time_hh >= 24)
781         end = 48;
782       else
783         {
784           assert (end_time_hh >= 0);
785           assert (0 <= end_time_mm && end_time_mm < 60);
786
787           end = end_time_hh * 2 + end_time_mm / 30;
788         }
789
790       /* Make sure that short events are displayed. */
791       if (start == end)
792         {
793           assert (end < 48);
794           end++;
795         }
796
797 #if 0
798       fprintf (stderr,
799                "fetched event #%d for day %04d/%02d/%02d: starts at %02d:%02d "
800                "ends at %02d:%02d startbox %d endbox %d\n",
801                eventid, w->yyyy, w->mm, w->dd, start_time_hh, start_time_mm,
802                end_time_hh, end_time_mm, start, end);
803 #endif
804
805       /* Pack it into the appropriate flow widget. */
806       for (i = start; i < end; ++i)
807         {
808           if (flow[i] == 0)
809             flow[i] = new_ml_flow_layout (w->pool);
810           ml_flow_layout_push_back (flow[i], b);
811           ml_flow_layout_push_back (flow[i], spacer);
812         }
813     }
814
815   /* Copy the flow widgets into the table's second column. */
816   for (i = 0; i < 48; ++i)
817     ml_table_layout_pack (w->tbl, flow[i], i, 1);
818
819   put_db_handle (dbh);
820 }
821
822 static void
823 repaint (void *vw, ml_session session, const char *windowid, io_handle io)
824 {
825   ml_calendar_day w = (ml_calendar_day) vw;
826
827   ml_widget_repaint (w->iframe, session, windowid, io);
828 }