Add to git.
[monolith.git] / calendar / ml_calendar_notes.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_notes.c,v 1.6 2003/02/22 15:34:26 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_table_layout.h>
43 #include <ml_flow_layout.h>
44 #include <ml_vertical_layout.h>
45 #include <ml_form_layout.h>
46 #include <ml_button.h>
47 #include <ml_text_label.h>
48 #include <ml_dialog.h>
49 #include <ml_form.h>
50 #include <ml_form_text.h>
51 #include <ml_form_textarea.h>
52 #include <ml_form_select.h>
53 #include <ml_form_submit.h>
54
55 #include "ml_calendar_lib.h"
56 #include "ml_calendar_notes.h"
57
58 static void repaint (void *, ml_session, const char *, io_handle);
59
60 struct ml_widget_operations calendar_notes_ops =
61   {
62     repaint: repaint
63   };
64
65 struct ml_calendar_notes
66 {
67   struct ml_widget_operations *ops;
68   pool pool;                    /* Pool for allocations. */
69   ml_session session;           /* Current session. */
70   const char *conninfo;         /* Database connection. */
71   ml_calendar calendar;         /* Parent calendar. */
72   int yyyy, mm, dd;             /* Current date. */
73
74   ml_table_layout top;          /* Top-level layout. */
75   ml_button add;                /* Add a note. */
76   ml_vertical_layout tbl;       /* Table of notes. */
77
78   /* These widgets are used in the add note form. */
79   ml_form_select f_resid;       /* Calendar selected. */
80   vector f_resids;              /* List of calendars. */
81   ml_form_text f_subject;       /* Title. */
82   ml_form_textarea f_body;      /* Notes. */
83
84   /* The above fields are also used on the edit note form, plus these: */
85   int eventid;                  /* Event ID we are editing. */
86   ml_form_submit edit, delete;  /* Which button was pressed. */
87 };
88
89 /* This is the parameter to the note_button_press function. */
90 struct note_button_data
91 {
92   int eventid;
93   ml_calendar_notes w;
94 };
95
96 static void update_notes (ml_calendar_notes w);
97 static void add_note_button (ml_session session, void *vw);
98 static void add_note (ml_session session, void *vw);
99 static void note_button_press (ml_session session, void *vdata);
100 static void edit_note (ml_session session, void *vw);
101
102 ml_calendar_notes
103 new_ml_calendar_notes (pool pool, ml_session session, const char *conninfo,
104                        ml_calendar calendar)
105 {
106   ml_calendar_notes w = pmalloc (pool, sizeof *w);
107
108   w->ops = &calendar_notes_ops;
109   w->pool = pool;
110   w->session = session;
111   w->conninfo = conninfo;
112   w->calendar = calendar;
113
114   /* The parent calendar must call ml_calendar_month_set_date to set these
115    * before using this widget.
116    */
117   w->yyyy = w->mm = w->dd = -1;
118
119   /* Create the top-level layout and add buttons to it. */
120   w->top = new_ml_table_layout (pool, 2, 1);
121   ml_widget_set_property (w->top, "class", "ml_calendar_notes");
122   w->add = new_ml_button (pool, "Add note");
123   ml_button_set_callback (w->add, add_note_button, session, w);
124   ml_button_set_popup (w->add, "ml_calendar_notes_add_event");
125   ml_button_set_popup_size (w->add, 640, 300);
126   ml_table_layout_pack (w->top, w->add, 1, 0);
127   ml_table_layout_set_align (w->top, 1, 0, "left");
128
129   /* Create the notes area, unpopulated to start with. */
130   w->tbl = new_ml_vertical_layout (pool);
131   ml_widget_set_property (w->tbl, "class", "ml_calendar_notes");
132   ml_table_layout_pack (w->top, w->tbl, 0, 0);
133   ml_table_layout_set_align (w->top, 0, 0, "left");
134
135   return w;
136 }
137
138 void
139 ml_calendar_notes_set_date (ml_calendar_notes w, int yyyy, int mm, int dd)
140 {
141   /* Update the date. */
142   w->yyyy = yyyy;
143   w->mm = mm;
144   w->dd = dd;
145
146   update_notes (w);
147 }
148
149 /* Update the notes area from the database. */
150 static void
151 update_notes (ml_calendar_notes w)
152 {
153   db_handle dbh;
154   st_handle sth;
155   const vector resids = _ml_calendar_get_calendars (w->calendar);
156   int eventid, resid;
157   const char *subject, *body;
158   ml_button b;
159   struct note_button_data *data;
160
161   ml_vertical_layout_clear (w->tbl);
162
163   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
164
165   /* Read the notes from the database. */
166   sth = st_prepare_cached
167     (dbh,
168      "select id, resid, subject, body from ml_calendar_events "
169      " where resid in (@) "
170      "   and length = '1 day' "
171      "   and start_time = date (? || '/' || ? || '/' || ? || ' 00:00:00') "
172      " order by 1",
173      DBI_VECTOR_INT, DBI_INT, DBI_INT, DBI_INT);
174   st_execute (sth, resids, w->yyyy, w->mm, w->dd);
175
176   st_bind (sth, 0, eventid, DBI_INT);
177   st_bind (sth, 1, resid, DBI_INT);
178   st_bind (sth, 2, subject, DBI_STRING);
179   st_bind (sth, 3, body, DBI_STRING);
180
181   while (st_fetch (sth))
182     {
183       data = pmalloc (w->pool, sizeof *data);
184       data->eventid = eventid;
185       data->w = w;
186
187       b = new_ml_button (w->pool, subject);
188       ml_widget_set_property (b, "button.style", "link");
189       ml_widget_set_property (b, "color", _ml_calendar_colour (resid));
190       ml_widget_set_property (b, "title", body);
191       ml_button_set_callback (b, note_button_press, w->session, data);
192       ml_button_set_popup (b, "ml_calendar_notes_edit_event");
193       ml_button_set_popup_size (b, 640, 300);
194
195       ml_vertical_layout_push_back (w->tbl, b);
196     }
197
198   put_db_handle (dbh);
199 }
200
201 static void
202 add_note_button (ml_session session, void *vw)
203 {
204   ml_calendar_notes w = (ml_calendar_notes) vw;
205   ml_window win;
206   ml_form form;
207   ml_form_layout tbl;
208   ml_form_submit submit;
209   db_handle dbh;
210   st_handle sth;
211   int resid;
212   const char *resname;
213
214   win = new_ml_window (w->session, w->pool);
215
216   /* Create the form. */
217   form = new_ml_form (w->pool);
218   tbl = new_ml_form_layout (w->pool);
219
220   /* Set the callback on the form. */
221   ml_form_set_callback (form, add_note, session, w);
222   ml_widget_set_property (form, "method", "GET");
223
224   /* Pull out the list of calendar names from the database and populate
225    * the drop-down menu with it.
226    */
227   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
228
229   w->f_resid = new_ml_form_select (w->pool, form);
230   w->f_resids = new_vector (w->pool, int);
231
232   sth = st_prepare_cached
233     (dbh,
234      "select c.resid, r.name from ml_calendar c, ml_resources r "
235      "where c.resid in (@) and c.resid = r.resid order by 2",
236      DBI_VECTOR_INT);
237   st_execute (sth, _ml_calendar_get_calendars (w->calendar));
238
239   st_bind (sth, 0, resid, DBI_INT);
240   st_bind (sth, 1, resname, DBI_STRING);
241
242   while (st_fetch (sth))
243     {
244       vector_push_back (w->f_resids, resid);
245       ml_form_select_push_back (w->f_resid, resname);
246       /* XXX Colours. */
247     }
248   ml_form_layout_pack (tbl, "Calendar: ", w->f_resid);
249
250   put_db_handle (dbh);
251
252   /* Subject/title. */
253   w->f_subject = new_ml_form_text (w->pool, form);
254   ml_widget_set_property (w->f_subject, "form.text.size", 50);
255   ml_form_layout_pack (tbl, "Title: ", w->f_subject);
256
257   /* Body. */
258   w->f_body = new_ml_form_textarea (w->pool, form, 5, 50);
259   ml_form_layout_pack (tbl, "Notes: ", w->f_body);
260
261   /* Submit button. */
262   /* XXX Cancel button. */
263   submit = new_ml_form_submit (w->pool, form, "Save");
264   ml_form_layout_pack (tbl, 0, submit);
265
266   /* Pack everything together. */
267   ml_form_pack (form, tbl);
268   ml_window_pack (win, form);
269 }
270
271 static void
272 add_note (ml_session session, void *vw)
273 {
274   ml_calendar_notes w = (ml_calendar_notes) vw;
275   db_handle dbh;
276   st_handle sth;
277   int userid, i, resid;
278   const char *subject, *body;
279
280   /* Only logged-in users can post. */
281   /* XXX Resource-level security - later. */
282   userid = ml_session_userid (session);
283   if (!userid)
284     {
285       ml_error_window (w->pool, session,
286                        "You must be logged in to add events to the calendar.",
287                        ML_DIALOG_CLOSE_BUTTON);
288       return;
289     }
290
291   /* Verify that the user has at least given a subject line. If not, just
292    * return, which redisplays the form.
293    */
294   subject = ml_form_input_get_value (w->f_subject);
295   if (!subject || strlen (subject) == 0)
296     return;
297
298   /* Verify the remaining fields are in range. */
299   i = ml_form_select_get_selection (w->f_resid);
300   if (i < 0 || i >= vector_size (w->f_resids))
301     return;
302
303   /* Pull the other fields from the form. */
304   body = ml_form_input_get_value (w->f_body);
305   vector_get (w->f_resids, i, resid);
306
307   /* Save it in the database. */
308   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
309
310   sth = st_prepare_cached
311     (dbh,
312      "insert into ml_calendar_events (resid, start_time, length, subject, "
313      "body, author, original_ip) "
314      "values (?, ?, '1 day', ?, ?, ?, ?)",
315      DBI_INT, DBI_STRING, DBI_STRING, DBI_STRING, DBI_INT, DBI_STRING);
316   st_execute (sth,
317               resid,
318               psprintf (w->pool, "%04d/%02d/%02d 00:00:00",
319                         w->yyyy, w->mm, w->dd),
320               subject, body,
321               userid,
322               ml_session_get_peernamestr (session));
323
324   /* Commit change to the database. */
325   db_commit (dbh);
326   put_db_handle (dbh);
327
328   /* Print confirmation page. */
329   ml_ok_window (w->pool, session,
330                 "That note was added to the calendar.",
331                 ML_DIALOG_CLOSE_BUTTON|ML_DIALOG_CLOSE_RELOAD_OPENER);
332   update_notes (w);
333 }
334
335 static void
336 note_button_press (ml_session session, void *vdata)
337 {
338   struct note_button_data *data = (struct note_button_data *) vdata;
339   ml_calendar_notes w = data->w;
340   ml_window win;
341   ml_form form;
342   ml_form_layout tbl;
343   ml_flow_layout buttons;
344   db_handle dbh;
345   st_handle sth;
346   int resid, t;
347   const char *resname, *subject, *body;
348
349   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
350
351   /* Pull out the current values of the fields from the database. */
352   sth = st_prepare_cached
353     (dbh,
354      "select resid, subject, body "
355      "  from ml_calendar_events "
356      " where id = ? and resid in (@)", DBI_INT, DBI_VECTOR_INT);
357   st_execute (sth, data->eventid, _ml_calendar_get_calendars (w->calendar));
358
359   st_bind (sth, 0, resid, DBI_INT);
360   st_bind (sth, 1, subject, DBI_STRING);
361   st_bind (sth, 2, body, DBI_STRING);
362
363   if (!st_fetch (sth))
364     {
365       ml_error_window (w->pool, session,
366                        "I cannot find that calendar event. Perhaps it has "
367                        "been deleted by another user, or it is in a calendar "
368                        "which you can no longer see.",
369                        ML_DIALOG_CLOSE_BUTTON);
370       return;
371     }
372
373   /* Save the event ID in the widget structure. We need it in edit_event. */
374   w->eventid = data->eventid;
375
376   win = new_ml_window (w->session, w->pool);
377
378   /* Create the form. */
379   form = new_ml_form (w->pool);
380   tbl = new_ml_form_layout (w->pool);
381
382   /* Set the callback on the form. */
383   ml_form_set_callback (form, edit_note, session, w);
384   ml_widget_set_property (form, "method", "GET");
385
386   /* Pull out the list of calendar names from the database and populate
387    * the drop-down menu with it.
388    */
389   w->f_resid = new_ml_form_select (w->pool, form);
390   w->f_resids = new_vector (w->pool, int);
391
392   sth = st_prepare_cached
393     (dbh,
394      "select c.resid, r.name from ml_calendar c, ml_resources r "
395      "where c.resid in (@) and c.resid = r.resid order by 2",
396      DBI_VECTOR_INT);
397   st_execute (sth, _ml_calendar_get_calendars (w->calendar));
398
399   st_bind (sth, 0, t, DBI_INT);
400   st_bind (sth, 1, resname, DBI_STRING);
401
402   while (st_fetch (sth))
403     {
404       vector_push_back (w->f_resids, resid);
405       ml_form_select_push_back (w->f_resid, resname);
406       /* XXX Colours. */
407       if (t == resid)
408         ml_form_select_set_selection (w->f_resid,
409                                       ml_form_select_size (w->f_resid) - 1);
410     }
411   ml_form_layout_pack (tbl, "Calendar: ", w->f_resid);
412
413   put_db_handle (dbh);
414
415   /* Subject/title. */
416   w->f_subject = new_ml_form_text (w->pool, form);
417   ml_widget_set_property (w->f_subject, "form.text.size", 50);
418   if (subject)
419     ml_form_input_set_value (w->f_subject, subject);
420   ml_form_layout_pack (tbl, "Title: ", w->f_subject);
421
422   /* Body. */
423   w->f_body = new_ml_form_textarea (w->pool, form, 5, 50);
424   if (body)
425     ml_form_input_set_value (w->f_body, body);
426   ml_form_layout_pack (tbl, "Notes: ", w->f_body);
427
428   /* Submit buttons. */
429   buttons = new_ml_flow_layout (w->pool);
430   w->edit = new_ml_form_submit (w->pool, form, "Edit");
431   ml_flow_layout_pack (buttons, w->edit);
432   w->delete = new_ml_form_submit (w->pool, form, "Delete");
433   ml_flow_layout_pack (buttons, w->delete);
434
435   ml_form_layout_pack (tbl, 0, buttons);
436
437   /* Pack everything together. */
438   ml_form_pack (form, tbl);
439   ml_window_pack (win, form);
440 }
441
442 static void
443 edit_note (ml_session session, void *vw)
444 {
445   ml_calendar_notes w = (ml_calendar_notes) vw;
446   int userid;
447   int edit_pressed, delete_pressed;
448   const char *s, *subject, *body;
449   int i, resid;
450   db_handle dbh;
451   st_handle sth;
452
453   /* Only logged-in users can post. */
454   /* XXX Resource-level security - later. */
455   userid = ml_session_userid (session);
456   if (!userid)
457     {
458       ml_error_window (w->pool, session,
459                        "You must be logged in to change "
460                        "events in the calendar.",
461                        ML_DIALOG_CLOSE_BUTTON);
462       return;
463     }
464
465   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
466
467   /* Which submit button was pressed? */
468   s = ml_form_input_get_value (w->edit);
469   edit_pressed = s && strlen (s) > 0;
470
471   s = ml_form_input_get_value (w->delete);
472   delete_pressed = s && strlen (s) > 0;
473
474   assert ((edit_pressed || delete_pressed) &&
475           !(edit_pressed && delete_pressed));
476
477   /* Delete button pressed? */
478   if (delete_pressed)
479     {
480       sth = st_prepare_cached
481         (dbh,
482          "delete from ml_calendar_events "
483          "where id = ? and resid in (@)",
484          DBI_INT, DBI_VECTOR_INT);
485       st_execute (sth, w->eventid, _ml_calendar_get_calendars (w->calendar));
486
487       /* Commit change to the database. */
488       db_commit (dbh);
489       put_db_handle (dbh);
490
491       /* Print confirmation page. */
492       ml_ok_window (w->pool, session,
493                     "That event was deleted.",
494                     ML_DIALOG_CLOSE_BUTTON|ML_DIALOG_CLOSE_RELOAD_OPENER);
495       update_notes (w);
496       return;
497     }
498
499   /* Verify that the user has at least given a subject line. If not, just
500    * return, which redisplays the form.
501    */
502   subject = ml_form_input_get_value (w->f_subject);
503   if (!subject || strlen (subject) == 0)
504     return;
505
506   /* Verify the remaining fields are in range. */
507   i = ml_form_select_get_selection (w->f_resid);
508   if (i < 0 || i >= vector_size (w->f_resids))
509     return;
510
511   /* Pull the other fields from the form. */
512   body = ml_form_input_get_value (w->f_body);
513   vector_get (w->f_resids, i, resid);
514
515   /* Update the database. */
516   sth = st_prepare_cached
517     (dbh,
518      "update ml_calendar_events "
519      "set resid = ?, subject = ?, body = ?, "
520      "last_modified_date = current_timestamp "
521      "where id = ? and resid in (@)",
522      DBI_INT, DBI_STRING, DBI_STRING, DBI_INT, DBI_VECTOR_INT);
523   st_execute (sth,
524               resid,
525               subject, body,
526               w->eventid, _ml_calendar_get_calendars (w->calendar));
527
528   /* Commit change to the database. */
529   db_commit (dbh);
530   put_db_handle (dbh);
531
532   /* Print confirmation page. */
533   ml_ok_window (w->pool, session,
534                 "That event was edited.",
535                 ML_DIALOG_CLOSE_BUTTON|ML_DIALOG_CLOSE_RELOAD_OPENER);
536   update_notes (w);
537 }
538
539 static void
540 repaint (void *vw, ml_session session, const char *windowid, io_handle io)
541 {
542   ml_calendar_notes w = (ml_calendar_notes) vw;
543
544   ml_widget_repaint (w->top, session, windowid, io);
545 }