Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / e-day-view-time-item.c
blob0afec821d2ef665eb370d9fc56759bbaa5b78c97
1 /*
2 * EDayViewTimeItem - canvas item which displays the times down the left of
3 * the EDayView.
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 * Authors:
18 * Damon Chaplin <damon@ximian.com>
20 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
23 #include "evolution-config.h"
25 #include <string.h>
26 #include <gtk/gtk.h>
27 #include <glib/gi18n.h>
29 #include "e-day-view-time-item.h"
30 #include "calendar-config.h"
32 /* The spacing between items in the time column. GRID_X_PAD is the space down
33 * either side of the column, i.e. outside the main horizontal grid lines.
34 * HOUR_L_PAD & HOUR_R_PAD are the spaces on the left & right side of the
35 * big hour number (this is inside the horizontal grid lines).
36 * MIN_X_PAD is the spacing either side of the minute number. The smaller
37 * horizontal grid lines match with this.
38 * 60_MIN_X_PAD is the space either side of the HH:MM display used when
39 * we are displaying 60 mins per row (inside the main grid lines).
40 * LARGE_HOUR_Y_PAD is the offset of the large hour string from the top of the
41 * row.
42 * SMALL_FONT_Y_PAD is the offset of the small time/minute string from the top
43 * of the row. */
44 #define E_DVTMI_TIME_GRID_X_PAD 4
45 #define E_DVTMI_HOUR_L_PAD 4
46 #define E_DVTMI_HOUR_R_PAD 2
47 #define E_DVTMI_MIN_X_PAD 2
48 #define E_DVTMI_60_MIN_X_PAD 4
49 #define E_DVTMI_LARGE_HOUR_Y_PAD 1
50 #define E_DVTMI_SMALL_FONT_Y_PAD 1
52 #define E_DAY_VIEW_TIME_ITEM_GET_PRIVATE(obj) \
53 (G_TYPE_INSTANCE_GET_PRIVATE \
54 ((obj), E_TYPE_DAY_VIEW_TIME_ITEM, EDayViewTimeItemPrivate))
56 struct _EDayViewTimeItemPrivate {
57 /* The parent EDayView widget. */
58 EDayView *day_view;
60 /* The width of the time column. */
61 gint column_width;
63 /* TRUE if we are currently dragging the selection times. */
64 gboolean dragging_selection;
66 /* The second timezone if shown, or else NULL. */
67 icaltimezone *second_zone;
70 static void e_day_view_time_item_update (GnomeCanvasItem *item,
71 const cairo_matrix_t *i2c,
72 gint flags);
73 static void e_day_view_time_item_draw (GnomeCanvasItem *item,
74 cairo_t *cr,
75 gint x,
76 gint y,
77 gint width,
78 gint height);
79 static GnomeCanvasItem *
80 e_day_view_time_item_point (GnomeCanvasItem *item,
81 gdouble x,
82 gdouble y,
83 gint cx,
84 gint cy);
85 static gint e_day_view_time_item_event (GnomeCanvasItem *item,
86 GdkEvent *event);
87 static void e_day_view_time_item_increment_time
88 (gint *hour,
89 gint *minute,
90 gint time_divisions);
91 static void e_day_view_time_item_show_popup_menu
92 (EDayViewTimeItem *time_item,
93 GdkEvent *event);
94 static void e_day_view_time_item_on_set_divisions
95 (GtkWidget *item,
96 EDayViewTimeItem *time_item);
97 static void e_day_view_time_item_on_button_press
98 (EDayViewTimeItem *time_item,
99 GdkEvent *event);
100 static void e_day_view_time_item_on_button_release
101 (EDayViewTimeItem *time_item,
102 GdkEvent *event);
103 static void e_day_view_time_item_on_motion_notify
104 (EDayViewTimeItem *time_item,
105 GdkEvent *event);
106 static gint e_day_view_time_item_convert_position_to_row
107 (EDayViewTimeItem *time_item,
108 gint y);
110 static void edvti_second_zone_changed_cb (GSettings *settings,
111 const gchar *key,
112 gpointer user_data);
114 enum {
115 PROP_0,
116 PROP_DAY_VIEW
119 G_DEFINE_TYPE (
120 EDayViewTimeItem,
121 e_day_view_time_item,
122 GNOME_TYPE_CANVAS_ITEM)
124 static void
125 day_view_time_item_set_property (GObject *object,
126 guint property_id,
127 const GValue *value,
128 GParamSpec *pspec)
130 switch (property_id) {
131 case PROP_DAY_VIEW:
132 e_day_view_time_item_set_day_view (
133 E_DAY_VIEW_TIME_ITEM (object),
134 g_value_get_object (value));
135 return;
138 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
141 static void
142 day_view_time_item_get_property (GObject *object,
143 guint property_id,
144 GValue *value,
145 GParamSpec *pspec)
147 switch (property_id) {
148 case PROP_DAY_VIEW:
149 g_value_set_object (
150 value, e_day_view_time_item_get_day_view (
151 E_DAY_VIEW_TIME_ITEM (object)));
152 return;
155 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
158 static void
159 day_view_time_item_dispose (GObject *object)
161 EDayViewTimeItemPrivate *priv;
163 priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (object);
165 if (priv->day_view != NULL) {
166 g_object_unref (priv->day_view);
167 priv->day_view = NULL;
170 /* Chain up to parent's dispose() method. */
171 G_OBJECT_CLASS (e_day_view_time_item_parent_class)->dispose (object);
174 static void
175 day_view_time_item_finalize (GObject *object)
177 EDayViewTimeItem *time_item;
179 time_item = E_DAY_VIEW_TIME_ITEM (object);
181 calendar_config_remove_notification (
182 (CalendarConfigChangedFunc)
183 edvti_second_zone_changed_cb, time_item);
185 /* Chain up to parent's dispose() method. */
186 G_OBJECT_CLASS (e_day_view_time_item_parent_class)->finalize (object);
189 static void
190 e_day_view_time_item_class_init (EDayViewTimeItemClass *class)
192 GObjectClass *object_class;
193 GnomeCanvasItemClass *item_class;
195 g_type_class_add_private (class, sizeof (EDayViewTimeItemPrivate));
197 object_class = G_OBJECT_CLASS (class);
198 object_class->set_property = day_view_time_item_set_property;
199 object_class->get_property = day_view_time_item_get_property;
200 object_class->dispose = day_view_time_item_dispose;
201 object_class->finalize = day_view_time_item_finalize;
203 item_class = GNOME_CANVAS_ITEM_CLASS (class);
204 item_class->update = e_day_view_time_item_update;
205 item_class->draw = e_day_view_time_item_draw;
206 item_class->point = e_day_view_time_item_point;
207 item_class->event = e_day_view_time_item_event;
209 g_object_class_install_property (
210 object_class,
211 PROP_DAY_VIEW,
212 g_param_spec_object (
213 "day-view",
214 "Day View",
215 NULL,
216 E_TYPE_DAY_VIEW,
217 G_PARAM_READWRITE));
220 static void
221 e_day_view_time_item_init (EDayViewTimeItem *time_item)
223 gchar *last;
225 time_item->priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (time_item);
227 last = calendar_config_get_day_second_zone ();
229 if (last) {
230 if (*last)
231 time_item->priv->second_zone =
232 icaltimezone_get_builtin_timezone (last);
233 g_free (last);
236 calendar_config_add_notification_day_second_zone (
237 (CalendarConfigChangedFunc) edvti_second_zone_changed_cb,
238 time_item);
241 static void
242 e_day_view_time_item_update (GnomeCanvasItem *item,
243 const cairo_matrix_t *i2c,
244 gint flags)
246 if (GNOME_CANVAS_ITEM_CLASS (e_day_view_time_item_parent_class)->update)
247 (* GNOME_CANVAS_ITEM_CLASS (e_day_view_time_item_parent_class)->update) (item, i2c, flags);
249 /* The item covers the entire canvas area. */
250 item->x1 = 0;
251 item->y1 = 0;
252 item->x2 = INT_MAX;
253 item->y2 = INT_MAX;
257 * DRAWING ROUTINES - functions to paint the canvas item.
259 static void
260 edvti_draw_zone (GnomeCanvasItem *canvas_item,
261 cairo_t *cr,
262 gint x,
263 gint y,
264 gint width,
265 gint height,
266 gint x_offset,
267 icaltimezone *use_zone)
269 EDayView *day_view;
270 EDayViewTimeItem *time_item;
271 ECalendarView *cal_view;
272 ECalModel *model;
273 const gchar *suffix;
274 gchar buffer[64], *midnight_day = NULL, *midnight_month = NULL;
275 gint time_divisions;
276 gint hour, display_hour, minute, row;
277 gint row_y, start_y, large_hour_y_offset, small_font_y_offset;
278 gint long_line_x1, long_line_x2, short_line_x1;
279 gint large_hour_x2, minute_x2;
280 gint hour_width, minute_width, suffix_width;
281 gint max_suffix_width, max_minute_or_suffix_width;
282 PangoLayout *layout;
283 PangoContext *context;
284 PangoFontMetrics *large_font_metrics, *small_font_metrics;
285 GtkWidget *widget;
286 GdkRGBA fg, dark;
287 GdkColor mb_color;
289 time_item = E_DAY_VIEW_TIME_ITEM (canvas_item);
290 day_view = e_day_view_time_item_get_day_view (time_item);
291 g_return_if_fail (day_view != NULL);
293 widget = GTK_WIDGET (day_view);
294 cal_view = E_CALENDAR_VIEW (day_view);
295 model = e_calendar_view_get_model (cal_view);
296 time_divisions = e_calendar_view_get_time_divisions (cal_view);
298 context = gtk_widget_get_pango_context (GTK_WIDGET (day_view));
299 small_font_metrics = pango_context_get_metrics (
300 context, NULL,
301 pango_context_get_language (context));
302 large_font_metrics = pango_context_get_metrics (
303 context, day_view->large_font_desc,
304 pango_context_get_language (context));
306 e_utils_get_theme_color (widget, "theme_fg_color,theme_text_color", E_UTILS_DEFAULT_THEME_FG_COLOR, &fg);
307 e_utils_get_theme_color (widget, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &dark);
309 /* The start and end of the long horizontal line between hours. */
310 long_line_x1 =
311 (use_zone ? 0 : E_DVTMI_TIME_GRID_X_PAD) - x + x_offset;
312 long_line_x2 =
313 time_item->priv->column_width -
314 E_DVTMI_TIME_GRID_X_PAD - x -
315 (use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0) + x_offset;
317 if (time_divisions == 60) {
318 /* The right edge of the complete time string in 60-min
319 * divisions, e.g. "14:00" or "2 pm". */
320 minute_x2 = long_line_x2 - E_DVTMI_60_MIN_X_PAD;
322 /* These aren't used for 60-minute divisions, but we initialize
323 * them to keep gcc happy. */
324 short_line_x1 = 0;
325 large_hour_x2 = 0;
326 } else {
327 max_suffix_width = MAX (
328 day_view->am_string_width,
329 day_view->pm_string_width);
331 max_minute_or_suffix_width = MAX (
332 max_suffix_width,
333 day_view->max_minute_width);
335 /* The start of the short horizontal line between the periods
336 * within each hour. */
337 short_line_x1 = long_line_x2 - E_DVTMI_MIN_X_PAD * 2
338 - max_minute_or_suffix_width;
340 /* The right edge of the large hour string. */
341 large_hour_x2 = short_line_x1 - E_DVTMI_HOUR_R_PAD;
343 /* The right edge of the minute part of the time. */
344 minute_x2 = long_line_x2 - E_DVTMI_MIN_X_PAD;
347 /* Start with the first hour & minute shown in the EDayView. */
348 hour = day_view->first_hour_shown;
349 minute = day_view->first_minute_shown;
351 if (use_zone) {
352 /* shift time with a difference between
353 * local time and the other timezone */
354 icaltimezone *cal_zone;
355 struct icaltimetype tt;
356 gint diff;
357 struct tm mn;
359 cal_zone = e_calendar_view_get_timezone (
360 E_CALENDAR_VIEW (day_view));
361 tt = icaltime_from_timet_with_zone (
362 day_view->day_starts[0], 0, cal_zone);
364 /* diff is number of minutes */
365 diff =(icaltimezone_get_utc_offset (use_zone, &tt, NULL) -
366 icaltimezone_get_utc_offset (cal_zone, &tt, NULL)) / 60;
368 tt = icaltime_from_timet_with_zone (day_view->day_starts[0], 0, cal_zone);
369 tt.is_date = FALSE;
370 icaltime_set_timezone (&tt, cal_zone);
371 tt = icaltime_convert_to_zone (tt, use_zone);
373 if (diff != 0) {
374 /* shows the next midnight */
375 icaltime_adjust (&tt, 1, 0, 0, 0);
378 mn = icaltimetype_to_tm (&tt);
380 /* up to two characters/numbers */
381 e_utf8_strftime (buffer, sizeof (buffer), "%d", &mn);
382 midnight_day = g_strdup (buffer);
383 /* up to three characters, abbreviated month name */
384 e_utf8_strftime (buffer, sizeof (buffer), "%b", &mn);
385 midnight_month = g_strdup (buffer);
387 minute += (diff % 60);
388 hour += (diff / 60) + (minute / 60);
390 minute = minute % 60;
391 if (minute < 0) {
392 hour--;
393 minute += 60;
396 hour = (hour + 48) % 24;
399 /* The offset of the large hour string from the top of the row. */
400 large_hour_y_offset = E_DVTMI_LARGE_HOUR_Y_PAD;
402 /* The offset of the small time/minute string from top of row. */
403 small_font_y_offset = E_DVTMI_SMALL_FONT_Y_PAD;
405 /* Calculate the minimum y position of the first row we need to draw.
406 * This is normally one row height above the 0 position, but if we
407 * are using the large font we may have to go back a bit further. */
408 start_y = 0 - MAX (day_view->row_height,
409 (pango_font_metrics_get_ascent (large_font_metrics) +
410 pango_font_metrics_get_descent (large_font_metrics)) / PANGO_SCALE +
411 E_DVTMI_LARGE_HOUR_Y_PAD);
413 /* Draw the Marcus Bains Line first, so it appears under other elements. */
414 if (e_day_view_marcus_bains_get_show_line (day_view)) {
415 struct icaltimetype time_now;
416 const gchar *marcus_bains_time_bar_color;
417 gint marcus_bains_y;
419 cairo_save (cr);
420 gdk_cairo_set_source_color (
421 cr, &day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE]);
423 marcus_bains_time_bar_color =
424 e_day_view_marcus_bains_get_time_bar_color (day_view);
425 if (marcus_bains_time_bar_color == NULL)
426 marcus_bains_time_bar_color = "";
428 if (gdk_color_parse (marcus_bains_time_bar_color, &mb_color)) {
429 gdk_cairo_set_source_color (cr, &mb_color);
430 } else {
431 mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE];
434 time_now = icaltime_current_time_with_zone (
435 e_calendar_view_get_timezone (
436 E_CALENDAR_VIEW (day_view)));
437 marcus_bains_y =
438 (time_now.hour * 60 + time_now.minute) *
439 day_view->row_height / time_divisions - y;
440 cairo_set_line_width (cr, 1.5);
441 cairo_move_to (
442 cr, long_line_x1 -
443 (use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0),
444 marcus_bains_y);
445 cairo_line_to (cr, long_line_x2, marcus_bains_y);
446 cairo_stroke (cr);
447 cairo_restore (cr);
448 } else {
449 const gchar *marcus_bains_time_bar_color;
451 marcus_bains_time_bar_color =
452 e_day_view_marcus_bains_get_time_bar_color (day_view);
453 if (marcus_bains_time_bar_color == NULL)
454 marcus_bains_time_bar_color = "";
456 mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE];
458 if (gdk_color_parse (marcus_bains_time_bar_color, &mb_color))
459 gdk_cairo_set_source_color (cr, &mb_color);
462 /* Step through each row, drawing the times and the horizontal lines
463 * between them. */
464 for (row = 0, row_y = 0 - y;
465 row < day_view->rows && row_y < height;
466 row++, row_y += day_view->row_height) {
467 gboolean show_midnight_date;
469 show_midnight_date =
470 use_zone && hour == 0 &&
471 (minute == 0 || time_divisions == 60) &&
472 midnight_day && midnight_month;
474 /* If the row is above the first row we want to draw just
475 * increment the time and skip to the next row. */
476 if (row_y < start_y) {
477 e_day_view_time_item_increment_time (
478 &hour, &minute, time_divisions);
479 continue;
482 /* Calculate the actual hour number to display. For 12-hour
483 * format we convert 0-23 to 12-11am / 12 - 11pm. */
484 e_day_view_convert_time_to_display (
485 day_view, hour,
486 &display_hour,
487 &suffix, &suffix_width);
489 if (time_divisions == 60) {
490 /* 60 minute intervals - draw a long horizontal line
491 * between hours and display as one long string,
492 * e.g. "14:00" or "2 pm". */
493 cairo_save (cr);
494 gdk_cairo_set_source_rgba (cr, &dark);
495 cairo_save (cr);
496 cairo_set_line_width (cr, 0.7);
497 cairo_move_to (cr, long_line_x1, row_y);
498 cairo_line_to (cr, long_line_x2, row_y);
499 cairo_stroke (cr);
500 cairo_restore (cr);
502 if (show_midnight_date) {
503 g_snprintf (buffer, sizeof (buffer), "%s %s", midnight_day, midnight_month);
504 } else if (e_cal_model_get_use_24_hour_format (model)) {
505 g_snprintf (
506 buffer, sizeof (buffer), "%i:%02i",
507 display_hour, minute);
508 } else {
509 g_snprintf (
510 buffer, sizeof (buffer), "%i %s",
511 display_hour, suffix);
514 cairo_save (cr);
515 if (show_midnight_date)
516 gdk_cairo_set_source_color (cr, &mb_color);
517 else
518 gdk_cairo_set_source_rgba (cr, &fg);
519 layout = gtk_widget_create_pango_layout (GTK_WIDGET (day_view), NULL);
520 pango_layout_set_text (layout, buffer, -1);
521 pango_layout_get_pixel_size (layout, &minute_width, NULL);
522 cairo_translate (
523 cr, minute_x2 - minute_width,
524 row_y + small_font_y_offset);
525 pango_cairo_update_layout (cr, layout);
526 pango_cairo_show_layout (cr, layout);
527 cairo_restore (cr);
529 g_object_unref (layout);
531 cairo_restore (cr);
532 } else {
533 /* 5/10/15/30 minute intervals. */
535 if (minute == 0) {
536 /* On the hour - draw a long horizontal line
537 * before the hour and display the hour in the
538 * large font. */
540 cairo_save (cr);
541 gdk_cairo_set_source_rgba (cr, &dark);
542 if (show_midnight_date)
543 g_snprintf (buffer, sizeof (buffer), "%s", midnight_day);
544 else
545 g_snprintf (
546 buffer, sizeof (buffer), "%i",
547 display_hour);
549 cairo_set_line_width (cr, 0.7);
550 cairo_move_to (cr, long_line_x1, row_y);
551 cairo_line_to (cr, long_line_x2, row_y);
552 cairo_stroke (cr);
553 cairo_restore (cr);
555 cairo_save (cr);
556 if (show_midnight_date)
557 gdk_cairo_set_source_color (cr, &mb_color);
558 else
559 gdk_cairo_set_source_rgba (cr, &fg);
560 layout = gtk_widget_create_pango_layout (GTK_WIDGET (day_view), NULL);
561 pango_layout_set_text (layout, buffer, -1);
562 pango_layout_set_font_description (
563 layout, day_view->large_font_desc);
564 pango_layout_get_pixel_size (
565 layout, &hour_width, NULL);
566 cairo_translate (
567 cr, large_hour_x2 - hour_width,
568 row_y + large_hour_y_offset);
569 pango_cairo_update_layout (cr, layout);
570 pango_cairo_show_layout (cr, layout);
571 cairo_restore (cr);
573 g_object_unref (layout);
574 } else {
575 /* Within the hour - draw a short line before
576 * the time. */
577 cairo_save (cr);
578 gdk_cairo_set_source_rgba (cr, &dark);
579 cairo_set_line_width (cr, 0.7);
580 cairo_move_to (cr, short_line_x1, row_y);
581 cairo_line_to (cr, long_line_x2, row_y);
582 cairo_stroke (cr);
583 cairo_restore (cr);
586 /* Normally we display the minute in each
587 * interval, but when using 30-minute intervals
588 * we don't display the '30'. */
589 if (time_divisions != 30 || minute != 30) {
590 /* In 12-hour format we display 'am' or 'pm'
591 * instead of '00'. */
592 if (show_midnight_date)
593 g_snprintf (buffer, sizeof (buffer), "%s", midnight_month);
594 else if (minute == 0
595 && !e_cal_model_get_use_24_hour_format (model)) {
596 g_snprintf (buffer, sizeof (buffer), "%s", suffix);
597 } else {
598 g_snprintf (
599 buffer, sizeof (buffer),
600 "%02i", minute);
603 cairo_save (cr);
604 if (show_midnight_date)
605 gdk_cairo_set_source_color (cr, &mb_color);
606 else
607 gdk_cairo_set_source_rgba (cr, &fg);
608 layout = gtk_widget_create_pango_layout (GTK_WIDGET (day_view), NULL);
609 pango_layout_set_text (layout, buffer, -1);
610 pango_layout_set_font_description (
611 layout, day_view->small_font_desc);
612 pango_layout_get_pixel_size (
613 layout, &minute_width, NULL);
614 cairo_translate (
615 cr, minute_x2 - minute_width,
616 row_y + small_font_y_offset);
617 pango_cairo_update_layout (cr, layout);
618 pango_cairo_show_layout (cr, layout);
619 cairo_restore (cr);
621 g_object_unref (layout);
625 e_day_view_time_item_increment_time (
626 &hour, &minute,
627 time_divisions);
630 pango_font_metrics_unref (large_font_metrics);
631 pango_font_metrics_unref (small_font_metrics);
633 g_free (midnight_day);
634 g_free (midnight_month);
637 static void
638 e_day_view_time_item_draw (GnomeCanvasItem *canvas_item,
639 cairo_t *cr,
640 gint x,
641 gint y,
642 gint width,
643 gint height)
645 EDayViewTimeItem *time_item;
647 time_item = E_DAY_VIEW_TIME_ITEM (canvas_item);
648 g_return_if_fail (time_item != NULL);
650 edvti_draw_zone (canvas_item, cr, x, y, width, height, 0, NULL);
652 if (time_item->priv->second_zone)
653 edvti_draw_zone (
654 canvas_item, cr, x, y, width, height,
655 time_item->priv->column_width,
656 time_item->priv->second_zone);
659 /* Increment the time by the 5/10/15/30/60 minute interval.
660 * Note that time_divisions is never > 60, so we never have to
661 * worry about adding more than 60 minutes. */
662 static void
663 e_day_view_time_item_increment_time (gint *hour,
664 gint *minute,
665 gint time_divisions)
667 *minute += time_divisions;
668 if (*minute >= 60) {
669 *minute -= 60;
670 /* Currently we never wrap around to the next day, but
671 * we may do if we display extra timezones. */
672 *hour = (*hour + 1) % 24;
676 static GnomeCanvasItem *
677 e_day_view_time_item_point (GnomeCanvasItem *item,
678 gdouble x,
679 gdouble y,
680 gint cx,
681 gint cy)
683 return item;
686 static gint
687 e_day_view_time_item_event (GnomeCanvasItem *item,
688 GdkEvent *event)
690 EDayViewTimeItem *time_item;
692 time_item = E_DAY_VIEW_TIME_ITEM (item);
694 switch (event->type) {
695 case GDK_BUTTON_PRESS:
696 if (event->button.button == 1) {
697 e_day_view_time_item_on_button_press (time_item, event);
698 } else if (event->button.button == 3) {
699 e_day_view_time_item_show_popup_menu (time_item, event);
700 return TRUE;
702 break;
703 case GDK_BUTTON_RELEASE:
704 if (event->button.button == 1)
705 e_day_view_time_item_on_button_release (
706 time_item, event);
707 break;
709 case GDK_MOTION_NOTIFY:
710 e_day_view_time_item_on_motion_notify (time_item, event);
711 break;
713 default:
714 break;
717 return FALSE;
720 static void
721 edvti_second_zone_changed_cb (GSettings *settings,
722 const gchar *key,
723 gpointer user_data)
725 EDayViewTimeItem *time_item = user_data;
726 EDayView *day_view;
727 icaltimezone *second_zone;
728 gchar *location;
730 g_return_if_fail (user_data != NULL);
731 g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item));
733 location = calendar_config_get_day_second_zone ();
734 second_zone = location ? icaltimezone_get_builtin_timezone (location) : NULL;
735 g_free (location);
737 if (second_zone == time_item->priv->second_zone)
738 return;
740 time_item->priv->second_zone = second_zone;
742 day_view = e_day_view_time_item_get_day_view (time_item);
743 gtk_widget_set_size_request (
744 day_view->time_canvas,
745 e_day_view_time_item_get_column_width (time_item), -1);
746 gtk_widget_queue_draw (day_view->time_canvas);
748 e_day_view_update_timezone_name_labels (day_view);
751 static void
752 edvti_on_select_zone (GtkWidget *item,
753 EDayViewTimeItem *time_item)
755 calendar_config_select_day_second_zone ();
758 static void
759 edvti_on_set_zone (GtkWidget *item,
760 EDayViewTimeItem *time_item)
762 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
763 return;
765 calendar_config_set_day_second_zone (
766 g_object_get_data (G_OBJECT (item), "timezone"));
769 static void
770 e_day_view_time_item_show_popup_menu (EDayViewTimeItem *time_item,
771 GdkEvent *event)
773 static gint divisions[] = { 60, 30, 15, 10, 5 };
774 EDayView *day_view;
775 ECalendarView *cal_view;
776 GtkWidget *menu, *item, *submenu;
777 gchar buffer[256];
778 GSList *group = NULL, *recent_zones, *s;
779 gint current_divisions, i;
780 icaltimezone *zone;
782 day_view = e_day_view_time_item_get_day_view (time_item);
783 g_return_if_fail (day_view != NULL);
785 cal_view = E_CALENDAR_VIEW (day_view);
786 current_divisions = e_calendar_view_get_time_divisions (cal_view);
788 menu = gtk_menu_new ();
790 /* Make sure the menu is destroyed when it disappears. */
791 g_signal_connect (
792 menu, "selection-done",
793 G_CALLBACK (gtk_widget_destroy), NULL);
795 for (i = 0; i < G_N_ELEMENTS (divisions); i++) {
796 g_snprintf (
797 buffer, sizeof (buffer),
798 /* Translators: %02i is the number of minutes;
799 * this is a context menu entry to change the
800 * length of the time division in the calendar
801 * day view, e.g. a day is displayed in
802 * 24 "60 minute divisions" or
803 * 48 "30 minute divisions". */
804 _("%02i minute divisions"), divisions[i]);
805 item = gtk_radio_menu_item_new_with_label (group, buffer);
806 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
807 gtk_widget_show (item);
808 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
810 if (current_divisions == divisions[i])
811 gtk_check_menu_item_set_active (
812 GTK_CHECK_MENU_ITEM (item), TRUE);
814 g_object_set_data (
815 G_OBJECT (item), "divisions",
816 GINT_TO_POINTER (divisions[i]));
818 g_signal_connect (
819 item, "toggled",
820 G_CALLBACK (e_day_view_time_item_on_set_divisions),
821 time_item);
824 item = gtk_separator_menu_item_new ();
825 gtk_widget_show (item);
826 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
828 submenu = gtk_menu_new ();
829 item = gtk_menu_item_new_with_label (_("Show the second time zone"));
830 gtk_widget_show (item);
831 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
832 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
834 zone = e_calendar_view_get_timezone (E_CALENDAR_VIEW (day_view));
835 if (zone)
836 item = gtk_menu_item_new_with_label (icaltimezone_get_display_name (zone));
837 else
838 item = gtk_menu_item_new_with_label ("---");
839 gtk_widget_set_sensitive (item, FALSE);
840 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
842 item = gtk_separator_menu_item_new ();
843 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
845 group = NULL;
846 item = gtk_radio_menu_item_new_with_label (group, C_("cal-second-zone", "None"));
847 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
848 if (!time_item->priv->second_zone)
849 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
850 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
851 g_signal_connect (
852 item, "toggled",
853 G_CALLBACK (edvti_on_set_zone), time_item);
855 recent_zones = calendar_config_get_day_second_zones ();
856 for (s = recent_zones; s != NULL; s = s->next) {
857 zone = icaltimezone_get_builtin_timezone (s->data);
858 if (!zone)
859 continue;
861 item = gtk_radio_menu_item_new_with_label (
862 group, icaltimezone_get_display_name (zone));
863 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
864 /* both comes from builtin, thus no problem to compare pointers */
865 if (zone == time_item->priv->second_zone)
866 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
867 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
868 g_object_set_data_full (
869 G_OBJECT (item), "timezone",
870 g_strdup (s->data), g_free);
871 g_signal_connect (
872 item, "toggled",
873 G_CALLBACK (edvti_on_set_zone), time_item);
875 calendar_config_free_day_second_zones (recent_zones);
877 item = gtk_separator_menu_item_new ();
878 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
880 item = gtk_menu_item_new_with_label (_("Select..."));
881 g_signal_connect (
882 item, "activate",
883 G_CALLBACK (edvti_on_select_zone), time_item);
884 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
886 gtk_widget_show_all (submenu);
888 gtk_menu_attach_to_widget (GTK_MENU (menu),
889 GTK_WIDGET (day_view),
890 NULL);
891 gtk_menu_popup_at_pointer (GTK_MENU (menu), event);
894 static void
895 e_day_view_time_item_on_set_divisions (GtkWidget *item,
896 EDayViewTimeItem *time_item)
898 EDayView *day_view;
899 ECalendarView *cal_view;
900 gint divisions;
902 day_view = e_day_view_time_item_get_day_view (time_item);
903 g_return_if_fail (day_view != NULL);
905 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
906 return;
908 divisions = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "divisions"));
910 cal_view = E_CALENDAR_VIEW (day_view);
911 e_calendar_view_set_time_divisions (cal_view, divisions);
914 static void
915 e_day_view_time_item_on_button_press (EDayViewTimeItem *time_item,
916 GdkEvent *event)
918 GdkWindow *window;
919 EDayView *day_view;
920 GnomeCanvas *canvas;
921 GdkGrabStatus grab_status;
922 GdkDevice *event_device;
923 guint32 event_time;
924 gint row;
926 day_view = e_day_view_time_item_get_day_view (time_item);
927 g_return_if_fail (day_view != NULL);
929 canvas = GNOME_CANVAS_ITEM (time_item)->canvas;
931 row = e_day_view_time_item_convert_position_to_row (
932 time_item,
933 event->button.y);
935 if (row == -1)
936 return;
938 if (!gtk_widget_has_focus (GTK_WIDGET (day_view)))
939 gtk_widget_grab_focus (GTK_WIDGET (day_view));
941 window = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
943 event_device = gdk_event_get_device (event);
944 event_time = gdk_event_get_time (event);
946 grab_status = gdk_device_grab (
947 event_device,
948 window,
949 GDK_OWNERSHIP_NONE,
950 FALSE,
951 GDK_POINTER_MOTION_MASK |
952 GDK_BUTTON_RELEASE_MASK,
953 NULL,
954 event_time);
956 if (grab_status == GDK_GRAB_SUCCESS) {
957 e_day_view_start_selection (day_view, -1, row);
958 time_item->priv->dragging_selection = TRUE;
962 static void
963 e_day_view_time_item_on_button_release (EDayViewTimeItem *time_item,
964 GdkEvent *event)
966 EDayView *day_view;
968 day_view = e_day_view_time_item_get_day_view (time_item);
969 g_return_if_fail (day_view != NULL);
971 if (time_item->priv->dragging_selection) {
972 GdkDevice *event_device;
973 guint32 event_time;
975 event_device = gdk_event_get_device (event);
976 event_time = gdk_event_get_time (event);
977 gdk_device_ungrab (event_device, event_time);
979 e_day_view_finish_selection (day_view);
980 e_day_view_stop_auto_scroll (day_view);
983 time_item->priv->dragging_selection = FALSE;
986 static void
987 e_day_view_time_item_on_motion_notify (EDayViewTimeItem *time_item,
988 GdkEvent *event)
990 EDayView *day_view;
991 GnomeCanvas *canvas;
992 gdouble window_y;
993 gint y, row;
995 if (!time_item->priv->dragging_selection)
996 return;
998 day_view = e_day_view_time_item_get_day_view (time_item);
999 g_return_if_fail (day_view != NULL);
1001 canvas = GNOME_CANVAS_ITEM (time_item)->canvas;
1003 y = event->motion.y;
1004 row = e_day_view_time_item_convert_position_to_row (time_item, y);
1006 if (row != -1) {
1007 gnome_canvas_world_to_window (
1008 canvas, 0, event->motion.y,
1009 NULL, &window_y);
1010 e_day_view_update_selection (day_view, -1, row);
1011 e_day_view_check_auto_scroll (day_view, -1, (gint) window_y);
1015 /* Returns the row corresponding to the y position, or -1. */
1016 static gint
1017 e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *time_item,
1018 gint y)
1020 EDayView *day_view;
1021 gint row;
1023 day_view = e_day_view_time_item_get_day_view (time_item);
1024 g_return_val_if_fail (day_view != NULL, -1);
1026 if (y < 0)
1027 return -1;
1029 row = y / day_view->row_height;
1030 if (row >= day_view->rows)
1031 return -1;
1033 return row;
1036 EDayView *
1037 e_day_view_time_item_get_day_view (EDayViewTimeItem *time_item)
1039 g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL);
1041 return time_item->priv->day_view;
1044 void
1045 e_day_view_time_item_set_day_view (EDayViewTimeItem *time_item,
1046 EDayView *day_view)
1048 g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item));
1049 g_return_if_fail (E_IS_DAY_VIEW (day_view));
1051 if (time_item->priv->day_view == day_view)
1052 return;
1054 if (time_item->priv->day_view != NULL)
1055 g_object_unref (time_item->priv->day_view);
1057 time_item->priv->day_view = g_object_ref (day_view);
1059 g_object_notify (G_OBJECT (time_item), "day-view");
1062 /* Returns the minimum width needed for the column, by adding up all the
1063 * maximum widths of the strings. The string widths are all calculated in
1064 * the style_updated handlers of EDayView and EDayViewTimeCanvas. */
1065 gint
1066 e_day_view_time_item_get_column_width (EDayViewTimeItem *time_item)
1068 EDayView *day_view;
1069 gint digit, large_digit_width, max_large_digit_width = 0;
1070 gint max_suffix_width, max_minute_or_suffix_width;
1071 gint column_width_default, column_width_60_min_rows;
1073 day_view = e_day_view_time_item_get_day_view (time_item);
1074 g_return_val_if_fail (day_view != NULL, 0);
1076 /* Find the maximum width a digit can have. FIXME: We could use pango's
1077 * approximation function, but I worry it won't be precise enough. Also
1078 * it needs a language tag that I don't know where to get. */
1079 for (digit = '0'; digit <= '9'; digit++) {
1080 PangoLayout *layout;
1081 gchar digit_str[2];
1083 digit_str[0] = digit;
1084 digit_str[1] = '\0';
1086 layout = gtk_widget_create_pango_layout (GTK_WIDGET (day_view), digit_str);
1087 pango_layout_set_font_description (layout, day_view->large_font_desc);
1088 pango_layout_get_pixel_size (layout, &large_digit_width, NULL);
1090 g_object_unref (layout);
1092 max_large_digit_width = MAX (
1093 max_large_digit_width,
1094 large_digit_width);
1097 /* Calculate the width of each time column, using the maximum of the
1098 * default format with large hour numbers, and the 60-min divisions
1099 * format which uses small text. */
1100 max_suffix_width = MAX (
1101 day_view->am_string_width,
1102 day_view->pm_string_width);
1104 max_minute_or_suffix_width = MAX (
1105 max_suffix_width,
1106 day_view->max_minute_width);
1108 column_width_default = max_large_digit_width * 2
1109 + max_minute_or_suffix_width
1110 + E_DVTMI_MIN_X_PAD * 2
1111 + E_DVTMI_HOUR_L_PAD
1112 + E_DVTMI_HOUR_R_PAD
1113 + E_DVTMI_TIME_GRID_X_PAD * 2;
1115 column_width_60_min_rows = day_view->max_small_hour_width
1116 + day_view->colon_width
1117 + max_minute_or_suffix_width
1118 + E_DVTMI_60_MIN_X_PAD * 2
1119 + E_DVTMI_TIME_GRID_X_PAD * 2;
1121 time_item->priv->column_width =
1122 MAX (column_width_default, column_width_60_min_rows);
1124 if (time_item->priv->second_zone)
1125 return (2 * time_item->priv->column_width) -
1126 E_DVTMI_TIME_GRID_X_PAD;
1128 return time_item->priv->column_width;
1131 icaltimezone *
1132 e_day_view_time_item_get_second_zone (EDayViewTimeItem *time_item)
1134 g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL);
1136 return time_item->priv->second_zone;