553dc8f6bb376c649489e94597f70eb3b70c0f67
[claws.git] / src / plugins / vcalendar / day-view.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  *
4  * Copyright (c) 2007-2008 Juha Kautto (juha at xfce.org)
5  * Copyright (c) 2008 Colin Leroy (colin@colino.net)
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #include "claws-features.h"
25 #endif
26
27 #include <stddef.h>
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include "defs.h"
31
32 #ifdef USE_PTHREAD
33 #include <pthread.h>
34 #endif
35 #include <string.h>
36 #include <time.h>
37
38 #include <glib.h>
39 #include <glib/gprintf.h>
40 #include <gdk/gdkkeysyms.h>
41 #include <gdk/gdk.h>
42 #include <gtk/gtk.h>
43
44 #include "summaryview.h"
45 #include "vcalendar.h"
46 #include "vcal_folder.h"
47 #include "vcal_prefs.h"
48 #include "vcal_manager.h"
49 #include "common-views.h"
50 #include "vcal_meeting_gtk.h"
51
52 #define MAX_DAYS 40
53 struct _day_win
54 {
55     GtkAccelGroup *accel_group;
56
57     GtkWidget *Window;
58     GtkWidget *Vbox;
59
60     GtkWidget *Menubar;
61     GtkWidget *File_menu;
62     GtkWidget *File_menu_new;
63     GtkWidget *File_menu_close;
64     GtkWidget *View_menu;
65     GtkWidget *View_menu_refresh;
66     GtkWidget *Go_menu;
67     GtkWidget *Go_menu_today;
68     GtkWidget *Go_menu_prev;
69     GtkWidget *Go_menu_next;
70
71     GtkWidget *Toolbar;
72     GtkWidget *Create_toolbutton;
73     GtkWidget *Previous_toolbutton;
74     GtkWidget *Today_toolbutton;
75     GtkWidget *Next_toolbutton;
76     GtkWidget *Refresh_toolbutton;
77     GtkWidget *Close_toolbutton;
78
79     GtkWidget *StartDate_button;
80     GtkRequisition StartDate_button_req;
81     GtkWidget *day_spin;
82
83     GtkWidget *day_view_vbox;
84     GtkWidget *scroll_win_h;
85     GtkWidget *dtable_h; /* header of day table */
86     GtkWidget *scroll_win;
87     GtkWidget *dtable;   /* day table */
88     GtkRequisition hour_req;
89
90     GtkWidget *header[MAX_DAYS];
91     GtkWidget *element[24][MAX_DAYS];
92     GtkWidget *line[24][MAX_DAYS];
93
94     guint upd_timer;
95     gdouble scroll_pos; /* remember the scroll position */
96
97     GdkColor bg1, bg2, line_color, bg_today, fg_sunday;
98     GList    *apptw_list; /* keep track of appointments being updated */
99     struct tm startdate;
100     FolderItem *item;
101     gulong selsig;
102     GtkWidget *view_menu;
103     GtkWidget *event_menu;
104     GtkActionGroup *event_group;
105     GtkUIManager *ui_manager;
106 };
107
108 static gchar *get_locale_date(struct tm *tmdate)
109 {
110         gchar *d = g_malloc(100);
111         strftime(d, 99, "%x", tmdate);
112         return d;
113 }
114
115 static void set_scroll_position(day_win *dw)
116 {
117     GtkAdjustment *v_adj;
118
119     v_adj = gtk_scrolled_window_get_vadjustment(
120             GTK_SCROLLED_WINDOW(dw->scroll_win));
121     if (dw->scroll_pos > 0) /* we have old value */
122         gtk_adjustment_set_value(v_adj, dw->scroll_pos);
123     else if (dw->scroll_pos < 0)
124         /* default: let's try to start roughly from line 8 = 8 o'clock */
125         gtk_adjustment_set_value(v_adj, v_adj->upper/3);
126     gtk_adjustment_changed(v_adj);
127 }
128
129 static gboolean scroll_position_timer(gpointer user_data)
130 {
131     set_scroll_position((day_win *)user_data);
132     return(FALSE); /* only once */
133 }
134
135 static void get_scroll_position(day_win *dw)
136 {
137     GtkAdjustment *v_adj;
138
139     v_adj = gtk_scrolled_window_get_vadjustment(
140             GTK_SCROLLED_WINDOW(dw->scroll_win));
141     dw->scroll_pos = gtk_adjustment_get_value(v_adj);
142 }
143
144 void dw_close_window(day_win *dw)
145 {
146     vcal_view_set_summary_page(dw->Vbox, dw->selsig);
147
148     g_free(dw);
149     dw = NULL;
150 }
151
152 static char *orage_tm_date_to_i18_date(struct tm *tm_date)
153 {
154     static char i18_date[32];
155
156     if (strftime(i18_date, 32, "%x", tm_date) == 0)
157         g_error("Orage: orage_tm_date_to_i18_date too long string in strftime");
158     return(i18_date);
159 }
160
161 static void changeSelectedDate(day_win *dw, gint day)
162 {
163     if (day > 0) {
164      do {
165       orage_move_day(&(dw->startdate), 1);
166      } while (--day > 0);
167     } else {
168      do {
169       orage_move_day(&(dw->startdate), -1);
170      } while (++day < 0);
171     }
172 }
173
174 static gint on_Previous_clicked(GtkWidget *button, GdkEventButton *event,
175                                     day_win *dw)
176 {
177     int days = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(dw->day_spin));
178     changeSelectedDate(dw, -days);
179     refresh_day_win(dw);
180     return TRUE;
181 }
182
183 static gint on_Next_clicked(GtkWidget *button, GdkEventButton *event,
184                                     day_win *dw)
185 {
186     int days = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(dw->day_spin));
187     changeSelectedDate(dw, +days);
188     refresh_day_win(dw);
189     return TRUE;
190 }
191
192 static void dw_summary_selected(GtkCMCTree *ctree, GtkCMCTreeNode *row,
193                              gint column, day_win *dw)
194 {
195         MsgInfo *msginfo = gtk_cmctree_node_get_row_data(ctree, row);
196         int days = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(dw->day_spin));
197         
198         if (msginfo && msginfo->msgid) {
199                 VCalEvent *event = vcal_manager_load_event(msginfo->msgid);
200                 if (event) {
201                         time_t t_first = mktime(&dw->startdate);
202                         time_t t_start = icaltime_as_timet(icaltime_from_string(event->dtstart));
203                         struct tm tm_start;
204                         gboolean changed = FALSE;
205                         GtkAdjustment *v_adj;
206
207 #ifdef G_OS_WIN32
208                         if (t_start < 0)
209                                 t_start = 1;
210 #endif
211                         localtime_r(&t_start, &tm_start);
212                         tm_start.tm_hour = tm_start.tm_min = tm_start.tm_sec = 0;
213                         t_start = mktime(&tm_start);
214
215                         while (t_start < t_first) {
216                                 changeSelectedDate(dw, -days);
217                                 t_first = mktime(&dw->startdate);
218                                 changed = TRUE;
219                         }
220                         while (t_start > t_first + (days-1)*24*60*60) {
221                                 changeSelectedDate(dw, +days);
222                                 t_first = mktime(&dw->startdate);
223                                 changed = TRUE;
224                         }
225                         
226                         t_start = icaltime_as_timet(icaltime_from_string(event->dtstart));
227 #ifdef G_OS_WIN32
228                         if (t_start < 0)
229                                 t_start = 1;
230 #endif
231                         localtime_r(&t_start, &tm_start);
232                         if (changed) {
233                                 debug_print("changed from %s\n", event->summary);
234                                 v_adj = gtk_scrolled_window_get_vadjustment(
235                                     GTK_SCROLLED_WINDOW(dw->scroll_win)); 
236 #ifdef G_OS_WIN32
237                                 if (t_start < 0)
238                                         t_start = 1;
239 #endif
240                                 localtime_r(&t_start, &tm_start);
241                                 if (tm_start.tm_hour > 2)
242                                         gtk_adjustment_set_value(v_adj, 
243                                                 ((v_adj->upper-v_adj->page_size)/(gdouble)(24/(tm_start.tm_hour-2))));
244                                 else
245                                         gtk_adjustment_set_value(v_adj, 0);
246                                 gtk_adjustment_changed(v_adj);
247                                 refresh_day_win(dw);
248                         }
249                 }
250                 vcal_manager_free_event(event);
251         }
252 }
253
254 static void day_view_new_meeting_cb(day_win *dw, gpointer data_i, gpointer data_s)
255 {
256     int offset = GPOINTER_TO_INT(data_i);
257     struct tm tm_date = dw->startdate;
258     int offset_h = offset % 1000;
259     int offset_d = (offset-offset_h) / 1000;
260     guint monthdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};    
261     int mon = tm_date.tm_mon;
262     
263     if (((tm_date.tm_year%4) == 0) && (((tm_date.tm_year%100) != 0) 
264             || ((tm_date.tm_year%400) == 0)))
265         monthdays[1] = 29;
266
267     if (offset_d > (int)monthdays[mon]) {
268         while (tm_date.tm_mday > 1)
269                 orage_move_day(&tm_date, 1);
270         offset_d -= monthdays[mon];
271     }
272
273     while (offset_d > tm_date.tm_mday) {
274             orage_move_day(&tm_date, 1);
275     }
276     while (offset_d < tm_date.tm_mday) {
277             orage_move_day(&tm_date, -1);
278     }
279     tm_date.tm_hour = offset_h;
280     vcal_meeting_create_with_start(NULL, &tm_date);
281 }
282
283 static void day_view_edit_meeting_cb(day_win *dw, gpointer data_i, gpointer data_s)
284 {
285         const gchar *uid = (gchar *)data_s;
286         vcal_view_select_event (uid, dw->item, TRUE,
287                             G_CALLBACK(dw_summary_selected), dw);
288 }
289
290 static void day_view_cancel_meeting_cb(day_win *dw, gpointer data_i, gpointer data_s)
291 {
292         const gchar *uid = (gchar *)data_s;
293         vcalendar_cancel_meeting(dw->item, uid);
294 }
295
296 static void day_view_today_cb(day_win *dw, gpointer data_i, gpointer data_s)
297 {
298     time_t now = time(NULL);
299     struct tm tm_today;
300     localtime_r(&now, &tm_today);
301
302     while (tm_today.tm_wday != 1)
303         orage_move_day(&tm_today, -1);
304     
305     dw->startdate = tm_today;
306     refresh_day_win(dw);
307 }
308
309 static void header_button_clicked_cb(GtkWidget *button, day_win *dw)
310 {
311     int offset = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "offset"));
312
313     day_view_new_meeting_cb(dw, GINT_TO_POINTER(offset), NULL);
314 }
315
316 static void on_button_press_event_cb(GtkWidget *widget
317         , GdkEventButton *event, gpointer *user_data)
318 {
319     day_win *dw = (day_win *)user_data;
320     gchar *uid = g_object_get_data(G_OBJECT(widget), "UID");
321     gpointer offset = g_object_get_data(G_OBJECT(widget), "offset");
322  
323     if (event->button == 1) {
324         if (uid)
325             vcal_view_select_event (uid, dw->item, (event->type==GDK_2BUTTON_PRESS),
326                             G_CALLBACK(dw_summary_selected), dw);
327     }
328     if (event->button == 3) {
329             g_object_set_data(G_OBJECT(dw->Vbox), "menu_win",
330                       dw);
331             g_object_set_data(G_OBJECT(dw->Vbox), "menu_data_i",
332                       offset);
333             g_object_set_data(G_OBJECT(dw->Vbox), "menu_data_s",
334                       uid);
335             g_object_set_data(G_OBJECT(dw->Vbox), "new_meeting_cb",
336                       day_view_new_meeting_cb);
337             g_object_set_data(G_OBJECT(dw->Vbox), "edit_meeting_cb",
338                       day_view_edit_meeting_cb);
339             g_object_set_data(G_OBJECT(dw->Vbox), "cancel_meeting_cb",
340                       day_view_cancel_meeting_cb);
341             g_object_set_data(G_OBJECT(dw->Vbox), "go_today_cb",
342                       day_view_today_cb);
343             if (uid)
344                     gtk_menu_popup(GTK_MENU(dw->event_menu), 
345                            NULL, NULL, NULL, NULL, 
346                            event->button, event->time);    
347             else
348                     gtk_menu_popup(GTK_MENU(dw->view_menu), 
349                            NULL, NULL, NULL, NULL, 
350                            event->button, event->time);    
351     }
352 }
353
354 static void add_row(day_win *dw, VCalEvent *event, gint days)
355 {
356     gint row, start_row, end_row;
357     gint col, start_col, end_col, first_col, last_col;
358     gint height, start_height, end_height;
359     gchar *text, *tip, *start_date, *end_date;
360     GtkWidget *ev, *lab, *hb;
361     time_t t_start, t_end;
362     struct tm tm_first, tm_start, tm_end;
363
364     /* First clarify timings */
365     t_start = icaltime_as_timet(icaltime_from_string(event->dtstart));
366     if (event->dtend && *event->dtend)
367         t_end = icaltime_as_timet(icaltime_from_string(event->dtend));
368     else 
369         t_end = t_start;
370
371 #ifdef G_OS_WIN32
372         if (t_start < 0)
373                 t_start = 1;
374         if (t_end < 0)
375                 t_end = 1;
376 #endif
377     localtime_r(&t_start, &tm_start);
378     localtime_r(&t_end, &tm_end);
379     tm_first = dw->startdate;
380    
381     tm_first.tm_year += 1900;
382     tm_first.tm_mon += 1;
383     tm_start.tm_year += 1900;
384     tm_start.tm_mon += 1;
385     tm_end.tm_year += 1900;
386     tm_end.tm_mon += 1;
387
388     start_col = orage_days_between(&tm_first, &tm_start)+1;
389     end_col   = orage_days_between(&tm_first, &tm_end)+1;
390
391     if (end_col < 1)
392         return;
393     if (start_col > days)
394         return;
395
396     else {
397         col = start_col;
398         row = tm_start.tm_hour;
399     }
400
401     /* then add the appointment */
402     text = g_strdup(event->summary?event->summary : _("Unknown"));
403     ev = gtk_event_box_new();
404     lab = gtk_label_new(text);
405     gtk_misc_set_alignment(GTK_MISC(lab), 0.0, 0.5);
406     gtk_label_set_ellipsize(GTK_LABEL(lab), PANGO_ELLIPSIZE_END);
407     gtk_container_add(GTK_CONTAINER(ev), lab);
408
409     if ((row % 2) == 1)
410         gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &dw->bg1);
411     else
412         gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &dw->bg2);
413     if (dw->element[row][col] == NULL) {
414         hb = gtk_hbox_new(TRUE, 3);
415         dw->element[row][col] = hb;
416     }
417     else {
418         hb = dw->element[row][col];
419         /* FIXME: set some real bar here to make it visible that we
420          * have more than 1 appointment here
421          */
422     }
423     if (orage_days_between(&tm_start, &tm_end) == 0)
424         tip = g_strdup_printf("%s\n%02d:%02d-%02d:%02d\n%s"
425                 , text, tm_start.tm_hour, tm_start.tm_min
426                 , tm_end.tm_hour, tm_end.tm_min, event->description);
427     else {
428 /* we took the date in unnormalized format, so we need to do that now */
429         start_date = g_strdup(orage_tm_date_to_i18_date(&tm_start));
430         end_date = g_strdup(orage_tm_date_to_i18_date(&tm_end));
431         tip = g_strdup_printf("%s\n%s %02d:%02d - %s %02d:%02d\n%s"
432                 , text
433                 , start_date, tm_start.tm_hour, tm_start.tm_min
434                 , end_date, tm_end.tm_hour, tm_end.tm_min
435                 , event->description);
436         g_free(start_date);
437         g_free(end_date);
438     }
439     CLAWS_SET_TIP(ev, tip);
440     /*
441     gtk_box_pack_start(GTK_BOX(hb2), ev, FALSE, FALSE, 0);
442     gtk_box_pack_start(GTK_BOX(hb), hb2, TRUE, TRUE, 0);
443     */
444     gtk_box_pack_start(GTK_BOX(hb), ev, TRUE, TRUE, 0);
445     g_object_set_data_full(G_OBJECT(ev), "UID", g_strdup(event->uid), g_free);
446     g_object_set_data(G_OBJECT(ev), "offset", GINT_TO_POINTER(tm_start.tm_mday*1000 + tm_start.tm_hour));
447     g_signal_connect((gpointer)ev, "button-press-event"
448             , G_CALLBACK(on_button_press_event_cb), dw);
449     g_free(tip);
450     g_free(text);
451
452     /* and finally draw the line to show how long the appointment is,
453      * but only if it is Busy type event (=availability != 0) 
454      * and it is not whole day event */
455     if (TRUE) {
456         height = dw->StartDate_button_req.height;
457         /*
458          * same_date = !strncmp(start_ical_time, end_ical_time, 8);
459          * */
460         if (start_col < 1)
461             first_col = 1;
462         else
463             first_col = start_col;
464         if (end_col > days)
465             last_col = days;
466         else
467             last_col = end_col;
468         for (col = first_col; col <= last_col; col++) {
469             if (col == start_col)
470                 start_row = tm_start.tm_hour;
471             else
472                 start_row = 0;
473             if (col == end_col)
474                 end_row   = tm_end.tm_hour;
475             else
476                 end_row   = 23;
477             for (row = start_row; row <= end_row; row++) {
478                 if (row == tm_start.tm_hour && col == start_col)
479                     start_height = tm_start.tm_min*height/60;
480                 else
481                     start_height = 0;
482                 if (row == tm_end.tm_hour && col == end_col)
483                     end_height = tm_end.tm_min*height/60;
484                 else
485                     end_height = height;
486                 dw->line[row][col] = build_line(1, start_height
487                         , 2, end_height-start_height, dw->line[row][col]
488                         , &dw->line_color);
489             }
490         }
491     }
492 }
493
494 static void app_rows(day_win *dw, FolderItem *item)
495 {
496    GSList *events = vcal_get_events_list(item);
497    GSList *cur = NULL;
498    int days = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(dw->day_spin));
499    for (cur = events; cur ; cur = cur->next) {
500         VCalEvent *event = (VCalEvent *) (cur->data);
501         add_row(dw, event, days);
502         vcal_manager_free_event(event);
503    }
504    g_slist_free(events);
505 }
506
507 static void app_data(day_win *dw, FolderItem *item)
508 {
509     app_rows(dw, item);
510 }
511
512
513 static void fill_days(day_win *dw, gint days, FolderItem *item, gint first_col_day)
514 {
515     gint row, col, height, width;
516     GtkWidget *ev, *hb;
517
518     height = dw->StartDate_button_req.height;
519     width = dw->StartDate_button_req.width;
520
521     /* first clear the structure */
522     for (col = 1; col <  days+1; col++) {
523         dw->header[col] = NULL;
524         for (row = 0; row < 24; row++) {
525             dw->element[row][col] = NULL;
526     /* gdk_draw_rectangle(, , , left_x, top_y, width, height); */
527             dw->line[row][col] = build_line(0, 0, 3, height, NULL
528                         , &dw->line_color);
529         }
530     }
531
532     app_data(dw, item);
533
534     for (col = 1; col < days+1; col++) {
535         hb = gtk_hbox_new(FALSE, 0);
536         /* check if we have full day events and put them to header */
537         if (dw->header[col]) {
538             gtk_box_pack_start(GTK_BOX(hb), dw->header[col], TRUE, TRUE, 0);
539             gtk_widget_set_size_request(hb, width, -1);
540         }
541         else {
542             ev = gtk_event_box_new();
543             gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &dw->bg2);
544             gtk_box_pack_start(GTK_BOX(hb), ev, TRUE, TRUE, 0);
545         }
546         gtk_table_attach(GTK_TABLE(dw->dtable_h), hb, col, col+1, 1, 2
547                  , (GTK_FILL), (0), 0, 0);
548
549         /* check rows */
550         for (row = 0; row < 24; row++) {
551             hb = gtk_hbox_new(FALSE, 0);
552             if (row == 0)
553                 gtk_widget_set_size_request(hb, width, -1);
554             if (dw->element[row][col]) {
555                 gtk_box_pack_start(GTK_BOX(hb), dw->line[row][col]
556                         , FALSE, FALSE, 0);
557                 gtk_box_pack_start(GTK_BOX(hb), dw->element[row][col]
558                         , TRUE, TRUE, 0);
559                 gtk_widget_set_size_request(hb, width, -1);
560             }
561             else {
562                 ev = gtk_event_box_new();
563                 g_object_set_data(G_OBJECT(ev), "offset", GINT_TO_POINTER(first_col_day*1000 + row));
564                 g_signal_connect((gpointer)ev, "button-press-event"
565                         , G_CALLBACK(on_button_press_event_cb), dw);
566                 /*
567                 name = gtk_label_new(" ");
568                 gtk_container_add(GTK_CONTAINER(ev), name);
569                 */
570                 if ((row % 2) == 1)
571                     gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &dw->bg1);
572                 else
573                     gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &dw->bg2);
574                 gtk_box_pack_start(GTK_BOX(hb), dw->line[row][col]
575                         , FALSE, FALSE, 0);
576                 gtk_box_pack_start(GTK_BOX(hb), ev, TRUE, TRUE, 0);
577             }
578             gtk_table_attach(GTK_TABLE(dw->dtable), hb, col, col+1, row, row+1
579                      , (GTK_FILL), (0), 0, 0);
580         }
581         first_col_day++;
582     }
583 }
584
585 static void build_day_view_header(day_win *dw, char *start_date)
586 {
587     GtkWidget *hbox, *label, *space_label;
588     SummaryView *summaryview = NULL;
589     int avail_w = 0, avail_d = 0;
590
591     hbox = gtk_hbox_new(FALSE, 0);
592
593     label = gtk_label_new(_("Start"));
594     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
595
596     /* start date button */
597     dw->StartDate_button = gtk_button_new();
598     gtk_box_pack_start(GTK_BOX(hbox), dw->StartDate_button, FALSE, FALSE, 0);
599
600     space_label = gtk_label_new("  ");
601     gtk_box_pack_start(GTK_BOX(hbox), space_label, FALSE, FALSE, 0);
602
603     space_label = gtk_label_new("     ");
604     gtk_box_pack_start(GTK_BOX(hbox), space_label, FALSE, FALSE, 0);
605
606     label = gtk_label_new(_("Show"));
607     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
608
609     /* show days spin = how many days to show */
610     dw->day_spin = gtk_spin_button_new_with_range(1, MAX_DAYS, 1);
611     gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(dw->day_spin), TRUE);
612     gtk_widget_set_size_request(dw->day_spin, 40, -1);
613     gtk_box_pack_start(GTK_BOX(hbox), dw->day_spin, FALSE, FALSE, 0);
614     label = gtk_label_new(_("days"));
615     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
616
617     space_label = gtk_label_new("   ");
618     gtk_box_pack_start(GTK_BOX(hbox), space_label, FALSE, FALSE, 0);
619
620     /* sizes */
621     gtk_button_set_label(GTK_BUTTON(dw->StartDate_button)
622             , (const gchar *)start_date);
623     gtk_widget_size_request(dw->StartDate_button, &dw->StartDate_button_req);
624     dw->StartDate_button_req.width += dw->StartDate_button_req.width/10;
625     label = gtk_label_new("00");
626     gtk_widget_size_request(label, &dw->hour_req);
627
628     if (mainwindow_get_mainwindow()) {
629         GtkAllocation allocation;
630         summaryview = mainwindow_get_mainwindow()->summaryview;
631         allocation = summaryview->mainwidget_book->allocation;
632         
633         avail_w = allocation.width - 20 - 2*(dw->hour_req.width);
634         avail_d = avail_w / dw->StartDate_button_req.width;
635     }
636     if (avail_d >= 7) {
637         avail_d = 7;
638         gtk_widget_set_size_request(dw->StartDate_button, avail_w / avail_d, -1);
639         gtk_widget_size_request(dw->StartDate_button, &dw->StartDate_button_req);
640     }
641    
642     /* initial values */
643     if (avail_d)
644         gtk_spin_button_set_value(GTK_SPIN_BUTTON(dw->day_spin), avail_d);
645
646 }
647
648 static void build_day_view_colours(day_win *dw)
649 {
650     GtkStyle *def_style;
651     GdkColormap *pic1_cmap;
652     GtkWidget *ctree = NULL;
653     def_style = gtk_widget_get_default_style();
654     pic1_cmap = gdk_colormap_get_system();
655     
656     if (mainwindow_get_mainwindow()) {
657         ctree = mainwindow_get_mainwindow()->summaryview->ctree;
658     }
659     if (ctree) {
660         dw->bg1 = ctree->style->bg[GTK_STATE_NORMAL];
661         dw->bg2 = ctree->style->bg[GTK_STATE_NORMAL];
662     } else {
663         dw->bg1 = def_style->bg[GTK_STATE_NORMAL];
664         dw->bg2 = def_style->bg[GTK_STATE_NORMAL];
665     }
666     dw->bg1.red +=  (dw->bg1.red < 63000 ? 2000 : -2000);
667     dw->bg1.green += (dw->bg1.green < 63000 ? 2000 : -2000);
668     dw->bg1.blue += (dw->bg1.blue < 63000 ? 2000 : -2000);
669     gdk_colormap_alloc_color(pic1_cmap, &dw->bg1, FALSE, TRUE);
670
671     dw->bg2.red +=  (dw->bg2.red > 1000 ? -1000 : 1000);
672     dw->bg2.green += (dw->bg2.green > 1000 ? -1000 : 1000);
673     dw->bg2.blue += (dw->bg2.blue > 1000 ? -1000 : 1000);
674     gdk_colormap_alloc_color(pic1_cmap, &dw->bg2, FALSE, TRUE);
675
676     if (!gdk_color_parse("white", &dw->line_color)) {
677         g_warning("color parse failed: white");
678         dw->line_color.red =  239 * (65535/255);
679         dw->line_color.green = 235 * (65535/255);
680         dw->line_color.blue = 230 * (65535/255);
681     }
682
683     if (!gdk_color_parse("blue", &dw->fg_sunday)) {
684         g_warning("color parse failed: blue");
685         dw->fg_sunday.red = 10 * (65535/255);
686         dw->fg_sunday.green = 10 * (65535/255);
687         dw->fg_sunday.blue = 255 * (65535/255);
688     }
689
690     if (!gdk_color_parse("gold", &dw->bg_today)) {
691         g_warning("color parse failed: gold");
692         dw->bg_today.red = 255 * (65535/255);
693         dw->bg_today.green = 215 * (65535/255);
694         dw->bg_today.blue = 115 * (65535/255);
695     }
696
697     if (ctree) {
698         dw->fg_sunday.red = (dw->fg_sunday.red + ctree->style->fg[GTK_STATE_SELECTED].red)/2;
699         dw->fg_sunday.green = (dw->fg_sunday.green + ctree->style->fg[GTK_STATE_SELECTED].red)/2;
700         dw->fg_sunday.blue = (3*dw->fg_sunday.blue + ctree->style->fg[GTK_STATE_SELECTED].red)/4;
701         dw->bg_today.red = (3*dw->bg_today.red + ctree->style->bg[GTK_STATE_NORMAL].red)/4;
702         dw->bg_today.green = (3*dw->bg_today.green + ctree->style->bg[GTK_STATE_NORMAL].red)/4;
703         dw->bg_today.blue = (3*dw->bg_today.blue + ctree->style->bg[GTK_STATE_NORMAL].red)/4;
704     }
705
706     gdk_colormap_alloc_color(pic1_cmap, &dw->line_color, FALSE, TRUE);
707     gdk_colormap_alloc_color(pic1_cmap, &dw->fg_sunday, FALSE, TRUE);
708     gdk_colormap_alloc_color(pic1_cmap, &dw->bg_today, FALSE, TRUE);
709 }
710
711 static void fill_hour(day_win *dw, gint col, gint row, char *text)
712 {
713     GtkWidget *name, *ev;
714
715     ev = gtk_event_box_new();
716     name = gtk_label_new(text);
717     gtk_container_add(GTK_CONTAINER(ev), name);
718     if ((row % 2) == 1)
719         gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &dw->bg1);
720     else
721         gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &dw->bg2);
722     gtk_widget_set_size_request(ev, dw->hour_req.width
723             , dw->StartDate_button_req.height);
724     if (text)
725         gtk_table_attach(GTK_TABLE(dw->dtable), ev, col, col+1, row, row+1
726              , (GTK_FILL), (0), 0, 0);
727     else  /* special, needed for header table full day events */
728         gtk_table_attach(GTK_TABLE(dw->dtable_h), ev, col, col+1, row, row+1
729              , (GTK_FILL), (0), 0, 0);
730 }
731
732 static void build_day_view_table(day_win *dw)
733 {
734     gint days;   /* number of days to show */
735     gint i = 0;
736     GtkWidget *label, *button;
737     char text[5+1], *date, *today;
738     struct tm tm_date, tm_today;
739     guint monthdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
740     GtkWidget *vp;
741     time_t t = time(NULL);
742     GtkWidget *arrow;
743     gchar *tip;
744     gint first_col_day = -1;
745
746 #ifdef G_OS_WIN32
747         if (t < 0)
748                 t = 1;
749 #endif
750     localtime_r(&t, &tm_today);
751     days = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(dw->day_spin));
752     today = get_locale_date(&tm_today);
753     /****** header of day table = days columns ******/
754     dw->scroll_win_h = gtk_scrolled_window_new(NULL, NULL);
755     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dw->scroll_win_h)
756             , GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
757     gtk_box_pack_start(GTK_BOX(dw->Vbox), dw->scroll_win_h
758             , TRUE, TRUE, 0);
759     dw->day_view_vbox = gtk_vbox_new(FALSE, 0);
760     gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(dw->scroll_win_h)
761             , dw->day_view_vbox);
762     /*
763     gtk_container_add(GTK_CONTAINER(dw->scroll_win_h), dw->day_view_vbox);
764     */
765     /* row 1= day header buttons 
766      * row 2= full day events after the buttons */
767     dw->dtable_h = gtk_table_new(2 , days+2, FALSE);
768     gtk_box_pack_start(GTK_BOX(dw->day_view_vbox), dw->dtable_h
769             , FALSE, FALSE, 0);
770
771     tm_date = dw->startdate;
772
773     if (((tm_date.tm_year%4) == 0) && (((tm_date.tm_year%100) != 0) 
774             || ((tm_date.tm_year%400) == 0)))
775         monthdays[1] = 29;
776
777
778     i=0;
779     dw->Previous_toolbutton = gtk_event_box_new();
780     gtk_event_box_set_visible_window(GTK_EVENT_BOX(dw->Previous_toolbutton), FALSE);
781     gtk_container_set_border_width(GTK_CONTAINER(dw->Previous_toolbutton), 0);
782     arrow = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_NONE);
783     gtk_container_add(GTK_CONTAINER(dw->Previous_toolbutton), arrow);
784     gtk_table_attach(GTK_TABLE(dw->dtable_h), dw->Previous_toolbutton, i, i+1, 0, 1
785                 , (GTK_FILL), (0), 0, 0);
786     gtk_widget_show_all(dw->Previous_toolbutton);
787     g_signal_connect((gpointer)dw->Previous_toolbutton, "button_release_event"
788             , G_CALLBACK(on_Previous_clicked), dw);
789     tip = g_strdup_printf("Back %d days", days);
790     CLAWS_SET_TIP(dw->Previous_toolbutton, tip);
791     g_free(tip);
792     for (i = 1; i < days+1; i++) {
793         tip = g_malloc(100);
794         strftime(tip, 99, "%A %d %B %Y", &tm_date);
795         if (first_col_day == -1)
796                 first_col_day = tm_date.tm_mday;
797         date = get_locale_date(&tm_date);
798         button = gtk_button_new();
799         gtk_button_set_label(GTK_BUTTON(button), date);
800         if (strcmp(today, date) == 0) {
801             gtk_widget_modify_bg(button, GTK_STATE_NORMAL, &dw->bg_today);
802         }
803
804         if (tm_date.tm_wday % 7 == 0) {
805             label = gtk_bin_get_child(GTK_BIN(button));
806             gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &dw->fg_sunday);
807         }
808         CLAWS_SET_TIP(button, tip);
809         g_free(tip);
810         gtk_widget_set_size_request(button, dw->StartDate_button_req.width, -1);
811         g_signal_connect((gpointer)button, "clicked"
812                 , G_CALLBACK(header_button_clicked_cb), dw);
813         g_object_set_data(G_OBJECT(button), "offset", GINT_TO_POINTER(tm_date.tm_mday*1000));
814         gtk_table_attach(GTK_TABLE(dw->dtable_h), button, i, i+1, 0, 1
815                 , (GTK_FILL), (0), 0, 0);
816
817         if (++tm_date.tm_mday == (int)(monthdays[tm_date.tm_mon]+1)) {
818             if (++tm_date.tm_mon == 12) {
819                 ++tm_date.tm_year;
820                 tm_date.tm_mon = 0;
821             }
822             tm_date.tm_mday = 1;
823         }
824         ++tm_date.tm_wday; tm_date.tm_wday %=7;
825         g_free(date);
826     }
827
828     dw->Next_toolbutton = gtk_event_box_new();
829     gtk_event_box_set_visible_window(GTK_EVENT_BOX(dw->Next_toolbutton), FALSE);
830     gtk_container_set_border_width(GTK_CONTAINER(dw->Next_toolbutton), 0);
831     arrow = gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
832     gtk_container_add(GTK_CONTAINER(dw->Next_toolbutton), arrow);
833     gtk_table_attach(GTK_TABLE(dw->dtable_h), dw->Next_toolbutton, i, i+1, 0, 1
834                 , (GTK_FILL), (0), 0, 0);
835     gtk_widget_show_all(dw->Next_toolbutton);
836     g_signal_connect((gpointer)dw->Next_toolbutton, "button_release_event"
837             , G_CALLBACK(on_Next_clicked), dw);
838     tip = g_strdup_printf("Forward %d days", days);
839     CLAWS_SET_TIP(dw->Next_toolbutton, tip);
840     g_free(tip);
841     g_free(today);
842
843     /****** body of day table ******/
844     dw->scroll_win = gtk_scrolled_window_new(NULL, NULL);
845     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(dw->scroll_win)
846             , GTK_SHADOW_NONE);
847     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dw->scroll_win)
848             , GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
849     gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(dw->scroll_win)
850             , GTK_CORNER_TOP_LEFT);
851     gtk_box_pack_start(GTK_BOX(dw->day_view_vbox), dw->scroll_win
852             , TRUE, TRUE, 0);
853     vp = gtk_viewport_new(NULL, NULL);
854     gtk_viewport_set_shadow_type(GTK_VIEWPORT(vp), GTK_SHADOW_NONE);
855     gtk_container_add(GTK_CONTAINER(dw->scroll_win), vp);
856     dw->dtable = gtk_table_new(24, days+2, FALSE);
857     gtk_container_add(GTK_CONTAINER(vp), dw->dtable);
858
859     gtk_widget_show_all(dw->dtable_h);
860     /* hours column = hour rows */
861     for (i = 0; i < 24; i++) {
862         g_sprintf(text, "%02d", i);
863         /* ev is needed for background colour */
864         fill_hour(dw, 0, i, text);
865         fill_hour(dw, days+1, i, text);
866     }
867     fill_days(dw, days, dw->item, first_col_day);
868 }
869
870 void refresh_day_win(day_win *dw)
871 {
872     get_scroll_position(dw);
873     gtk_widget_destroy(dw->scroll_win_h);
874     build_day_view_table(dw);
875     gtk_widget_show_all(dw->scroll_win_h);
876     /* I was not able to get this work without the timer. Ugly yes, but
877      * it works and does not hurt - much */
878     g_timeout_add(100, (GSourceFunc)scroll_position_timer, (gpointer)dw);
879 }
880
881 day_win *create_day_win(FolderItem *item, struct tm tmdate)
882 {
883     day_win *dw;
884     char *start_date = get_locale_date(&tmdate);
885
886     /* initialisation + main window + base vbox */
887     dw = g_new0(day_win, 1);
888     dw->scroll_pos = -1; /* not set */
889
890     dw->accel_group = gtk_accel_group_new();
891     
892     while (tmdate.tm_wday != 1)
893         orage_move_day(&tmdate, -1);
894     
895     dw->startdate = tmdate;
896     dw->startdate.tm_hour = 0;
897     dw->startdate.tm_min = 0;
898     dw->startdate.tm_sec = 0;
899     dw->Vbox = gtk_vbox_new(FALSE, 0);
900
901     dw->item = item;
902     build_day_view_colours(dw);
903     build_day_view_header(dw, start_date);
904     build_day_view_table(dw);
905     gtk_widget_show_all(dw->Vbox);
906     dw->selsig = vcal_view_set_calendar_page(dw->Vbox, 
907                     G_CALLBACK(dw_summary_selected), dw);
908     vcal_view_create_popup_menus(dw->Vbox, &dw->view_menu, 
909                                  &dw->event_menu, &dw->event_group,
910                                  &dw->ui_manager);
911
912     g_timeout_add(100, (GtkFunction)scroll_position_timer, (gpointer)dw);
913
914     return(dw);
915 }