Add to git.
[monolith.git] / calendar / ml_calendar_month.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_month.c,v 1.9 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 <vector.h>
37 #include <pstring.h>
38
39 #include <pthr_dbi.h>
40
41 #include <monolith.h>
42 #include <ml_widget.h>
43 #include <ml_button.h>
44 #include <ml_text_label.h>
45 #include <ml_table_layout.h>
46
47 #include "ml_calendar_lib.h"
48 #include "ml_calendar_month.h"
49
50 static void repaint (void *, ml_session, const char *, io_handle);
51
52 struct ml_widget_operations calendar_month_ops =
53   {
54     repaint: repaint
55   };
56
57 struct ml_calendar_month
58 {
59   struct ml_widget_operations *ops;
60   pool pool;                    /* Pool for allocations. */
61   ml_session session;           /* Current session. */
62   const char *conninfo;         /* Database connection. */
63   ml_calendar calendar;         /* Parent calendar. */
64   int yyyy, mm, dd;             /* Current date. */
65
66   ml_table_layout tbl;          /* Top-level table layout. */
67   ml_text_label days[7];        /* Days of the week labels. */
68   ml_button b[32];              /* Buttons (we reuse these). */
69 };
70
71 /* This structure is passed to the button callbacks. */
72 struct button_data
73 {
74   int dd;                       /* Day (1 - 31). */
75   ml_calendar_month w;          /* Widget pointer. */
76 };
77
78 static void button_press (ml_session, void *);
79 static void update_buttons (ml_calendar_month w);
80
81 ml_calendar_month
82 new_ml_calendar_month (pool pool, ml_session session, const char *conninfo,
83                        ml_calendar calendar)
84 {
85   ml_calendar_month w = pmalloc (pool, sizeof *w);
86   int i, j;
87   struct button_data *data;
88
89   w->ops = &calendar_month_ops;
90   w->pool = pool;
91   w->session = session;
92   w->conninfo = conninfo;
93   w->calendar = calendar;
94
95   /* The parent calendar must call ml_calendar_month_set_date to set these
96    * before using this widget.
97    */
98   w->yyyy = w->mm = w->dd = -1;
99
100   /* Create the days of the week labels and buttons. */
101   /* XXX Waiting for ml_text_label and ml_button to give us more control
102    * over the rendering of labels and buttons.
103    */
104   w->days[0] = new_ml_text_label (pool, "Su");
105   ml_widget_set_property (w->days[0], "font.weight", "bold");
106   w->days[1] = new_ml_text_label (pool, "Mo");
107   ml_widget_set_property (w->days[1], "font.weight", "bold");
108   w->days[2] = new_ml_text_label (pool, "Tu");
109   ml_widget_set_property (w->days[2], "font.weight", "bold");
110   w->days[3] = new_ml_text_label (pool, "We");
111   ml_widget_set_property (w->days[3], "font.weight", "bold");
112   w->days[4] = new_ml_text_label (pool, "Th");
113   ml_widget_set_property (w->days[4], "font.weight", "bold");
114   w->days[5] = new_ml_text_label (pool, "Fr");
115   ml_widget_set_property (w->days[5], "font.weight", "bold");
116   w->days[6] = new_ml_text_label (pool, "Sa");
117   ml_widget_set_property (w->days[6], "font.weight", "bold");
118
119   w->b[0] = 0;                  /* This is a dummy entry. */
120
121   for (i = 1; i <= 31; ++i)
122     {
123       data = pmalloc (pool, sizeof *data);
124       w->b[i] = new_ml_button (pool, pitoa (pool, i));
125       data->dd = i;
126       data->w = w;
127       ml_button_set_callback (w->b[i], button_press, session, data);
128     }
129
130   /* Create the top-level table widget, also reused. */
131   w->tbl = new_ml_table_layout (pool, 7, 7);
132   for (i = 0; i < 7; ++i)
133     {
134       ml_table_layout_pack (w->tbl, w->days[i], 0, i);
135       ml_table_layout_set_align (w->tbl, 0, i, "center");
136     }
137   for (j = 1; j < 7; ++j)
138     for (i = 0; i < 7; ++i)
139       ml_table_layout_set_align (w->tbl, j, i, "right");
140
141   return w;
142 }
143
144 /* Update the date, then update the widget. */
145 void
146 ml_calendar_month_set_date (ml_calendar_month w, int yyyy, int mm, int dd)
147 {
148   int j, i, d, n;
149
150   /* Update the date. */
151   w->yyyy = yyyy;
152   w->mm = mm;
153   w->dd = dd;
154
155   /* Clear the existing table of widgets. */
156   for (j = 1; j < 7; ++j)
157     for (i = 0; i < 7; ++i)
158       ml_table_layout_pack (w->tbl, 0, j, i);
159
160   /* Redraw the month. */
161   j = 1;
162   i = _ml_calendar_first_wday_of_month (mm, yyyy);
163   n = _ml_calendar_days_in_month (mm, yyyy);
164
165   for (d = 1; d <= n; ++d)
166     {
167       ml_table_layout_pack (w->tbl, w->b[d], j, i);
168       i++;
169       if (i == 7)
170         {
171           i = 0;
172           j++;
173         }
174     }
175
176   /* Update buttons from the database. */
177   update_buttons (w);
178 }
179
180 /* This function is called when a button is pressed. */
181 static void
182 button_press (ml_session session, void *vdata)
183 {
184   struct button_data *data = (struct button_data *) vdata;
185
186   /* This function, in the parent calendar, will eventually call our
187    * ml_calendar_month_set_date.
188    */
189   ml_calendar_set_date (data->w->calendar,
190                         data->w->yyyy, data->w->mm, data->dd);
191 }
192
193 /* This function updates the state of the buttons from the database,
194  * and also highlights the currently selected day.
195  */
196 static void
197 update_buttons (ml_calendar_month w)
198 {
199   db_handle dbh;
200   st_handle sth;
201   unsigned char flags[32];
202   int dd;
203   const vector resids = _ml_calendar_get_calendars (w->calendar);
204
205   dbh = get_db_handle (w->conninfo, DBI_THROW_ERRORS);
206
207   /* Find out which days contain data. Note how with PostgreSQL we
208    * can leave the database to take the strain of date munging. This
209    * is one of the great reasons to use and recommend PostgreSQL.
210    */
211   sth = st_prepare_cached
212     (dbh,
213      "select date_part ('day', start_time) from ml_calendar_events "
214      " where resid in (@) "
215      "   and start_time >= date (? || '/' || ? || '/01') "
216      "   and start_time <  (date (? || '/' || ? || '/01') + "
217      "                      interval '1 month')",
218      DBI_VECTOR_INT, DBI_INT, DBI_INT, DBI_INT, DBI_INT);
219   st_execute (sth, resids, w->yyyy, w->mm, w->yyyy, w->mm);
220
221   st_bind (sth, 0, dd, DBI_INT);
222
223   memset (flags, 0, sizeof flags);
224
225   while (st_fetch (sth))
226     {
227       assert (0 < dd && dd < 32);
228       flags[dd] |= 1;
229     }
230
231   flags[w->dd] |= 2;
232
233   /* Depending on the flags, we now set the class of each button. */
234   for (dd = 1; dd <= 31; ++dd)
235     {
236       switch (flags[dd]) {
237       case 0:
238         ml_widget_set_property (w->b[dd], "class", "ml_calendar_day");
239         break;
240       case 1:
241         ml_widget_set_property (w->b[dd], "class", "ml_calendar_day_events");
242         break;
243       case 2:
244         ml_widget_set_property (w->b[dd], "class", "ml_calendar_day_selected");
245         break;
246       case 3:
247         ml_widget_set_property (w->b[dd], "class",
248                                 "ml_calendar_day_events_selected");
249         break;
250       }
251     }
252
253   put_db_handle (dbh);
254 }
255
256 static void
257 repaint (void *vw, ml_session session, const char *windowid, io_handle io)
258 {
259   ml_calendar_month w = (ml_calendar_month) vw;
260
261   ml_widget_repaint (w->tbl, session, windowid, io);
262 }