/* * Copyright (C) 2001, 2002, 2004 Philip Blundell * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "event-ui.h" #include "globals.h" #include "day_view.h" #include "gtkdatesel.h" #define _(x) gettext(x) enum { COL_HOUR = 0, COL_MIN, COL_DIV, COL_ICON, COL_DETAIL, COL_BACK, COL_EVENT, NUM_COLS }; static GSList *strings; static GtkListStore *liststore; static GSList *day_events[24], *untimed_events; static GtkWidget *day_list; static GtkWidget *datesel, *calendar; static GtkAdjustment *scroll_adjustment; void delete_event (event_t ev) { event_t ev_real; event_details_t ev_d; recur_t r; ev_real = (event_t)ev->cloned_ev; ev_d = event_db_get_details (ev_real); if (ev_real->recur) { if (gpe_question_ask( _("Delete all recurring entries?\n(If no, delete this instance only)"), _("Question"), "question", "!gtk-no", NULL, "!gtk-yes", NULL, NULL)) { if (ev_real->flags & FLAG_ALARM) { schedule_cancel_alarm (ev_real->uid, ev->start); schedule_next (0,0); } event_db_remove (ev_real); } else { r = event_db_get_recurrence (ev_real); r->exceptions = g_slist_append (r->exceptions, (void *)ev->start); event_db_flush (ev_real); } } else { if (ev_real->flags & FLAG_ALARM) { schedule_cancel_alarm (ev_real->uid, ev->start); schedule_next (0,0); } event_db_remove (ev_real); } update_all_views (); event_db_forget_details (ev_real); } static gboolean list_key_press_event (GtkWidget *treeview, GdkEventKey *k, gpointer user_data) { event_t ev; GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); GtkTreeModel *model; GtkTreeIter iter; GtkWidget *w; if (k->keyval == GDK_Left) { gtk_date_sel_move_day (GTK_DATE_SEL(datesel), -1); return TRUE; } if (k->keyval == GDK_Right) { gtk_date_sel_move_day (GTK_DATE_SEL(datesel), 1); return TRUE; } if (!selection) return FALSE; if (!gtk_tree_selection_get_selected(selection, &model, &iter)) return FALSE; gtk_tree_model_get(model, &iter, COL_EVENT, &ev, -1); if (k->keyval == GDK_Return) { struct tm tm; if (ev) { w = edit_event(ev); } else { char *hour; char *min; localtime_r (&viewtime, &tm); gtk_tree_model_get(model, &iter, COL_HOUR, &hour, -1); strptime (hour, "%H:", &tm); gtk_tree_model_get(model, &iter, COL_HOUR, &min, -1); strptime (min, "%M", &tm); w = new_event (mktime (&tm), 1); } gtk_widget_show (w); return TRUE; } if (k->keyval == GDK_Delete) { if ((ev) && (gpe_question_ask (_("Delete event?"), _("Question"), "question", "!gtk-no", NULL, "!gtk-yes", NULL, NULL))) delete_event(ev); return TRUE; } return FALSE; } static char * format_event (event_t ev) { char buf[256]; char *p = buf; size_t l = sizeof (buf); size_t r; struct tm tm; time_t t; event_details_t evd; if ((ev->flags & FLAG_UNTIMED) == 0 && day_view_combined_times == FALSE) { localtime_r (&ev->start, &tm); r = strftime (p, l, "%R", &tm); if (r == 0) return NULL; p += r; l -= r; if (ev->duration) { t = ev->start + ev->duration; localtime_r (&t, &tm); r = strftime (p, l, "-%R", &tm); if (r == 0) return NULL; p += r; l -= r; } if (l) { *p++ = ' '; l--; } } evd = event_db_get_details (ev); if (evd == NULL) return NULL; strncpy (p, evd->summary, l - 1); p[l - 1] = 0; return g_strdup (buf); } static gboolean selection_made (GtkWidget *treeview, GdkEventButton *event, gpointer userdata) { event_t ev; GtkWidget *w; GtkTreeViewColumn *treecolumn; GtkTreePath *path; GtkTreeModel *model; GtkTreeIter iter; GList *cols; gint column; if (!event) return FALSE; if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), event->x, event->y, &path, &treecolumn, NULL, NULL)) return FALSE; cols = gtk_tree_view_get_columns(GTK_TREE_VIEW(treeview)); column = g_list_index(cols, (gpointer) treecolumn); g_list_free(cols); model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview)); if (!gtk_tree_model_get_iter(model, &iter, path)) return FALSE; gtk_tree_model_get(model, &iter, COL_EVENT, &ev, -1); if ((event->type == GDK_2BUTTON_PRESS) || ((event->type == GDK_BUTTON_RELEASE) && (column == COL_DETAIL))) { struct tm tm; if (ev) { w = edit_event (ev); } else { char *hour; char *min; localtime_r (&viewtime, &tm); gtk_tree_model_get(model, &iter, COL_HOUR, &hour, -1); strptime (hour, "%H:", &tm); gtk_tree_model_get(model, &iter, COL_HOUR, &min, -1); strptime (min, "%M", &tm); w = new_event (mktime (&tm), 1); } gtk_widget_show (w); } else if (event->type == GDK_BUTTON_RELEASE) { // set the default event time to match the selected row struct tm tm; char *hour; char *min; gtk_tree_model_get(model, &iter, COL_HOUR, &hour, -1); localtime_r (&viewtime, &tm); strptime (hour, "%H:", &tm); gtk_tree_model_get(model, &iter, COL_HOUR, &min, -1); strptime (min, "%M", &tm); viewtime = mktime (&tm); } return FALSE; } static GtkWidget * create_day_view (void) { GtkTreeViewColumn *col; GtkCellRenderer *renderer; GtkTreeModel *model; GtkWidget *view; view = gtk_tree_view_new (); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW (view), FALSE); /* Hour column */ renderer = gtk_cell_renderer_text_new (); g_object_set(renderer, "font", "Serif 12", "xpad", 0, "width", 24, NULL); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "", renderer, "text", COL_HOUR, NULL); /* Minute column */ renderer = gtk_cell_renderer_text_new (); g_object_set(renderer, "font", "Serif", "xpad", 0, NULL); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "", renderer, "text", COL_MIN, NULL); /* Divider column */ renderer = gtk_cell_renderer_text_new (); g_object_set(renderer, "cell-background", "black", NULL); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "", renderer, NULL); /* Force divider column to single pixel */ col = gtk_tree_view_get_column(GTK_TREE_VIEW (view), COL_DIV); g_object_set(col, "sizing", GTK_TREE_VIEW_COLUMN_FIXED, "min-width", 1, NULL); /* Pixmap */ renderer = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "", renderer, "pixbuf", COL_ICON, NULL); /* Details */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, "", renderer, "text", COL_DETAIL, "cell-background", COL_BACK, NULL); col = gtk_tree_view_get_column(GTK_TREE_VIEW (view), COL_DETAIL); gtk_tree_view_column_set_expand(col, TRUE); /* Background colour */ renderer = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW (view), -1, "", renderer, NULL); col = gtk_tree_view_get_column(GTK_TREE_VIEW (view), COL_BACK); g_object_set(col, "sizing", GTK_TREE_VIEW_COLUMN_FIXED, "max-width", 0, NULL); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW (view), -1, "", renderer, NULL); col = gtk_tree_view_get_column(GTK_TREE_VIEW (view), COL_EVENT); g_object_set(col, "sizing", GTK_TREE_VIEW_COLUMN_FIXED, "max-width", 0, NULL); liststore = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT); model = GTK_TREE_MODEL (liststore); gtk_tree_view_set_model (GTK_TREE_VIEW (view), model); g_object_unref (model); /* destroy model automatically with view */ return view; } static gint day_view_update () { guint hour; time_t start, end; struct tm tm_start, tm_end; char hour_buf[4]; char min_buf[4]; gchar *line_info[2]; GSList *iter; double pos; gchar *last_text = NULL; GtkTreeIter treeiter; gtk_list_store_clear(liststore); if (strings) { for (iter = strings; iter; iter = iter->next) g_free (iter->data); g_slist_free (strings); strings = NULL; } localtime_r (&viewtime, &tm_start); pos = 24 / (tm_start.tm_hour + 1); gtk_adjustment_set_value (scroll_adjustment, pos); tm_start.tm_hour = 0; tm_start.tm_min = 0; tm_start.tm_sec = 0; start = mktime (&tm_start); localtime_r (&viewtime, &tm_end); tm_end.tm_hour = 23; tm_end.tm_min = 59; tm_end.tm_sec = 0; end = mktime (&tm_end); if (untimed_events) event_db_list_destroy (untimed_events); untimed_events = event_db_untimed_list_for_period (start, end, TRUE); for (iter = untimed_events; iter; iter = iter->next) { event_t ev = (event_t)iter->data; gchar *text = format_event (ev); g_slist_append (strings, text); gtk_list_store_append (liststore, &treeiter); gtk_list_store_set (liststore, &treeiter, COL_HOUR, "", COL_MIN, "", COL_DIV, "", COL_ICON, NULL, COL_DETAIL, text, COL_BACK, "back", COL_EVENT, ev, -1); } for (hour = 0; hour <= 23; hour++) { localtime_r (&viewtime, &tm_start); tm_start.tm_hour = hour; tm_start.tm_min = 0; tm_start.tm_sec = 0; start = mktime (&tm_start); localtime_r (&viewtime, &tm_end); tm_end.tm_hour = hour+1; tm_end.tm_min = 0; tm_end.tm_sec = 0; end = mktime (&tm_end); if (day_events[hour]) event_db_list_destroy (day_events[hour]); day_events[hour] = event_db_untimed_list_for_period (start, end-1, FALSE); for (iter = day_events[hour]; iter; iter = iter->next) ((event_t)iter->data)->mark = FALSE; } for (hour = 0; hour <= 23; hour++) { gchar *text = NULL; event_t ev = NULL; tm_start.tm_hour = hour; tm_start.tm_min = 0; tm_start.tm_sec = 0; strftime (hour_buf, sizeof (hour_buf), "%H:", &tm_start); strftime (min_buf, sizeof (min_buf), "%M", &tm_start); for (iter = day_events[hour]; iter; iter = iter->next) { ev = (event_t) iter->data; if (ev->mark == FALSE) { struct tm tm; ev->mark = TRUE; text = format_event (ev); g_slist_append (strings, text); if (day_view_combined_times) { localtime_r (&ev->start, &tm); strftime (hour_buf, sizeof (hour_buf), "%H:", &tm); strftime (min_buf, sizeof (min_buf), "%M", &tm); } break; } } if (!last_text || !text || (strcmp(last_text,text))) line_info[1] = text; else line_info[1] = NULL; line_info[0] = hour_buf; if (last_text) g_free(last_text); last_text = g_strdup(text); /* don't display very early/late slots if they are not used */ if (ev || ((hour > 5) && (hour < 23))) { GdkPixbuf *pbuf = NULL; if (ev) { if ((ev->flags & FLAG_ALARM) && ev->recur) pbuf = gpe_find_icon ("bell_recur"); else if (ev->flags & FLAG_ALARM) pbuf = gpe_find_icon ("bell"); else if (ev->recur) pbuf = gpe_find_icon ("recur"); } gtk_list_store_append (liststore, &treeiter); gtk_list_store_set (liststore, &treeiter, COL_HOUR, hour_buf, COL_MIN, min_buf, COL_DIV, "", COL_ICON, pbuf, COL_DETAIL, line_info[1], COL_BACK, day_events[hour] ? "white" : "palegoldenrod", COL_EVENT, ev, -1); } for (; iter; iter = iter->next) { ev = (event_t) iter->data; if (ev->mark) continue; ev->mark = TRUE; if (day_view_combined_times) { struct tm tm; localtime_r (&ev->start, &tm); strftime (hour_buf, sizeof (hour_buf), "%H:", &tm); strftime (min_buf, sizeof (min_buf), "%M", &tm); line_info[0] = hour_buf; } else { line_info[0] = NULL; } line_info[1] = format_event (ev); gtk_list_store_append (liststore, &treeiter); gtk_list_store_set (liststore, &treeiter, COL_HOUR, line_info[0], COL_MIN, line_info[0] ? min_buf : NULL, COL_DIV, "", COL_ICON, NULL, COL_DETAIL, line_info[1], COL_BACK, "white", COL_EVENT, ev, -1); g_slist_append (strings, line_info[1]); } } return TRUE; } static void day_changed_calendar (GtkWidget *widget) { int day, month, year; struct tm tm; localtime_r (&viewtime, &tm); gtk_calendar_get_date (GTK_CALENDAR (widget), &year, &month, &day); if (tm.tm_year != (year - 1900) || tm.tm_mon != month || tm.tm_mday != day) { tm.tm_year = year - 1900; tm.tm_mon = month; tm.tm_mday = day; viewtime = mktime (&tm); set_time_all_views (); day_view_update (); } } static void date_sel_changed (GtkWidget *widget, GtkWidget *list) { struct tm tm; viewtime = gtk_date_sel_get_time (GTK_DATE_SEL (widget)); localtime_r (&viewtime, &tm); gtk_calendar_select_month (GTK_CALENDAR (calendar), tm.tm_mon, tm.tm_year + 1900); gtk_calendar_select_day (GTK_CALENDAR (calendar), tm.tm_mday); day_view_update (); } static void update_hook_callback() { struct tm tm; localtime_r (&viewtime, &tm); gtk_date_sel_set_time (GTK_DATE_SEL (datesel), viewtime); gtk_calendar_select_month (GTK_CALENDAR (calendar), tm.tm_mon, tm.tm_year + 1900); gtk_calendar_select_day (GTK_CALENDAR (calendar), tm.tm_mday); day_view_update (); } void day_free_lists() { int hour; if (untimed_events) event_db_list_destroy (untimed_events); untimed_events = NULL; for (hour = 0; hour <= 23; hour++) if (day_events[hour]) { event_db_list_destroy (day_events[hour]); day_events[hour] = NULL; } } GtkWidget * day_view (void) { GtkWidget *vbox = gtk_vbox_new (FALSE, 0); GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL); GtkWidget *hbox = gtk_hbox_new (FALSE, 0); gboolean landscape; landscape = gdk_screen_width () > gdk_screen_height () ? TRUE : FALSE; calendar = gtk_calendar_new (); GTK_WIDGET_UNSET_FLAGS (calendar, GTK_CAN_FOCUS); gtk_widget_show (scrolled_window); gtk_widget_show (hbox); gtk_calendar_set_display_options (GTK_CALENDAR (calendar), GTK_CALENDAR_SHOW_DAY_NAMES | (week_starts_monday ? GTK_CALENDAR_WEEK_START_MONDAY : 0)); datesel = gtk_date_sel_new (GTKDATESEL_FULL); gtk_widget_show (datesel); day_list = create_day_view(); gtk_widget_show (day_list); g_signal_connect (G_OBJECT (calendar), "day-selected", G_CALLBACK (day_changed_calendar), NULL); g_signal_connect (G_OBJECT (day_list), "button-press-event", G_CALLBACK (selection_made), NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (scrolled_window), day_list); gtk_box_pack_start (GTK_BOX (vbox), datesel, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (hbox), scrolled_window, TRUE, TRUE, 0); if (landscape) { GtkWidget *sep; sep = gtk_vseparator_new (); gtk_box_pack_start (GTK_BOX (hbox), sep, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (hbox), calendar, FALSE, FALSE, 0); gtk_widget_show (sep); gtk_widget_show (calendar); } g_signal_connect (G_OBJECT (datesel), "changed", G_CALLBACK (date_sel_changed), day_list); g_object_set_data (G_OBJECT (vbox), "update_hook", (gpointer) update_hook_callback); scroll_adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)); g_signal_connect (G_OBJECT (day_list), "key_press_event", G_CALLBACK (list_key_press_event), NULL); g_object_set_data (G_OBJECT (main_window), "datesel-day", datesel); return vbox; }