Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / e-cal-list-view.c
blob3ca21de7c6fb98141c9aee588eaafe0f87bdaa0d
1 /*
2 * ECalListView - display calendar events in an ETable.
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 * Authors:
17 * Hans Petter Jansson <hpj@ximian.com>
19 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 #include "evolution-config.h"
24 #include "e-cal-list-view.h"
25 #include "ea-calendar.h"
27 #include <math.h>
28 #include <time.h>
29 #include <sys/stat.h>
30 #include <gtk/gtk.h>
31 #include <glib/gi18n.h>
32 #include <glib/gstdio.h>
33 #include <gdk/gdkkeysyms.h>
35 #include "e-cal-model-calendar.h"
36 #include "e-cell-date-edit-text.h"
37 #include "comp-util.h"
38 #include "itip-utils.h"
39 #include "calendar-config.h"
40 #include "misc.h"
42 enum {
43 PROP_0,
44 PROP_IS_EDITING
47 static void e_cal_list_view_dispose (GObject *object);
49 static GList *e_cal_list_view_get_selected_events (ECalendarView *cal_view);
50 static gboolean e_cal_list_view_get_selected_time_range (ECalendarView *cal_view, time_t *start_time, time_t *end_time);
51 static gboolean e_cal_list_view_get_visible_time_range (ECalendarView *cal_view, time_t *start_time,
52 time_t *end_time);
54 static gboolean e_cal_list_view_popup_menu (GtkWidget *widget);
56 static gboolean e_cal_list_view_on_table_double_click (GtkWidget *table, gint row, gint col,
57 GdkEvent *event, gpointer data);
58 static gboolean e_cal_list_view_on_table_right_click (GtkWidget *table, gint row, gint col,
59 GdkEvent *event, gpointer data);
60 static gboolean e_cal_list_view_on_table_white_space_event (ETable *table, GdkEvent *event, gpointer data);
61 static void e_cal_list_view_cursor_change_cb (ETable *etable, gint row, gpointer data);
63 G_DEFINE_TYPE (ECalListView, e_cal_list_view, E_TYPE_CALENDAR_VIEW)
65 static void
66 e_cal_list_view_get_property (GObject *object,
67 guint property_id,
68 GValue *value,
69 GParamSpec *pspec)
71 ECalListView *eclv = E_CAL_LIST_VIEW (object);
73 switch (property_id) {
74 case PROP_IS_EDITING:
75 g_value_set_boolean (value, e_cal_list_view_is_editing (eclv));
76 break;
77 default:
78 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
79 break;
83 static void
84 e_cal_list_view_class_init (ECalListViewClass *class)
86 GObjectClass *object_class;
87 GtkWidgetClass *widget_class;
88 ECalendarViewClass *view_class;
90 object_class = (GObjectClass *) class;
91 widget_class = (GtkWidgetClass *) class;
92 view_class = (ECalendarViewClass *) class;
94 /* Method override */
95 object_class->dispose = e_cal_list_view_dispose;
96 object_class->get_property = e_cal_list_view_get_property;
98 widget_class->popup_menu = e_cal_list_view_popup_menu;
100 view_class->get_selected_events = e_cal_list_view_get_selected_events;
101 view_class->get_selected_time_range = e_cal_list_view_get_selected_time_range;
102 view_class->get_visible_time_range = e_cal_list_view_get_visible_time_range;
104 g_object_class_override_property (
105 object_class,
106 PROP_IS_EDITING,
107 "is-editing");
110 static void
111 e_cal_list_view_init (ECalListView *cal_list_view)
113 cal_list_view->table = NULL;
114 cal_list_view->cursor_event = NULL;
115 cal_list_view->set_table_id = 0;
118 /* Returns the current time, for the ECellDateEdit items. */
119 static struct tm
120 get_current_time_cb (ECellDateEdit *ecde,
121 gpointer data)
123 ECalListView *cal_list_view = data;
124 icaltimezone *zone;
125 struct tm tmp_tm = { 0 };
126 struct icaltimetype tt;
128 zone = e_calendar_view_get_timezone (E_CALENDAR_VIEW (cal_list_view));
129 tt = icaltime_from_timet_with_zone (time (NULL), FALSE, zone);
131 /* Now copy it to the struct tm and return it. */
132 tmp_tm.tm_year = tt.year - 1900;
133 tmp_tm.tm_mon = tt.month - 1;
134 tmp_tm.tm_mday = tt.day;
135 tmp_tm.tm_hour = tt.hour;
136 tmp_tm.tm_min = tt.minute;
137 tmp_tm.tm_sec = tt.second;
138 tmp_tm.tm_isdst = -1;
140 return tmp_tm;
143 static void
144 e_cal_list_view_table_editing_changed_cb (ETable *table,
145 GParamSpec *param,
146 ECalListView *eclv)
148 g_return_if_fail (E_IS_CAL_LIST_VIEW (eclv));
150 g_object_notify (G_OBJECT (eclv), "is-editing");
153 static void
154 setup_e_table (ECalListView *cal_list_view)
156 ECalModel *model;
157 ETableExtras *extras;
158 ETableSpecification *specification;
159 GList *strings;
160 ECell *cell, *popup_cell;
161 GtkWidget *container;
162 GtkWidget *widget;
163 gchar *etspecfile;
164 GError *local_error = NULL;
166 model = e_calendar_view_get_model (E_CALENDAR_VIEW (cal_list_view));
168 /* Create the header columns */
170 extras = e_table_extras_new ();
172 /* Normal string fields */
174 cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
175 g_object_set (
176 cell,
177 "bg_color_column", E_CAL_MODEL_FIELD_COLOR,
178 NULL);
180 e_table_extras_add_cell (extras, "calstring", cell);
181 g_object_unref (cell);
183 /* Date fields */
185 cell = e_cell_date_edit_text_new (NULL, GTK_JUSTIFY_LEFT);
186 g_object_set (
187 cell,
188 "bg_color_column", E_CAL_MODEL_FIELD_COLOR,
189 NULL);
191 e_binding_bind_property (
192 model, "timezone",
193 cell, "timezone",
194 G_BINDING_BIDIRECTIONAL |
195 G_BINDING_SYNC_CREATE);
197 e_binding_bind_property (
198 model, "use-24-hour-format",
199 cell, "use-24-hour-format",
200 G_BINDING_BIDIRECTIONAL |
201 G_BINDING_SYNC_CREATE);
203 popup_cell = e_cell_date_edit_new ();
204 e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell);
205 g_object_unref (cell);
207 e_binding_bind_property (
208 model, "use-24-hour-format",
209 popup_cell, "use-24-hour-format",
210 G_BINDING_BIDIRECTIONAL |
211 G_BINDING_SYNC_CREATE);
213 e_table_extras_add_cell (extras, "dateedit", popup_cell);
214 g_object_unref (popup_cell);
215 cal_list_view->dates_cell = E_CELL_DATE_EDIT (popup_cell);
217 gtk_widget_hide (E_CELL_DATE_EDIT (popup_cell)->none_button);
219 e_cell_date_edit_set_get_time_callback (
220 E_CELL_DATE_EDIT (popup_cell),
221 get_current_time_cb,
222 cal_list_view, NULL);
224 /* Combo fields */
226 cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT);
227 g_object_set (
228 cell,
229 "bg_color_column", E_CAL_MODEL_FIELD_COLOR,
230 "editable", FALSE,
231 NULL);
233 popup_cell = e_cell_combo_new ();
234 e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell);
235 g_object_unref (cell);
237 strings = NULL;
238 strings = g_list_append (strings, (gchar *) _("Public"));
239 strings = g_list_append (strings, (gchar *) _("Private"));
240 strings = g_list_append (strings, (gchar *) _("Confidential"));
241 e_cell_combo_set_popdown_strings (
242 E_CELL_COMBO (popup_cell),
243 strings);
244 g_list_free (strings);
246 e_table_extras_add_cell (extras, "classification", popup_cell);
247 g_object_unref (popup_cell);
249 /* Sorting */
251 e_table_extras_add_compare (
252 extras, "date-compare",
253 e_cell_date_edit_compare_cb);
255 /* set proper format component for a default 'date' cell renderer */
256 cell = e_table_extras_get_cell (extras, "date");
257 e_cell_date_set_format_component (E_CELL_DATE (cell), "calendar");
259 /* Create table view */
261 container = GTK_WIDGET (cal_list_view);
263 widget = gtk_scrolled_window_new (NULL, NULL);
264 gtk_scrolled_window_set_policy (
265 GTK_SCROLLED_WINDOW (widget),
266 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
267 gtk_grid_attach (GTK_GRID (container), widget, 0, 0, 2, 2);
268 g_object_set (G_OBJECT (widget),
269 "hexpand", TRUE,
270 "vexpand", TRUE,
271 "halign", GTK_ALIGN_FILL,
272 "valign", GTK_ALIGN_FILL,
273 NULL);
274 gtk_widget_show (widget);
276 container = widget;
278 etspecfile = g_build_filename (
279 EVOLUTION_ETSPECDIR, "e-cal-list-view.etspec", NULL);
280 specification = e_table_specification_new (etspecfile, &local_error);
282 /* Failure here is fatal. */
283 if (local_error != NULL) {
284 g_error ("%s: %s", etspecfile, local_error->message);
285 g_return_if_reached ();
288 widget = e_table_new (E_TABLE_MODEL (model), extras, specification);
289 gtk_container_add (GTK_CONTAINER (container), widget);
290 cal_list_view->table = E_TABLE (widget);
291 gtk_widget_show (widget);
293 g_object_unref (specification);
294 g_object_unref (extras);
295 g_free (etspecfile);
297 /* Connect signals */
298 g_signal_connect (
299 cal_list_view->table, "double_click",
300 G_CALLBACK (e_cal_list_view_on_table_double_click),
301 cal_list_view);
302 g_signal_connect (
303 cal_list_view->table, "right-click",
304 G_CALLBACK (e_cal_list_view_on_table_right_click),
305 cal_list_view);
306 g_signal_connect (
307 cal_list_view->table, "white-space-event",
308 G_CALLBACK (e_cal_list_view_on_table_white_space_event),
309 cal_list_view);
310 g_signal_connect_after (
311 cal_list_view->table, "cursor_change",
312 G_CALLBACK (e_cal_list_view_cursor_change_cb),
313 cal_list_view);
314 e_signal_connect_notify_after (
315 cal_list_view->table, "notify::is-editing",
316 G_CALLBACK (e_cal_list_view_table_editing_changed_cb),
317 cal_list_view);
321 * e_cal_list_view_new:
322 * @Returns: a new #ECalListView.
324 * Creates a new #ECalListView.
326 ECalendarView *
327 e_cal_list_view_new (ECalModel *model)
329 ECalendarView *cal_list_view;
331 cal_list_view = g_object_new (
332 E_TYPE_CAL_LIST_VIEW, "model", model, NULL);
333 setup_e_table (E_CAL_LIST_VIEW (cal_list_view));
335 return cal_list_view;
338 static void
339 e_cal_list_view_dispose (GObject *object)
341 ECalListView *cal_list_view;
343 cal_list_view = E_CAL_LIST_VIEW (object);
345 if (cal_list_view->set_table_id) {
346 g_source_remove (cal_list_view->set_table_id);
347 cal_list_view->set_table_id = 0;
350 if (cal_list_view->cursor_event) {
351 g_free (cal_list_view->cursor_event);
352 cal_list_view->cursor_event = NULL;
355 if (cal_list_view->table) {
356 gtk_widget_destroy (GTK_WIDGET (cal_list_view->table));
357 cal_list_view->table = NULL;
360 /* Chain up to parent's dispose() method. */
361 G_OBJECT_CLASS (e_cal_list_view_parent_class)->dispose (object);
364 static void
365 e_cal_list_view_show_popup_menu (ECalListView *cal_list_view,
366 GdkEvent *event)
368 e_calendar_view_popup_event (E_CALENDAR_VIEW (cal_list_view), event);
371 static gboolean
372 e_cal_list_view_popup_menu (GtkWidget *widget)
374 ECalListView *cal_list_view = E_CAL_LIST_VIEW (widget);
376 e_cal_list_view_show_popup_menu (cal_list_view, NULL);
377 return TRUE;
380 static gboolean
381 e_cal_list_view_on_table_double_click (GtkWidget *table,
382 gint row,
383 gint col,
384 GdkEvent *event,
385 gpointer data)
387 ECalListView *cal_list_view = E_CAL_LIST_VIEW (data);
388 ECalModelComponent *comp_data;
390 comp_data = e_cal_model_get_component_at (e_calendar_view_get_model (E_CALENDAR_VIEW (cal_list_view)), row);
391 e_calendar_view_edit_appointment (E_CALENDAR_VIEW (cal_list_view), comp_data->client, comp_data->icalcomp, EDIT_EVENT_AUTODETECT);
393 return TRUE;
396 static gboolean
397 e_cal_list_view_on_table_right_click (GtkWidget *table,
398 gint row,
399 gint col,
400 GdkEvent *event,
401 gpointer data)
403 ECalListView *cal_list_view = E_CAL_LIST_VIEW (data);
405 e_cal_list_view_show_popup_menu (cal_list_view, event);
407 return TRUE;
410 static gboolean
411 e_cal_list_view_on_table_white_space_event (ETable *table,
412 GdkEvent *event,
413 gpointer user_data)
415 ECalListView *cal_list_view = user_data;
416 guint event_button = 0;
418 g_return_val_if_fail (E_IS_CAL_LIST_VIEW (cal_list_view), FALSE);
419 g_return_val_if_fail (event != NULL, FALSE);
421 if (event->type == GDK_BUTTON_PRESS &&
422 gdk_event_get_button (event, &event_button) &&
423 event_button == 3) {
424 GtkWidget *table_canvas;
426 table_canvas = GTK_WIDGET (table->table_canvas);
428 if (!gtk_widget_has_focus (table_canvas))
429 gtk_widget_grab_focus (table_canvas);
431 e_cal_list_view_show_popup_menu (cal_list_view, event);
433 return TRUE;
436 return FALSE;
439 static void
440 e_cal_list_view_cursor_change_cb (ETable *etable,
441 gint row,
442 gpointer data)
444 ECalListView *cal_list_view = E_CAL_LIST_VIEW (data);
446 g_signal_emit_by_name (cal_list_view, "selection_changed");
449 static gboolean
450 e_cal_list_view_get_selected_time_range (ECalendarView *cal_view,
451 time_t *start_time,
452 time_t *end_time)
454 GList *selected;
455 icaltimezone *zone;
457 selected = e_calendar_view_get_selected_events (cal_view);
458 if (selected) {
459 ECalendarViewEvent *event = (ECalendarViewEvent *) selected->data;
460 ECalComponentDateTime dtstart, dtend;
461 ECalComponent *comp;
463 if (!is_comp_data_valid (event))
464 return FALSE;
466 comp = e_cal_component_new ();
467 e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp));
468 if (start_time) {
469 e_cal_component_get_dtstart (comp, &dtstart);
470 if (dtstart.tzid) {
471 zone = icalcomponent_get_timezone (e_cal_component_get_icalcomponent (comp), dtstart.tzid);
472 } else {
473 zone = NULL;
475 *start_time = icaltime_as_timet_with_zone (*dtstart.value, zone);
476 e_cal_component_free_datetime (&dtstart);
478 if (end_time) {
479 e_cal_component_get_dtend (comp, &dtend);
480 if (dtend.tzid) {
481 zone = icalcomponent_get_timezone (e_cal_component_get_icalcomponent (comp), dtend.tzid);
482 } else {
483 zone = NULL;
485 *end_time = icaltime_as_timet_with_zone (*dtend.value, zone);
486 e_cal_component_free_datetime (&dtend);
489 g_object_unref (comp);
490 g_list_free (selected);
492 return TRUE;
495 return FALSE;
498 static GList *
499 e_cal_list_view_get_selected_events (ECalendarView *cal_view)
501 GList *event_list = NULL;
502 gint cursor_row;
504 if (E_CAL_LIST_VIEW (cal_view)->cursor_event) {
505 g_free (E_CAL_LIST_VIEW (cal_view)->cursor_event);
506 E_CAL_LIST_VIEW (cal_view)->cursor_event = NULL;
509 cursor_row = e_table_get_cursor_row (
510 E_CAL_LIST_VIEW (cal_view)->table);
512 if (cursor_row >= 0) {
513 ECalendarViewEvent *event;
515 event = E_CAL_LIST_VIEW (cal_view)->cursor_event = g_new0 (ECalendarViewEvent, 1);
516 event->comp_data =
517 e_cal_model_get_component_at (
518 e_calendar_view_get_model (cal_view),
519 cursor_row);
520 event_list = g_list_prepend (event_list, event);
523 return event_list;
526 static void
527 adjust_range (icaltimetype icaltime,
528 time_t *earliest,
529 time_t *latest,
530 gboolean *set)
532 time_t t;
534 if (!icaltime_is_valid_time (icaltime))
535 return;
537 t = icaltime_as_timet (icaltime);
538 *earliest = MIN (*earliest, t);
539 *latest = MAX (*latest, t);
541 *set = TRUE;
544 /* NOTE: Time use for this function increases linearly with number of events.
545 * This is not ideal, since it's used in a couple of places. We could probably
546 * be smarter about it, and use do it less frequently... */
547 static gboolean
548 e_cal_list_view_get_visible_time_range (ECalendarView *cal_view,
549 time_t *start_time,
550 time_t *end_time)
552 time_t earliest = G_MAXINT, latest = 0;
553 gboolean set = FALSE;
554 gint n_rows, i;
556 n_rows = e_table_model_row_count (E_TABLE_MODEL (e_calendar_view_get_model (cal_view)));
558 for (i = 0; i < n_rows; i++) {
559 ECalModelComponent *comp;
560 icalcomponent *icalcomp;
562 comp = e_cal_model_get_component_at (e_calendar_view_get_model (cal_view), i);
563 if (!comp)
564 continue;
566 icalcomp = comp->icalcomp;
567 if (!icalcomp)
568 continue;
570 adjust_range (icalcomponent_get_dtstart (icalcomp), &earliest, &latest, &set);
571 adjust_range (icalcomponent_get_dtend (icalcomp), &earliest, &latest, &set);
574 if (set) {
575 *start_time = earliest;
576 *end_time = latest;
577 return TRUE;
580 if (!n_rows) {
581 ECalModel *model = e_calendar_view_get_model (cal_view);
583 /* Use time range set in the model when nothing shown in the list view */
584 e_cal_model_get_time_range (model, start_time, end_time);
586 return TRUE;
589 return FALSE;
592 gboolean
593 e_cal_list_view_get_range_shown (ECalListView *cal_list_view,
594 GDate *start_date,
595 gint *days_shown)
597 time_t first, last;
598 GDate end_date;
600 if (!e_cal_list_view_get_visible_time_range (E_CALENDAR_VIEW (cal_list_view), &first, &last))
601 return FALSE;
603 time_to_gdate_with_zone (start_date, first, e_calendar_view_get_timezone (E_CALENDAR_VIEW (cal_list_view)));
604 time_to_gdate_with_zone (&end_date, last, e_calendar_view_get_timezone (E_CALENDAR_VIEW (cal_list_view)));
606 *days_shown = g_date_days_between (start_date, &end_date);
607 return TRUE;
610 gboolean
611 e_cal_list_view_is_editing (ECalListView *eclv)
613 g_return_val_if_fail (E_IS_CAL_LIST_VIEW (eclv), FALSE);
615 return eclv->table && e_table_is_editing (eclv->table);