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