6ecc834ac69efb956b6138a46ac45198e04c8cdc
[claws.git] / src / plugins / vcalendar / month-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 _month_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 *month_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[6][MAX_DAYS];
94     GtkWidget *line[6][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 gchar *dayname[7] = {
111         N_("Monday"),
112         N_("Tuesday"),
113         N_("Wednesday"),
114         N_("Thursday"),
115         N_("Friday"),
116         N_("Saturday"),
117         N_("Sunday")
118         };
119 gchar *monthname[12] = {
120         N_("January"),
121         N_("February"),
122         N_("March"),
123         N_("April"),
124         N_("May"),
125         N_("June"),
126         N_("July"),
127         N_("August"),
128         N_("September"),
129         N_("October"),
130         N_("November"),
131         N_("December")
132         };
133
134 static gchar *get_locale_date(struct tm *tmdate)
135 {
136         gchar *d = g_malloc(100);
137         strftime(d, 99, "%x", tmdate);
138         return d;
139 }
140
141 void mw_close_window(month_win *mw)
142 {
143     vcal_view_set_summary_page(mw->Vbox, mw->selsig);
144     
145 #if !(GTK_CHECK_VERSION(2,12,0))
146     gtk_object_destroy(G_OBJECT(mw->Tooltips));
147 #endif
148     g_free(mw);
149     mw = NULL;
150 }
151
152 static char *orage_tm_date_to_i18_date(struct tm *tm_date)
153 {
154     static char i18_date[32];
155     struct tm t;
156     t.tm_mday = tm_date->tm_mday;
157     t.tm_mon = tm_date->tm_mon - 1;
158     t.tm_year = tm_date->tm_year - 1900;
159     if (strftime(i18_date, 32, "%x", &t) == 0)
160         g_error("Orage: orage_tm_date_to_i18_date too long string in strftime");
161     return(i18_date);
162 }
163
164 static void changeSelectedDate(month_win *mw, gint month)
165 {
166     gint curmon = mw->startdate.tm_mon;
167     if (month > 0) {
168      do { /* go to first of next month */
169       orage_move_day(&(mw->startdate), 1);
170      } while (curmon == mw->startdate.tm_mon);
171     } else {
172      do { /* go to last day of last month */
173       orage_move_day(&(mw->startdate), -1);
174      } while (curmon == mw->startdate.tm_mon);
175      do { /* go to first of last month */
176       orage_move_day(&(mw->startdate), -1);
177      } while (mw->startdate.tm_mday > 1);
178     }
179 }
180
181 static gint on_Previous_clicked(GtkWidget *button, GdkEventButton *event,
182                                     month_win *mw)
183 {
184     changeSelectedDate(mw, -1);
185     refresh_month_win(mw);
186     return TRUE;
187 }
188
189 static gint on_Next_clicked(GtkWidget *button, GdkEventButton *event,
190                                     month_win *mw)
191 {
192     changeSelectedDate(mw, +1);
193     refresh_month_win(mw);
194     return TRUE;
195 }
196
197 static void mw_summary_selected(GtkCMCTree *ctree, GtkCMCTreeNode *row,
198                              gint column, month_win *mw)
199 {
200         MsgInfo *msginfo = gtk_cmctree_node_get_row_data(ctree, row);
201         
202         if (msginfo && msginfo->msgid) {
203                 VCalEvent *event = vcal_manager_load_event(msginfo->msgid);
204                 if (event) {
205                         struct tm tm_start;
206                         time_t t_start = icaltime_as_timet(icaltime_from_string(event->dtstart));
207                         gboolean changed = FALSE;
208
209 #ifdef G_OS_WIN32
210                         if (t_start < 0)
211                                 t_start = 1;
212 #endif
213                         localtime_r(&t_start, &tm_start);
214                         while (tm_start.tm_year < mw->startdate.tm_year) {
215                                 changeSelectedDate(mw, -1);
216                                 changed = TRUE;
217                         }
218                         while (tm_start.tm_year > mw->startdate.tm_year) {
219                                 changeSelectedDate(mw, +1);
220                                 changed = TRUE;
221                         }
222                         while (tm_start.tm_mon < mw->startdate.tm_mon) {
223                                 changeSelectedDate(mw, -1);
224                                 changed = TRUE;
225                         }
226                         while (tm_start.tm_mon > mw->startdate.tm_mon) {
227                                 changeSelectedDate(mw, +1);
228                                 changed = TRUE;
229                         }
230                         if (changed)
231                                 refresh_month_win(mw);
232                 }
233                 vcal_manager_free_event(event);
234         }
235 }
236
237 static void month_view_new_meeting_cb(month_win *mw, gpointer data_i, gpointer data_s)
238 {
239     int offset = GPOINTER_TO_INT(data_i);
240     struct tm tm_date = mw->startdate;
241
242     while (offset > tm_date.tm_mday) {
243             orage_move_day(&tm_date, 1);
244     }
245     while (offset < tm_date.tm_mday) {
246             orage_move_day(&tm_date, -1);
247     }
248     tm_date.tm_hour = 0;
249     vcal_meeting_create_with_start(NULL, &tm_date);
250 }
251
252 static void month_view_edit_meeting_cb(month_win *mw, gpointer data_i, gpointer data_s)
253 {
254         const gchar *uid = (gchar *)data_s;
255         vcal_view_select_event (uid, mw->item, TRUE,
256                             G_CALLBACK(mw_summary_selected), mw);
257 }
258
259 static void month_view_cancel_meeting_cb(month_win *mw, gpointer data_i, gpointer data_s)
260 {
261         const gchar *uid = (gchar *)data_s;
262         vcalendar_cancel_meeting(mw->item, uid);
263 }
264
265 static void month_view_today_cb(month_win *mw, gpointer data_i, gpointer data_s)
266 {
267     time_t now = time(NULL);
268     struct tm tm_today;
269     localtime_r(&now, &tm_today);
270
271     while (tm_today.tm_mday != 1)
272         orage_move_day(&tm_today, -1);
273     
274     mw->startdate = tm_today;
275     refresh_month_win(mw);
276 }
277
278 static void header_button_clicked_cb(GtkWidget *button
279         , GdkEventButton *event, gpointer *user_data)
280 {
281     month_win *mw = (month_win *)user_data;
282     int offset = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "day"));
283
284     if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
285             month_view_new_meeting_cb(mw, GINT_TO_POINTER(offset), NULL);
286     }
287     if (event->button == 3) {
288                 g_object_set_data(G_OBJECT(mw->Vbox), "menu_win",
289                           mw);
290                 g_object_set_data(G_OBJECT(mw->Vbox), "menu_data_i",
291                           GINT_TO_POINTER(offset));
292                 g_object_set_data(G_OBJECT(mw->Vbox), "menu_data_s",
293                           NULL);
294                 g_object_set_data(G_OBJECT(mw->Vbox), "new_meeting_cb",
295                           month_view_new_meeting_cb);
296                 g_object_set_data(G_OBJECT(mw->Vbox), "go_today_cb",
297                           month_view_today_cb);
298                 gtk_menu_popup(GTK_MENU(mw->view_menu), 
299                                NULL, NULL, NULL, NULL, 
300                                event->button, event->time);
301     }
302 }
303
304 static void on_button_press_event_cb(GtkWidget *widget
305         , GdkEventButton *event, gpointer *user_data)
306 {
307     month_win *mw = (month_win *)user_data;
308     gchar *uid = g_object_get_data(G_OBJECT(widget), "UID");;
309     gpointer offset = g_object_get_data(G_OBJECT(widget), "offset");
310
311     if (event->button == 1) {
312         if (uid)
313             vcal_view_select_event (uid, mw->item, (event->type==GDK_2BUTTON_PRESS),
314                             G_CALLBACK(mw_summary_selected), mw);
315         else if (event->type == GDK_2BUTTON_PRESS) {
316             month_view_new_meeting_cb(mw, GINT_TO_POINTER(offset), NULL);
317         }
318     }
319     if (event->button == 3) {
320             g_object_set_data(G_OBJECT(mw->Vbox), "menu_win",
321                       mw);
322             g_object_set_data(G_OBJECT(mw->Vbox), "menu_data_i",
323                       offset);
324             g_object_set_data(G_OBJECT(mw->Vbox), "menu_data_s",
325                       uid);
326             g_object_set_data(G_OBJECT(mw->Vbox), "new_meeting_cb",
327                       month_view_new_meeting_cb);
328             g_object_set_data(G_OBJECT(mw->Vbox), "edit_meeting_cb",
329                       month_view_edit_meeting_cb);
330             g_object_set_data(G_OBJECT(mw->Vbox), "cancel_meeting_cb",
331                       month_view_cancel_meeting_cb);
332             g_object_set_data(G_OBJECT(mw->Vbox), "go_today_cb",
333                       month_view_today_cb);
334             gtk_menu_popup(GTK_MENU(mw->event_menu), 
335                            NULL, NULL, NULL, NULL, 
336                            event->button, event->time);    
337     }
338 }
339
340 static void add_row(month_win *mw, VCalEvent *event, gint days)
341 {
342     gint row, start_row, end_row, first_row, last_row;
343     gint col, start_col, end_col, first_col, last_col;
344     gint width, start_width, end_width;
345     gchar *text, *tip, *start_date, *end_date;
346     GtkWidget *ev = NULL, *lab = NULL, *hb;
347     time_t t_start, t_end;
348     struct tm tm_first, tm_start, tm_end;
349     guint monthdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
350     int weekoffset = -1;
351     gboolean pack = TRUE, update_tip = FALSE;
352     time_t now = time(NULL);
353     struct tm tm_today;
354     gboolean start_prev_mon = FALSE;
355
356 #if !(GTK_CHECK_VERSION(2,12,0))
357         GtkTooltips *tips = mw->Tooltips;
358 #endif
359
360     localtime_r(&now, &tm_today);
361
362     tm_today.tm_year += 1900;
363     tm_today.tm_mon++;
364
365     /* First clarify timings */
366     t_start = icaltime_as_timet(icaltime_from_string(event->dtstart));
367     if (event->dtend && *event->dtend)
368         t_end = icaltime_as_timet(icaltime_from_string(event->dtend));
369     else 
370         t_end = t_start;
371
372 #ifdef G_OS_WIN32
373         if (t_start < 0)
374                 t_start = 1;
375         if (t_end < 0)
376                 t_end = 1;
377 #endif
378     localtime_r(&t_start, &tm_start);
379     localtime_r(&t_end, &tm_end);
380     tm_first = mw->startdate;
381    
382     tm_first.tm_year += 1900;
383
384     if (((tm_first.tm_year%4) == 0) 
385     && (((tm_first.tm_year%100) != 0) || ((tm_first.tm_year%400) == 0)))
386         ++monthdays[1]; /* leap year, february has 29 days */
387
388
389     tm_first.tm_mon += 1;
390     tm_start.tm_year += 1900;
391     tm_start.tm_mon += 1;
392     tm_end.tm_year += 1900;
393     tm_end.tm_mon += 1;
394
395     start_col = orage_days_between(&tm_first, &tm_start)+1;
396     end_col   = orage_days_between(&tm_first, &tm_end)+1;
397
398     if (start_col < 0)
399         start_prev_mon = TRUE;
400
401     if (end_col < 1) {
402         return;
403     }
404     if (start_col > 0 && start_col > monthdays[tm_first.tm_mon-1]) {
405         return;
406     }
407     else {
408         GDate *edate = g_date_new_dmy(tm_end.tm_mday, tm_end.tm_mon, tm_end.tm_year);
409         GDate *fdate = g_date_new_dmy(1, tm_first.tm_mon, tm_first.tm_year);
410         GDate *sdate;
411         if (start_col >= 1) {
412           sdate = g_date_new_dmy(tm_start.tm_mday, tm_start.tm_mon, tm_start.tm_year);
413         } else {
414           sdate = g_date_new_dmy(1, tm_first.tm_mon, tm_first.tm_year);
415         } /* endif */
416
417         col = start_col = (int)g_date_get_weekday(sdate);
418         end_col = (int)g_date_get_weekday(edate);
419         weekoffset = (int)g_date_get_monday_week_of_year(fdate);
420         row = start_row = (int)g_date_get_monday_week_of_year(sdate) - weekoffset;
421         end_row = (int)g_date_get_monday_week_of_year(edate) - weekoffset;
422         g_date_free(fdate);
423         g_date_free(sdate);
424         g_date_free(edate);
425     }
426
427     if (end_col > 7)
428        end_col = 7;
429
430     /* then add the appointment */
431     text = g_strdup(event->summary?event->summary : _("Unknown"));
432
433     if (mw->element[row][col] == NULL) {
434         hb = gtk_vbox_new(TRUE, 1);
435         mw->element[row][col] = hb;
436     }
437     else {
438         GList *children = NULL;
439         hb = mw->element[row][col];
440         /* FIXME: set some real bar here to make it visible that we
441          * have more than 1 appointment here
442          */
443         children = gtk_container_get_children(GTK_CONTAINER(hb));
444         if (g_list_length(children) > 2) {
445                 pack = FALSE;
446                 update_tip = TRUE;
447         } else if (g_list_length(children) > 1) {
448                 pack = FALSE;
449                 update_tip = FALSE;
450         }
451         g_list_free(children);
452     }
453     if (pack || !update_tip) {
454        ev = gtk_event_box_new();
455        lab = gtk_label_new(text);
456        gtk_misc_set_alignment(GTK_MISC(lab), 0.0, 0.0);
457        gtk_label_set_ellipsize(GTK_LABEL(lab), PANGO_ELLIPSIZE_END);
458        if ((row % 2) == 1)
459            gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &mw->bg1);
460        else
461            gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &mw->bg2);
462     } else if (!pack && update_tip) {
463        GList *children = gtk_container_get_children(GTK_CONTAINER(hb));
464        ev = GTK_WIDGET(g_list_last(children)->data);
465        g_list_free(children);
466     }
467
468     if (ev && tm_start.tm_mday == tm_today.tm_mday && tm_start.tm_mon == tm_today.tm_mon 
469         && tm_start.tm_year == tm_today.tm_year)
470         gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &mw->bg_today);
471
472     if (orage_days_between(&tm_start, &tm_end) == 0)
473         tip = g_strdup_printf("%s\n%02d:%02d-%02d:%02d\n%s"
474                 , text, tm_start.tm_hour, tm_start.tm_min
475                 , tm_end.tm_hour, tm_end.tm_min, event->description);
476     else {
477 /* we took the date in unnormalized format, so we need to do that now */
478         start_date = g_strdup(orage_tm_date_to_i18_date(&tm_start));
479         end_date = g_strdup(orage_tm_date_to_i18_date(&tm_end));
480         tip = g_strdup_printf("%s\n%s %02d:%02d - %s %02d:%02d\n%s"
481                 , text
482                 , start_date, tm_start.tm_hour, tm_start.tm_min
483                 , end_date, tm_end.tm_hour, tm_end.tm_min
484                 , event->description);
485         g_free(start_date);
486         g_free(end_date);
487     }
488     if (pack) {
489         gtk_container_add(GTK_CONTAINER(ev), lab);
490         CLAWS_SET_TIP(ev, tip);
491         gtk_box_pack_start(GTK_BOX(hb), ev, TRUE, TRUE, 0);
492     } else if (!update_tip) {
493         gtk_label_set_label(GTK_LABEL(lab), "...");
494         gtk_container_add(GTK_CONTAINER(ev), lab);
495         CLAWS_SET_TIP(ev, tip);
496         gtk_box_pack_start(GTK_BOX(hb), ev, TRUE, TRUE, 0);
497     } else {
498 #if !GTK_CHECK_VERSION(2,12,0)
499         GtkTooltipsData *tdata = gtk_tooltips_data_get(ev);
500         gchar *new = g_strdup_printf("%s\n\n%s", tdata?tdata->tip_text:"", tip);
501 #else
502         gchar *old = gtk_widget_get_tooltip_text(ev);
503         gchar *new = g_strdup_printf("%s\n\n%s", old?old:"", tip);
504         g_free(old);
505 #endif
506         CLAWS_SET_TIP(ev, new);
507         g_free(new);
508     }
509     g_object_set_data_full(G_OBJECT(ev), "UID", g_strdup(event->uid), g_free);
510     g_object_set_data(G_OBJECT(ev), "offset", GINT_TO_POINTER(tm_start.tm_mday));
511     g_signal_connect((gpointer)ev, "button-press-event"
512             , G_CALLBACK(on_button_press_event_cb), mw);
513     g_free(tip);
514     g_free(text);
515
516     /* and finally draw the line to show how long the appointment is,
517      * but only if it is Busy type event (=availability != 0) 
518      * and it is not whole day event */
519     if (TRUE) {
520         width = mw->StartDate_button_req.width;
521         /*
522          * same_date = !strncmp(start_ical_time, end_ical_time, 8);
523          * */
524         if (start_row < 0)
525             first_row = 0;
526         else
527             first_row = start_row;
528         if (end_row > 5)
529             last_row = 5;
530         else
531             last_row = end_row;
532         for (row = first_row; row <= last_row; row++) {
533             if (row == start_row)
534                 first_col = start_col;
535             else
536                 first_col = 0;
537             if (row == end_row)
538                 last_col   = end_col;
539             else
540                 last_col   = 7;
541             for (col = first_col; col <= last_col; col++) {
542                 if (row == start_row && col == start_col && !start_prev_mon)
543                     start_width = ((tm_start.tm_hour*60)+(tm_start.tm_min))*width/(24*60);
544                 else
545                     start_width = 0;
546                 if (row == last_row && col == last_col)
547                     end_width = ((tm_end.tm_hour*60)+(tm_end.tm_min))*width/(24*60);
548                 else
549                     end_width = width;
550
551                 mw->line[row][col] = build_line(start_width, 0
552                         , end_width-start_width, 2, mw->line[row][col]
553                         , &mw->line_color);
554             }
555         }
556     }
557 }
558
559 static void app_rows(month_win *mw, FolderItem *item)
560 {
561    GSList *events = vcal_get_events_list(item);
562    GSList *cur = NULL;
563    int days = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mw->day_spin));
564    for (cur = events; cur ; cur = cur->next) {
565         VCalEvent *event = (VCalEvent *) (cur->data);
566         add_row(mw, event, days);
567         vcal_manager_free_event(event);
568    }
569    g_slist_free(events);
570 }
571
572 static void app_data(month_win *mw, FolderItem *item)
573 {
574     app_rows(mw, item);
575 }
576
577
578 static void fill_days(month_win *mw, gint days, FolderItem *item)
579 {
580     gint day, col, height, width;
581     GtkWidget *ev, *vb, *hb;
582     guint monthdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
583     struct tm t = mw->startdate;
584     int weekoffset = -1;
585     time_t now = time(NULL);
586     struct tm tm_today;
587 #if !(GTK_CHECK_VERSION(2,12,0))
588         GtkTooltips *tips = mw->Tooltips;
589 #endif
590
591     
592     localtime_r(&now, &tm_today);
593
594     t.tm_year += 1900;
595     t.tm_mon++;
596     tm_today.tm_year += 1900;
597     tm_today.tm_mon++;
598     
599     if (((t.tm_year%4) == 0) 
600     && (((t.tm_year%100) != 0) || ((t.tm_year%400) == 0)))
601         ++monthdays[1]; /* leap year, february has 29 days */
602
603     height = mw->StartDate_button_req.height;
604     width = mw->StartDate_button_req.width;
605
606     /* first clear the structure */
607     for (col = 1; col <  days+1; col++) {
608         mw->header[col] = NULL;
609     }
610     for (day = 0; day < MAX_DAYS; day++)
611         for (col = 0; col < 6; col++)
612                 mw->line[col][day] = NULL;
613     for (day = 1; day <= monthdays[t.tm_mon-1]; day++) {
614         GDate *date = g_date_new_dmy(day, t.tm_mon, t.tm_year);
615         int dcol = (int)g_date_get_weekday(date);
616         int row = (int)g_date_get_monday_week_of_year(date);
617         if (weekoffset == -1) {
618                 weekoffset = row;
619                 row = 0;
620         } else {
621                 row = row - weekoffset;
622         }
623         mw->element[row][dcol] = NULL;
624         mw->line[row][dcol] = build_line(0, 0, width, 3, NULL
625                         , &mw->line_color);
626         g_date_free(date);
627     }
628
629     app_data(mw, item);
630
631     /* check rows */
632     for (day = 1; day <= monthdays[t.tm_mon-1]; day++) {
633         GDate *date = g_date_new_dmy(day, t.tm_mon, t.tm_year);
634         int col = (int)g_date_get_weekday(date);
635         int row = (int)g_date_get_monday_week_of_year(date);
636         gchar *label;
637         gchar *tmp;
638         GtkWidget *name;
639
640         if (weekoffset == -1) {
641                 weekoffset = row;
642                 row = 0;
643         } else {
644                 row = row - weekoffset;
645         }
646         vb = gtk_vbox_new(FALSE, 0);
647         gtk_widget_set_size_request(vb, width, height);
648             if (g_date_get_day(date) == 1)
649                 label = g_strdup_printf("%d %s", g_date_get_day(date),
650                                 _(monthname[g_date_get_month(date)-1]));
651             else
652                 label = g_strdup_printf("%d", g_date_get_day(date));
653             tmp = g_strdup_printf("%s %d %s %d",
654                                 _(dayname[col-1]),
655                                 g_date_get_day(date),
656                                 _(monthname[g_date_get_month(date)-1]),
657                                 g_date_get_year(date));
658
659             ev = gtk_event_box_new();
660             g_object_set_data(G_OBJECT(ev), "day", GINT_TO_POINTER((int)g_date_get_day(date)));
661             g_signal_connect((gpointer)ev, "button-press-event"
662                     , G_CALLBACK(header_button_clicked_cb), mw);
663             name = gtk_label_new(label);
664             gtk_misc_set_alignment(GTK_MISC(name), 0.0, 0.0);
665
666             CLAWS_SET_TIP(ev, tmp);
667             gtk_container_add(GTK_CONTAINER(ev), name);
668             g_free(tmp);
669             g_free(label);
670
671             if ((row % 2) == 1)
672                 gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &mw->bg1);
673             else
674                 gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &mw->bg2);
675             if (col == 7)
676                 gtk_widget_modify_fg(name, GTK_STATE_NORMAL, &mw->fg_sunday);
677             if (day == tm_today.tm_mday && t.tm_mon == tm_today.tm_mon && t.tm_year == tm_today.tm_year)
678                 gtk_widget_modify_bg(ev, GTK_STATE_NORMAL, &mw->bg_today);
679
680             hb = gtk_hbox_new(FALSE, 0);
681             gtk_box_pack_start(GTK_BOX(hb), ev, TRUE, TRUE, 1);
682             gtk_box_pack_start(GTK_BOX(vb), hb, TRUE, TRUE, 0);
683             if (mw->element[row][col]) {
684                 gtk_box_pack_start(GTK_BOX(vb), mw->element[row][col], TRUE, TRUE, 0);
685             }
686             gtk_box_pack_start(GTK_BOX(vb), mw->line[row][col]
687                     , FALSE, FALSE, 0);
688
689         gtk_table_attach(GTK_TABLE(mw->dtable), vb, col, col+1, row, row+1
690                  , (GTK_FILL), (0), 0, 0);
691         g_date_free(date);
692     }
693 }
694
695 static void build_month_view_header(month_win *mw, char *start_date)
696 {
697     GtkWidget *hbox, *label, *space_label;
698
699     hbox = gtk_hbox_new(FALSE, 0);
700
701     label = gtk_label_new(_("Start"));
702     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
703
704     /* start date button */
705     mw->StartDate_button = gtk_button_new();
706     gtk_box_pack_start(GTK_BOX(hbox), mw->StartDate_button, FALSE, FALSE, 0);
707
708     space_label = gtk_label_new("  ");
709     gtk_box_pack_start(GTK_BOX(hbox), space_label, FALSE, FALSE, 0);
710
711     space_label = gtk_label_new("     ");
712     gtk_box_pack_start(GTK_BOX(hbox), space_label, FALSE, FALSE, 0);
713
714     label = gtk_label_new(_("Show"));
715     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
716
717     /* show days spin = how many days to show */
718     mw->day_spin = gtk_spin_button_new_with_range(1, MAX_DAYS, 1);
719     gtk_spin_button_set_wrap(GTK_SPIN_BUTTON(mw->day_spin), TRUE);
720     gtk_widget_set_size_request(mw->day_spin, 40, -1);
721     gtk_box_pack_start(GTK_BOX(hbox), mw->day_spin, FALSE, FALSE, 0);
722     label = gtk_label_new(_("days"));
723     gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
724
725     space_label = gtk_label_new("   ");
726     gtk_box_pack_start(GTK_BOX(hbox), space_label, FALSE, FALSE, 0);
727
728     /* sizes */
729     gtk_button_set_label(GTK_BUTTON(mw->StartDate_button)
730             , (const gchar *)start_date);
731     gtk_widget_size_request(mw->StartDate_button, &mw->StartDate_button_req);
732     mw->StartDate_button_req.width += mw->StartDate_button_req.width/10;
733     label = gtk_label_new("00");
734     gtk_widget_size_request(label, &mw->hour_req);
735 }
736
737 static void build_month_view_colours(month_win *mw)
738 {
739     GtkStyle *def_style;
740     GdkColormap *pic1_cmap;
741     GtkWidget *ctree = NULL;
742     def_style = gtk_widget_get_default_style();
743     pic1_cmap = gdk_colormap_get_system();
744     
745     if (mainwindow_get_mainwindow()) {
746         ctree = mainwindow_get_mainwindow()->summaryview->ctree;
747     }
748     if (ctree) {
749         mw->bg1 = ctree->style->bg[GTK_STATE_NORMAL];
750         mw->bg2 = ctree->style->bg[GTK_STATE_NORMAL];
751     } else {
752         mw->bg1 = def_style->bg[GTK_STATE_NORMAL];
753         mw->bg2 = def_style->bg[GTK_STATE_NORMAL];
754     }
755
756     mw->bg1.red +=  (mw->bg1.red < 63000 ? 2000 : -2000);
757     mw->bg1.green += (mw->bg1.green < 63000 ? 2000 : -2000);
758     mw->bg1.blue += (mw->bg1.blue < 63000 ? 2000 : -2000);
759     gdk_colormap_alloc_color(pic1_cmap, &mw->bg1, FALSE, TRUE);
760
761     mw->bg2.red +=  (mw->bg2.red > 1000 ? -1000 : 1000);
762     mw->bg2.green += (mw->bg2.green > 1000 ? -1000 : 1000);
763     mw->bg2.blue += (mw->bg2.blue > 1000 ? -1000 : 1000);
764     gdk_colormap_alloc_color(pic1_cmap, &mw->bg2, FALSE, TRUE);
765
766     if (!gdk_color_parse("white", &mw->line_color)) {
767         mw->line_color.red =  239 * (65535/255);
768         mw->line_color.green = 235 * (65535/255);
769         mw->line_color.blue = 230 * (65535/255);
770     }
771
772     if (!gdk_color_parse("blue", &mw->fg_sunday)) {
773         g_warning("color parse failed: red\n");
774         mw->fg_sunday.red = 10 * (65535/255);
775         mw->fg_sunday.green = 10 * (65535/255);
776         mw->fg_sunday.blue = 255 * (65535/255);
777     }
778
779     if (!gdk_color_parse("gold", &mw->bg_today)) {
780         g_warning("color parse failed: gold\n");
781         mw->bg_today.red = 255 * (65535/255);
782         mw->bg_today.green = 215 * (65535/255);
783         mw->bg_today.blue = 115 * (65535/255);
784     }
785
786     if (ctree) {
787         mw->fg_sunday.red = (mw->fg_sunday.red + ctree->style->fg[GTK_STATE_SELECTED].red)/2;
788         mw->fg_sunday.green = (mw->fg_sunday.green + ctree->style->fg[GTK_STATE_SELECTED].red)/2;
789         mw->fg_sunday.blue = (3*mw->fg_sunday.blue + ctree->style->fg[GTK_STATE_SELECTED].red)/4;
790         mw->bg_today.red = (3*mw->bg_today.red + ctree->style->bg[GTK_STATE_NORMAL].red)/4;
791         mw->bg_today.green = (3*mw->bg_today.green + ctree->style->bg[GTK_STATE_NORMAL].red)/4;
792         mw->bg_today.blue = (3*mw->bg_today.blue + ctree->style->bg[GTK_STATE_NORMAL].red)/4;
793     }
794     gdk_colormap_alloc_color(pic1_cmap, &mw->line_color, FALSE, TRUE);
795     gdk_colormap_alloc_color(pic1_cmap, &mw->fg_sunday, FALSE, TRUE);
796     gdk_colormap_alloc_color(pic1_cmap, &mw->bg_today, FALSE, TRUE);
797 }
798
799 static void fill_hour(month_win *mw, gint col, gint row, char *text)
800 {
801     GtkWidget *name, *ev;
802 #if !(GTK_CHECK_VERSION(2,12,0))
803         GtkTooltips *tips = mw->Tooltips;
804 #endif
805
806     ev = gtk_event_box_new();
807     name = gtk_label_new(text);
808     gtk_misc_set_alignment(GTK_MISC(name), 0, 0.5);
809     CLAWS_SET_TIP(ev, _("Week number"));
810     gtk_container_add(GTK_CONTAINER(ev), name);
811     gtk_widget_set_size_request(ev, mw->hour_req.width
812             , mw->StartDate_button_req.height);
813     if (text)
814         gtk_table_attach(GTK_TABLE(mw->dtable), ev, col, col+1, row, row+1
815              , (GTK_FILL), (0), 0, 0);
816     else  /* special, needed for header table full day events */
817         gtk_table_attach(GTK_TABLE(mw->dtable_h), ev, col, col+1, row, row+1
818              , (GTK_FILL), (0), 0, 0);
819 }
820
821 static void build_month_view_table(month_win *mw)
822 {
823     gint days;   /* number of days to show */
824     int year, month, day;
825     gint i = 0;
826     GtkWidget *button;
827     struct tm tm_date, tm_today;
828     guint monthdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
829     GtkWidget *vp;
830     time_t t = time(NULL);
831     GtkWidget *arrow;
832     int avail_w = 0, avail_d = 0;
833     int avail_h = 0;
834     int weekoffset = -1;
835     GDate *date;
836     int first_week=0;
837 #if !(GTK_CHECK_VERSION(2,12,0))
838         GtkTooltips *tips = mw->Tooltips;
839 #endif
840
841     if (mainwindow_get_mainwindow()) {
842         GtkAllocation allocation;
843         SummaryView *summaryview = mainwindow_get_mainwindow()->summaryview;
844         GTK_EVENTS_FLUSH();
845         allocation = summaryview->mainwidget_book->allocation;
846         
847         avail_w = allocation.width - 25 - 2*(mw->hour_req.width);
848         avail_h = allocation.height - 20;
849         if (avail_h < 250)
850                 avail_h = 250;
851         avail_d = avail_w / mw->StartDate_button_req.width;
852     }
853     avail_d = 7;
854     gtk_widget_set_size_request(mw->StartDate_button, avail_w / avail_d, 
855                         (avail_h)/6);
856     gtk_widget_size_request(mw->StartDate_button, &mw->StartDate_button_req);
857    
858     /* initial values */
859     if (avail_d)
860         gtk_spin_button_set_value(GTK_SPIN_BUTTON(mw->day_spin), avail_d);
861
862 #ifdef G_OS_WIN32
863         if (t < 0)
864                 t = 1;
865 #endif
866     localtime_r(&t, &tm_today);
867     days = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(mw->day_spin));
868     /****** header of day table = days columns ******/
869     mw->scroll_win_h = gtk_scrolled_window_new(NULL, NULL);
870     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mw->scroll_win_h)
871             , GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
872     gtk_box_pack_start(GTK_BOX(mw->Vbox), mw->scroll_win_h
873             , TRUE, TRUE, 0);
874     mw->month_view_vbox = gtk_vbox_new(FALSE, 0);
875     gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(mw->scroll_win_h)
876             , mw->month_view_vbox);
877     /*
878     gtk_container_add(GTK_CONTAINER(mw->scroll_win_h), mw->month_view_vbox);
879     */
880     /* row 1= day header buttons 
881      * row 2= full day events after the buttons */
882     mw->dtable_h = gtk_table_new(2 , days+2, FALSE);
883     gtk_box_pack_start(GTK_BOX(mw->month_view_vbox), mw->dtable_h
884             , FALSE, FALSE, 0);
885
886     tm_date = mw->startdate;
887
888     year = tm_date.tm_year + 1900;
889     month = tm_date.tm_mon;
890     day = tm_date.tm_mday;
891     if (((tm_date.tm_year%4) == 0) && (((tm_date.tm_year%100) != 0) 
892             || ((tm_date.tm_year%400) == 0)))
893         ++monthdays[1];
894
895
896     i=0;
897     mw->Previous_toolbutton = gtk_event_box_new();
898     gtk_event_box_set_visible_window(GTK_EVENT_BOX(mw->Previous_toolbutton), FALSE);
899     gtk_container_set_border_width(GTK_CONTAINER(mw->Previous_toolbutton), 0);
900     arrow = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_NONE);
901     gtk_container_add(GTK_CONTAINER(mw->Previous_toolbutton), arrow);
902     gtk_table_attach(GTK_TABLE(mw->dtable_h), mw->Previous_toolbutton, i, i+1, 0, 1
903                 , (GTK_FILL), (0), 0, 0);
904     gtk_widget_show_all(mw->Previous_toolbutton);
905     g_signal_connect((gpointer)mw->Previous_toolbutton, "button_release_event"
906             , G_CALLBACK(on_Previous_clicked), mw);
907     CLAWS_SET_TIP(mw->Previous_toolbutton, _("Previous month"));
908     for (i = 1; i < days+1; i++) {
909         button = gtk_label_new(_(dayname[i-1]));
910
911         if (i == 8) {
912             gtk_widget_modify_fg(button, GTK_STATE_NORMAL, &mw->fg_sunday);
913         }
914         gtk_widget_set_size_request(button, mw->StartDate_button_req.width, -1);
915         g_object_set_data(G_OBJECT(button), "offset", GINT_TO_POINTER(i-1));
916         gtk_table_attach(GTK_TABLE(mw->dtable_h), button, i, i+1, 0, 1
917                 , (GTK_FILL), (0), 0, 0);
918     }
919
920     mw->Next_toolbutton = gtk_event_box_new();
921     gtk_event_box_set_visible_window(GTK_EVENT_BOX(mw->Next_toolbutton), FALSE);
922     gtk_container_set_border_width(GTK_CONTAINER(mw->Next_toolbutton), 0);
923     arrow = gtk_arrow_new(GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
924     gtk_container_add(GTK_CONTAINER(mw->Next_toolbutton), arrow);
925     gtk_table_attach(GTK_TABLE(mw->dtable_h), mw->Next_toolbutton, i, i+1, 0, 1
926                 , (GTK_FILL), (0), 0, 0);
927     gtk_widget_show_all(mw->Next_toolbutton);
928     g_signal_connect((gpointer)mw->Next_toolbutton, "button_release_event"
929             , G_CALLBACK(on_Next_clicked), mw);
930     CLAWS_SET_TIP(mw->Next_toolbutton, _("Next month"));
931
932     /****** body of day table ******/
933     mw->scroll_win = gtk_scrolled_window_new(NULL, NULL);
934     gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(mw->scroll_win)
935             , GTK_SHADOW_NONE);
936     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(mw->scroll_win)
937             , GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
938     gtk_scrolled_window_set_placement(GTK_SCROLLED_WINDOW(mw->scroll_win)
939             , GTK_CORNER_TOP_LEFT);
940     gtk_box_pack_start(GTK_BOX(mw->month_view_vbox), mw->scroll_win
941             , TRUE, TRUE, 0);
942     vp = gtk_viewport_new(NULL, NULL);
943     gtk_viewport_set_shadow_type(GTK_VIEWPORT(vp), GTK_SHADOW_IN);
944     gtk_container_add(GTK_CONTAINER(mw->scroll_win), vp);
945     mw->dtable = gtk_table_new(6, days+2, FALSE);
946     gtk_container_add(GTK_CONTAINER(vp), mw->dtable);
947
948     gtk_widget_show_all(mw->dtable_h);
949
950     date = g_date_new_dmy(1, 1, tm_date.tm_year+1900);
951     first_week = g_date_get_monday_week_of_year(date);
952
953     if (first_week == 0)
954         first_week = 1;
955     else
956         first_week = 0;
957     g_date_free(date);
958
959     /* hours column = hour rows */
960     for (i = 0; i <= 6; i++) {
961         for (day = 1; day <= monthdays[tm_date.tm_mon]; day++) {
962             date = g_date_new_dmy(day, tm_date.tm_mon+1, tm_date.tm_year+1900);
963             int row = (int)g_date_get_monday_week_of_year(date);
964             if (weekoffset == -1) {
965                 weekoffset = row;
966             }
967             if (row - weekoffset == i) {
968                 gchar *wn = g_strdup_printf("%d", row+first_week > 53?1:row+first_week);
969                 fill_hour(mw, 0, i, wn);
970                 fill_hour(mw, days+1, i, "");
971                 g_free(wn);
972                 g_date_free(date);
973                 break;
974             }
975             g_date_free(date);
976         }
977     }
978     fill_days(mw, days, mw->item);
979 }
980
981 void refresh_month_win(month_win *mw)
982 {
983     gtk_widget_destroy(mw->scroll_win_h);
984     build_month_view_table(mw);
985     gtk_widget_show_all(mw->scroll_win_h);
986 }
987
988 month_win *create_month_win(FolderItem *item, struct tm tmdate)
989 {
990     month_win *mw;
991     char *start_date = get_locale_date(&tmdate);
992
993     /* initialisation + main window + base vbox */
994     mw = g_new0(month_win, 1);
995     mw->scroll_pos = -1; /* not set */
996 #if !(GTK_CHECK_VERSION(2,12,0))
997     mw->Tooltips = tips;
998 #endif
999     mw->accel_group = gtk_accel_group_new();
1000
1001     while (tmdate.tm_mday != 1)
1002         orage_move_day(&tmdate, -1);
1003
1004     mw->startdate = tmdate;
1005
1006     mw->Vbox = gtk_vbox_new(FALSE, 0);
1007
1008     mw->item = item;
1009     build_month_view_colours(mw);
1010     build_month_view_header(mw, start_date);
1011     build_month_view_table(mw);
1012     gtk_widget_show_all(mw->Vbox);
1013     mw->selsig = vcal_view_set_calendar_page(mw->Vbox, 
1014                     G_CALLBACK(mw_summary_selected), mw);
1015
1016     vcal_view_create_popup_menus(mw->Vbox, &mw->view_menu,
1017                                  &mw->event_menu, &mw->event_group,
1018                                  &mw->ui_manager);
1019     return(mw);
1020 }