No issues found
1 /*
2 * EDayViewTimeItem - canvas item which displays the times down the left of
3 * the EDayView.
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with the program; if not, see <http://www.gnu.org/licenses/>
17 *
18 * Authors:
19 * Damon Chaplin <damon@ximian.com>
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <string.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31
32 #include "e-day-view-time-item.h"
33 #include "calendar-config.h"
34 #include <widgets/e-timezone-dialog/e-timezone-dialog.h>
35
36 /* The spacing between items in the time column. GRID_X_PAD is the space down
37 * either side of the column, i.e. outside the main horizontal grid lines.
38 * HOUR_L_PAD & HOUR_R_PAD are the spaces on the left & right side of the
39 * big hour number (this is inside the horizontal grid lines).
40 * MIN_X_PAD is the spacing either side of the minute number. The smaller
41 * horizontal grid lines match with this.
42 * 60_MIN_X_PAD is the space either side of the HH:MM display used when
43 * we are displaying 60 mins per row (inside the main grid lines).
44 * LARGE_HOUR_Y_PAD is the offset of the large hour string from the top of the
45 * row.
46 * SMALL_FONT_Y_PAD is the offset of the small time/minute string from the top
47 * of the row. */
48 #define E_DVTMI_TIME_GRID_X_PAD 4
49 #define E_DVTMI_HOUR_L_PAD 4
50 #define E_DVTMI_HOUR_R_PAD 2
51 #define E_DVTMI_MIN_X_PAD 2
52 #define E_DVTMI_60_MIN_X_PAD 4
53 #define E_DVTMI_LARGE_HOUR_Y_PAD 1
54 #define E_DVTMI_SMALL_FONT_Y_PAD 1
55
56 #define E_DAY_VIEW_TIME_ITEM_GET_PRIVATE(obj) \
57 (G_TYPE_INSTANCE_GET_PRIVATE \
58 ((obj), E_TYPE_DAY_VIEW_TIME_ITEM, EDayViewTimeItemPrivate))
59
60 struct _EDayViewTimeItemPrivate {
61 /* The parent EDayView widget. */
62 EDayView *day_view;
63
64 /* The width of the time column. */
65 gint column_width;
66
67 /* TRUE if we are currently dragging the selection times. */
68 gboolean dragging_selection;
69
70 /* The second timezone if shown, or else NULL. */
71 icaltimezone *second_zone;
72 };
73
74 static void e_day_view_time_item_update (GnomeCanvasItem *item,
75 const cairo_matrix_t *i2c,
76 gint flags);
77 static void e_day_view_time_item_draw (GnomeCanvasItem *item,
78 cairo_t *cr,
79 gint x,
80 gint y,
81 gint width,
82 gint height);
83 static GnomeCanvasItem *
84 e_day_view_time_item_point (GnomeCanvasItem *item,
85 gdouble x,
86 gdouble y,
87 gint cx,
88 gint cy);
89 static gint e_day_view_time_item_event (GnomeCanvasItem *item,
90 GdkEvent *event);
91 static void e_day_view_time_item_increment_time
92 (gint *hour,
93 gint *minute,
94 gint time_divisions);
95 static void e_day_view_time_item_show_popup_menu
96 (EDayViewTimeItem *time_item,
97 GdkEvent *event);
98 static void e_day_view_time_item_on_set_divisions
99 (GtkWidget *item,
100 EDayViewTimeItem *time_item);
101 static void e_day_view_time_item_on_button_press
102 (EDayViewTimeItem *time_item,
103 GdkEvent *event);
104 static void e_day_view_time_item_on_button_release
105 (EDayViewTimeItem *time_item,
106 GdkEvent *event);
107 static void e_day_view_time_item_on_motion_notify
108 (EDayViewTimeItem *time_item,
109 GdkEvent *event);
110 static gint e_day_view_time_item_convert_position_to_row
111 (EDayViewTimeItem *time_item,
112 gint y);
113
114 static void edvti_second_zone_changed_cb (GSettings *settings,
115 const gchar *key,
116 gpointer user_data);
117
118 enum {
119 PROP_0,
120 PROP_DAY_VIEW
121 };
122
123 G_DEFINE_TYPE (
124 EDayViewTimeItem,
125 e_day_view_time_item,
126 GNOME_TYPE_CANVAS_ITEM)
127
128 static void
129 day_view_time_item_set_property (GObject *object,
130 guint property_id,
131 const GValue *value,
132 GParamSpec *pspec)
133 {
134 switch (property_id) {
135 case PROP_DAY_VIEW:
136 e_day_view_time_item_set_day_view (
137 E_DAY_VIEW_TIME_ITEM (object),
138 g_value_get_object (value));
139 return;
140 }
141
142 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
143 }
144
145 static void
146 day_view_time_item_get_property (GObject *object,
147 guint property_id,
148 GValue *value,
149 GParamSpec *pspec)
150 {
151 switch (property_id) {
152 case PROP_DAY_VIEW:
153 g_value_set_object (
154 value, e_day_view_time_item_get_day_view (
155 E_DAY_VIEW_TIME_ITEM (object)));
156 return;
157 }
158
159 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
160 }
161
162 static void
163 day_view_time_item_dispose (GObject *object)
164 {
165 EDayViewTimeItemPrivate *priv;
166
167 priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (object);
168
169 if (priv->day_view != NULL) {
170 g_object_unref (priv->day_view);
171 priv->day_view = NULL;
172 }
173
174 /* Chain up to parent's dispose() method. */
175 G_OBJECT_CLASS (e_day_view_time_item_parent_class)->dispose (object);
176 }
177
178 static void
179 day_view_time_item_finalize (GObject *object)
180 {
181 EDayViewTimeItem *time_item;
182
183 time_item = E_DAY_VIEW_TIME_ITEM (object);
184
185 calendar_config_remove_notification (
186 (CalendarConfigChangedFunc)
187 edvti_second_zone_changed_cb, time_item);
188
189 /* Chain up to parent's dispose() method. */
190 G_OBJECT_CLASS (e_day_view_time_item_parent_class)->finalize (object);
191 }
192
193 static void
194 e_day_view_time_item_class_init (EDayViewTimeItemClass *class)
195 {
196 GObjectClass *object_class;
197 GnomeCanvasItemClass *item_class;
198
199 g_type_class_add_private (class, sizeof (EDayViewTimeItemPrivate));
200
201 object_class = G_OBJECT_CLASS (class);
202 object_class->set_property = day_view_time_item_set_property;
203 object_class->get_property = day_view_time_item_get_property;
204 object_class->dispose = day_view_time_item_dispose;
205 object_class->finalize = day_view_time_item_finalize;
206
207 item_class = GNOME_CANVAS_ITEM_CLASS (class);
208 item_class->update = e_day_view_time_item_update;
209 item_class->draw = e_day_view_time_item_draw;
210 item_class->point = e_day_view_time_item_point;
211 item_class->event = e_day_view_time_item_event;
212
213 g_object_class_install_property (
214 object_class,
215 PROP_DAY_VIEW,
216 g_param_spec_object (
217 "day-view",
218 "Day View",
219 NULL,
220 E_TYPE_DAY_VIEW,
221 G_PARAM_READWRITE));
222 }
223
224 static void
225 e_day_view_time_item_init (EDayViewTimeItem *time_item)
226 {
227 gchar *last;
228
229 time_item->priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (time_item);
230
231 last = calendar_config_get_day_second_zone ();
232
233 if (last) {
234 if (*last)
235 time_item->priv->second_zone =
236 icaltimezone_get_builtin_timezone (last);
237 g_free (last);
238 }
239
240 calendar_config_add_notification_day_second_zone (
241 (CalendarConfigChangedFunc) edvti_second_zone_changed_cb,
242 time_item);
243 }
244
245 static void
246 e_day_view_time_item_update (GnomeCanvasItem *item,
247 const cairo_matrix_t *i2c,
248 gint flags)
249 {
250 if (GNOME_CANVAS_ITEM_CLASS (e_day_view_time_item_parent_class)->update)
251 (* GNOME_CANVAS_ITEM_CLASS (e_day_view_time_item_parent_class)->update) (item, i2c, flags);
252
253 /* The item covers the entire canvas area. */
254 item->x1 = 0;
255 item->y1 = 0;
256 item->x2 = INT_MAX;
257 item->y2 = INT_MAX;
258 }
259
260 /*
261 * DRAWING ROUTINES - functions to paint the canvas item.
262 */
263 static void
264 edvti_draw_zone (GnomeCanvasItem *canvas_item,
265 cairo_t *cr,
266 gint x,
267 gint y,
268 gint width,
269 gint height,
270 gint x_offset,
271 icaltimezone *use_zone)
272 {
273 EDayView *day_view;
274 EDayViewTimeItem *time_item;
275 ECalendarView *cal_view;
276 ECalModel *model;
277 GtkStyle *style;
278 const gchar *suffix;
279 gchar buffer[64], *midnight_day = NULL, *midnight_month = NULL;
280 gint time_divisions;
281 gint hour, display_hour, minute, row;
282 gint row_y, start_y, large_hour_y_offset, small_font_y_offset;
283 gint long_line_x1, long_line_x2, short_line_x1;
284 gint large_hour_x2, minute_x2;
285 gint hour_width, minute_width, suffix_width;
286 gint max_suffix_width, max_minute_or_suffix_width;
287 PangoLayout *layout;
288 PangoContext *context;
289 PangoFontDescription *small_font_desc;
290 PangoFontMetrics *large_font_metrics, *small_font_metrics;
291 GdkColor fg, dark;
292 GdkColor mb_color;
293
294 time_item = E_DAY_VIEW_TIME_ITEM (canvas_item);
295 day_view = e_day_view_time_item_get_day_view (time_item);
296 g_return_if_fail (day_view != NULL);
297
298 cal_view = E_CALENDAR_VIEW (day_view);
299 model = e_calendar_view_get_model (cal_view);
300 time_divisions = e_calendar_view_get_time_divisions (cal_view);
301
302 style = gtk_widget_get_style (GTK_WIDGET (day_view));
303 small_font_desc = style->font_desc;
304
305 context = gtk_widget_get_pango_context (GTK_WIDGET (day_view));
306 large_font_metrics = pango_context_get_metrics (
307 context, day_view->large_font_desc,
308 pango_context_get_language (context));
309 small_font_metrics = pango_context_get_metrics (
310 context, small_font_desc,
311 pango_context_get_language (context));
312
313 fg = style->fg[GTK_STATE_NORMAL];
314 dark = style->dark[GTK_STATE_NORMAL];
315
316 /* The start and end of the long horizontal line between hours. */
317 long_line_x1 =
318 (use_zone ? 0 : E_DVTMI_TIME_GRID_X_PAD) - x + x_offset;
319 long_line_x2 =
320 time_item->priv->column_width -
321 E_DVTMI_TIME_GRID_X_PAD - x -
322 (use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0) + x_offset;
323
324 if (time_divisions == 60) {
325 /* The right edge of the complete time string in 60-min
326 * divisions, e.g. "14:00" or "2 pm". */
327 minute_x2 = long_line_x2 - E_DVTMI_60_MIN_X_PAD;
328
329 /* These aren't used for 60-minute divisions, but we initialize
330 * them to keep gcc happy. */
331 short_line_x1 = 0;
332 large_hour_x2 = 0;
333 } else {
334 max_suffix_width = MAX (
335 day_view->am_string_width,
336 day_view->pm_string_width);
337
338 max_minute_or_suffix_width = MAX (
339 max_suffix_width,
340 day_view->max_minute_width);
341
342 /* The start of the short horizontal line between the periods
343 * within each hour. */
344 short_line_x1 = long_line_x2 - E_DVTMI_MIN_X_PAD * 2
345 - max_minute_or_suffix_width;
346
347 /* The right edge of the large hour string. */
348 large_hour_x2 = short_line_x1 - E_DVTMI_HOUR_R_PAD;
349
350 /* The right edge of the minute part of the time. */
351 minute_x2 = long_line_x2 - E_DVTMI_MIN_X_PAD;
352 }
353
354 /* Start with the first hour & minute shown in the EDayView. */
355 hour = day_view->first_hour_shown;
356 minute = day_view->first_minute_shown;
357
358 if (use_zone) {
359 /* shift time with a difference between
360 * local time and the other timezone */
361 icaltimezone *cal_zone;
362 struct icaltimetype tt;
363 gint diff;
364 struct tm mn;
365
366 cal_zone = e_calendar_view_get_timezone (
367 E_CALENDAR_VIEW (day_view));
368 tt = icaltime_from_timet_with_zone (
369 day_view->day_starts[0], 0, cal_zone);
370
371 /* diff is number of minutes */
372 diff =(icaltimezone_get_utc_offset (use_zone, &tt, NULL) -
373 icaltimezone_get_utc_offset (cal_zone, &tt, NULL)) / 60;
374
375 tt = icaltime_from_timet_with_zone (day_view->day_starts[0], 0, cal_zone);
376 tt.is_date = FALSE;
377 icaltime_set_timezone (&tt, cal_zone);
378 tt = icaltime_convert_to_zone (tt, use_zone);
379
380 if (diff != 0) {
381 /* shows the next midnight */
382 icaltime_adjust (&tt, 1, 0, 0, 0);
383 }
384
385 mn = icaltimetype_to_tm (&tt);
386
387 /* up to two characters/numbers */
388 e_utf8_strftime (buffer, sizeof (buffer), "%d", &mn);
389 midnight_day = g_strdup (buffer);
390 /* up to three characters, abbreviated month name */
391 e_utf8_strftime (buffer, sizeof (buffer), "%b", &mn);
392 midnight_month = g_strdup (buffer);
393
394 minute += (diff % 60);
395 hour += (diff / 60) + (minute / 60);
396
397 minute = minute % 60;
398 if (minute < 0) {
399 hour--;
400 minute += 60;
401 }
402
403 hour = (hour + 48) % 24;
404 }
405
406 /* The offset of the large hour string from the top of the row. */
407 large_hour_y_offset = E_DVTMI_LARGE_HOUR_Y_PAD;
408
409 /* The offset of the small time/minute string from top of row. */
410 small_font_y_offset = E_DVTMI_SMALL_FONT_Y_PAD;
411
412 /* Calculate the minimum y position of the first row we need to draw.
413 * This is normally one row height above the 0 position, but if we
414 * are using the large font we may have to go back a bit further. */
415 start_y = 0 - MAX (day_view->row_height,
416 (pango_font_metrics_get_ascent (large_font_metrics) +
417 pango_font_metrics_get_descent (large_font_metrics)) / PANGO_SCALE +
418 E_DVTMI_LARGE_HOUR_Y_PAD);
419
420 /* Draw the Marcus Bains Line first, so it appears under other elements. */
421 if (e_day_view_marcus_bains_get_show_line (day_view)) {
422 struct icaltimetype time_now;
423 gint marcus_bains_y;
424
425 cairo_save (cr);
426 gdk_cairo_set_source_color (
427 cr, &day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE]);
428
429 if (day_view->marcus_bains_time_bar_color &&
430 gdk_color_parse (
431 day_view->marcus_bains_time_bar_color,
432 &mb_color)) {
433 gdk_cairo_set_source_color (cr, &mb_color);
434 } else
435 mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE];
436
437 time_now = icaltime_current_time_with_zone (
438 e_calendar_view_get_timezone (
439 E_CALENDAR_VIEW (day_view)));
440 marcus_bains_y =
441 (time_now.hour * 60 + time_now.minute) *
442 day_view->row_height / time_divisions - y;
443 cairo_set_line_width (cr, 1.5);
444 cairo_move_to (
445 cr, long_line_x1 -
446 (use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0),
447 marcus_bains_y);
448 cairo_line_to (cr, long_line_x2, marcus_bains_y);
449 cairo_stroke (cr);
450 cairo_restore (cr);
451 } else {
452 mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE];
453
454 if (day_view->marcus_bains_time_bar_color &&
455 gdk_color_parse (
456 day_view->marcus_bains_time_bar_color,
457 &mb_color)) {
458 gdk_cairo_set_source_color (cr, &mb_color);
459 }
460 }
461
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;
468
469 show_midnight_date =
470 use_zone && hour == 0 &&
471 (minute == 0 || time_divisions == 60) &&
472 midnight_day && midnight_month;
473
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;
480 }
481
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);
488
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_color (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);
501
502 if (show_midnight_date) {
503 strcpy (buffer, midnight_day);
504 strcat (buffer, " ");
505 strcat (buffer, midnight_month);
506 } else if (e_cal_model_get_use_24_hour_format (model)) {
507 g_snprintf (
508 buffer, sizeof (buffer), "%i:%02i",
509 display_hour, minute);
510 } else {
511 g_snprintf (
512 buffer, sizeof (buffer), "%i %s",
513 display_hour, suffix);
514 }
515
516 cairo_save (cr);
517 if (show_midnight_date)
518 gdk_cairo_set_source_color (cr, &mb_color);
519 else
520 gdk_cairo_set_source_color (cr, &fg);
521 layout = pango_cairo_create_layout (cr);
522 pango_layout_set_text (layout, buffer, -1);
523 pango_layout_get_pixel_size (layout, &minute_width, NULL);
524 cairo_translate (
525 cr, minute_x2 - minute_width,
526 row_y + small_font_y_offset);
527 pango_cairo_update_layout (cr, layout);
528 pango_cairo_show_layout (cr, layout);
529 cairo_restore (cr);
530
531 g_object_unref (layout);
532 } else {
533 /* 5/10/15/30 minute intervals. */
534
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. */
539
540 cairo_save (cr);
541 gdk_cairo_set_source_color (cr, &dark);
542 if (show_midnight_date)
543 strcpy (buffer, midnight_day);
544 else
545 g_snprintf (
546 buffer, sizeof (buffer), "%i",
547 display_hour);
548
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);
554
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_color (cr, &fg);
560 layout = pango_cairo_create_layout (cr);
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);
572
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_color (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);
584 }
585
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 strcpy (buffer, midnight_month);
594 else if (minute == 0
595 && !e_cal_model_get_use_24_hour_format (model)) {
596 strcpy (buffer, suffix);
597 } else {
598 g_snprintf (
599 buffer, sizeof (buffer),
600 "%02i", minute);
601 }
602
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_color (cr, &fg);
608 layout = pango_cairo_create_layout (cr);
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);
620
621 g_object_unref (layout);
622 }
623 }
624
625 e_day_view_time_item_increment_time (
626 &hour, &minute,
627 time_divisions);
628 }
629
630 pango_font_metrics_unref (large_font_metrics);
631 pango_font_metrics_unref (small_font_metrics);
632
633 g_free (midnight_day);
634 g_free (midnight_month);
635 }
636
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)
644 {
645 EDayViewTimeItem *time_item;
646
647 time_item = E_DAY_VIEW_TIME_ITEM (canvas_item);
648 g_return_if_fail (time_item != NULL);
649
650 edvti_draw_zone (canvas_item, cr, x, y, width, height, 0, NULL);
651
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);
657 }
658
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)
666 {
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;
673 }
674 }
675
676 static GnomeCanvasItem *
677 e_day_view_time_item_point (GnomeCanvasItem *item,
678 gdouble x,
679 gdouble y,
680 gint cx,
681 gint cy)
682 {
683 return item;
684 }
685
686 static gint
687 e_day_view_time_item_event (GnomeCanvasItem *item,
688 GdkEvent *event)
689 {
690 EDayViewTimeItem *time_item;
691
692 time_item = E_DAY_VIEW_TIME_ITEM (item);
693
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;
701 }
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;
708
709 case GDK_MOTION_NOTIFY:
710 e_day_view_time_item_on_motion_notify (time_item, event);
711 break;
712
713 default:
714 break;
715 }
716
717 return FALSE;
718 }
719
720 static void
721 edvti_second_zone_changed_cb (GSettings *settings,
722 const gchar *key,
723 gpointer user_data)
724 {
725 EDayViewTimeItem *time_item = user_data;
726 EDayView *day_view;
727 gchar *location;
728
729 g_return_if_fail (user_data != NULL);
730 g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item));
731
732 location = calendar_config_get_day_second_zone ();
733 time_item->priv->second_zone =
734 location ? icaltimezone_get_builtin_timezone (location) : NULL;
735 g_free (location);
736
737 day_view = e_day_view_time_item_get_day_view (time_item);
738 gtk_widget_set_size_request (
739 day_view->time_canvas,
740 e_day_view_time_item_get_column_width (time_item), -1);
741 gtk_widget_queue_draw (day_view->time_canvas);
742 }
743
744 static void
745 edvti_on_select_zone (GtkWidget *item,
746 EDayViewTimeItem *time_item)
747 {
748 calendar_config_select_day_second_zone ();
749 }
750
751 static void
752 edvti_on_set_zone (GtkWidget *item,
753 EDayViewTimeItem *time_item)
754 {
755 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
756 return;
757
758 calendar_config_set_day_second_zone (
759 g_object_get_data (G_OBJECT (item), "timezone"));
760 }
761
762 static void
763 e_day_view_time_item_show_popup_menu (EDayViewTimeItem *time_item,
764 GdkEvent *event)
765 {
766 static gint divisions[] = { 60, 30, 15, 10, 5 };
767 EDayView *day_view;
768 ECalendarView *cal_view;
769 GtkWidget *menu, *item, *submenu;
770 gchar buffer[256];
771 GSList *group = NULL, *recent_zones, *s;
772 gint current_divisions, i;
773 icaltimezone *zone;
774
775 day_view = e_day_view_time_item_get_day_view (time_item);
776 g_return_if_fail (day_view != NULL);
777
778 cal_view = E_CALENDAR_VIEW (day_view);
779 current_divisions = e_calendar_view_get_time_divisions (cal_view);
780
781 menu = gtk_menu_new ();
782
783 /* Make sure the menu is destroyed when it disappears. */
784 g_signal_connect (
785 menu, "selection-done",
786 G_CALLBACK (gtk_widget_destroy), NULL);
787
788 for (i = 0; i < G_N_ELEMENTS (divisions); i++) {
789 g_snprintf (
790 buffer, sizeof (buffer),
791 /* Translators: %02i is the number of minutes;
792 * this is a context menu entry to change the
793 * length of the time division in the calendar
794 * day view, e.g. a day is displayed in
795 * 24 "60 minute divisions" or
796 * 48 "30 minute divisions". */
797 _("%02i minute divisions"), divisions[i]);
798 item = gtk_radio_menu_item_new_with_label (group, buffer);
799 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
800 gtk_widget_show (item);
801 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
802
803 if (current_divisions == divisions[i])
804 gtk_check_menu_item_set_active (
805 GTK_CHECK_MENU_ITEM (item), TRUE);
806
807 g_object_set_data (
808 G_OBJECT (item), "divisions",
809 GINT_TO_POINTER (divisions[i]));
810
811 g_signal_connect (
812 item, "toggled",
813 G_CALLBACK (e_day_view_time_item_on_set_divisions),
814 time_item);
815 }
816
817 item = gtk_separator_menu_item_new ();
818 gtk_widget_show (item);
819 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
820
821 submenu = gtk_menu_new ();
822 item = gtk_menu_item_new_with_label (_("Show the second time zone"));
823 gtk_widget_show (item);
824 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
825 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
826
827 zone = e_calendar_view_get_timezone (E_CALENDAR_VIEW (day_view));
828 if (zone)
829 item = gtk_menu_item_new_with_label (icaltimezone_get_display_name (zone));
830 else
831 item = gtk_menu_item_new_with_label ("---");
832 gtk_widget_set_sensitive (item, FALSE);
833 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
834
835 item = gtk_separator_menu_item_new ();
836 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
837
838 group = NULL;
839 item = gtk_radio_menu_item_new_with_label (group, C_("cal-second-zone", "None"));
840 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
841 if (!time_item->priv->second_zone)
842 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
843 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
844 g_signal_connect (
845 item, "toggled",
846 G_CALLBACK (edvti_on_set_zone), time_item);
847
848 recent_zones = calendar_config_get_day_second_zones ();
849 for (s = recent_zones; s != NULL; s = s->next) {
850 zone = icaltimezone_get_builtin_timezone (s->data);
851 if (!zone)
852 continue;
853
854 item = gtk_radio_menu_item_new_with_label (
855 group, icaltimezone_get_display_name (zone));
856 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
857 /* both comes from builtin, thus no problem to compare pointers */
858 if (zone == time_item->priv->second_zone)
859 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
860 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
861 g_object_set_data_full (
862 G_OBJECT (item), "timezone",
863 g_strdup (s->data), g_free);
864 g_signal_connect (
865 item, "toggled",
866 G_CALLBACK (edvti_on_set_zone), time_item);
867 }
868 calendar_config_free_day_second_zones (recent_zones);
869
870 item = gtk_separator_menu_item_new ();
871 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
872
873 item = gtk_menu_item_new_with_label (_("Select..."));
874 g_signal_connect (
875 item, "activate",
876 G_CALLBACK (edvti_on_select_zone), time_item);
877 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
878
879 gtk_widget_show_all (submenu);
880
881 gtk_menu_popup (
882 GTK_MENU (menu), NULL, NULL, NULL, NULL,
883 event->button.button, event->button.time);
884 }
885
886 static void
887 e_day_view_time_item_on_set_divisions (GtkWidget *item,
888 EDayViewTimeItem *time_item)
889 {
890 EDayView *day_view;
891 ECalendarView *cal_view;
892 gint divisions;
893
894 day_view = e_day_view_time_item_get_day_view (time_item);
895 g_return_if_fail (day_view != NULL);
896
897 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
898 return;
899
900 divisions = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "divisions"));
901
902 cal_view = E_CALENDAR_VIEW (day_view);
903 e_calendar_view_set_time_divisions (cal_view, divisions);
904 }
905
906 static void
907 e_day_view_time_item_on_button_press (EDayViewTimeItem *time_item,
908 GdkEvent *event)
909 {
910 GdkWindow *window;
911 EDayView *day_view;
912 GnomeCanvas *canvas;
913 gint row;
914
915 day_view = e_day_view_time_item_get_day_view (time_item);
916 g_return_if_fail (day_view != NULL);
917
918 canvas = GNOME_CANVAS_ITEM (time_item)->canvas;
919
920 row = e_day_view_time_item_convert_position_to_row (
921 time_item,
922 event->button.y);
923
924 if (row == -1)
925 return;
926
927 if (!gtk_widget_has_focus (GTK_WIDGET (day_view)))
928 gtk_widget_grab_focus (GTK_WIDGET (day_view));
929
930 window = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
931
932 if (gdk_pointer_grab (window, FALSE,
933 GDK_POINTER_MOTION_MASK
934 | GDK_BUTTON_RELEASE_MASK,
935 NULL, NULL, event->button.time) == 0) {
936 e_day_view_start_selection (day_view, -1, row);
937 time_item->priv->dragging_selection = TRUE;
938 }
939 }
940
941 static void
942 e_day_view_time_item_on_button_release (EDayViewTimeItem *time_item,
943 GdkEvent *event)
944 {
945 EDayView *day_view;
946
947 day_view = e_day_view_time_item_get_day_view (time_item);
948 g_return_if_fail (day_view != NULL);
949
950 if (time_item->priv->dragging_selection) {
951 gdk_pointer_ungrab (event->button.time);
952 e_day_view_finish_selection (day_view);
953 e_day_view_stop_auto_scroll (day_view);
954 }
955
956 time_item->priv->dragging_selection = FALSE;
957 }
958
959 static void
960 e_day_view_time_item_on_motion_notify (EDayViewTimeItem *time_item,
961 GdkEvent *event)
962 {
963 EDayView *day_view;
964 GnomeCanvas *canvas;
965 gdouble window_y;
966 gint y, row;
967
968 if (!time_item->priv->dragging_selection)
969 return;
970
971 day_view = e_day_view_time_item_get_day_view (time_item);
972 g_return_if_fail (day_view != NULL);
973
974 canvas = GNOME_CANVAS_ITEM (time_item)->canvas;
975
976 y = event->motion.y;
977 row = e_day_view_time_item_convert_position_to_row (time_item, y);
978
979 if (row != -1) {
980 gnome_canvas_world_to_window (
981 canvas, 0, event->motion.y,
982 NULL, &window_y);
983 e_day_view_update_selection (day_view, -1, row);
984 e_day_view_check_auto_scroll (day_view, -1, (gint) window_y);
985 }
986 }
987
988 /* Returns the row corresponding to the y position, or -1. */
989 static gint
990 e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *time_item,
991 gint y)
992 {
993 EDayView *day_view;
994 gint row;
995
996 day_view = e_day_view_time_item_get_day_view (time_item);
997 g_return_val_if_fail (day_view != NULL, -1);
998
999 if (y < 0)
1000 return -1;
1001
1002 row = y / day_view->row_height;
1003 if (row >= day_view->rows)
1004 return -1;
1005
1006 return row;
1007 }
1008
1009 EDayView *
1010 e_day_view_time_item_get_day_view (EDayViewTimeItem *time_item)
1011 {
1012 g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL);
1013
1014 return time_item->priv->day_view;
1015 }
1016
1017 void
1018 e_day_view_time_item_set_day_view (EDayViewTimeItem *time_item,
1019 EDayView *day_view)
1020 {
1021 g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item));
1022 g_return_if_fail (E_IS_DAY_VIEW (day_view));
1023
1024 if (time_item->priv->day_view == day_view)
1025 return;
1026
1027 if (time_item->priv->day_view != NULL)
1028 g_object_unref (time_item->priv->day_view);
1029
1030 time_item->priv->day_view = g_object_ref (day_view);
1031
1032 g_object_notify (G_OBJECT (time_item), "day-view");
1033 }
1034
1035 /* Returns the minimum width needed for the column, by adding up all the
1036 * maximum widths of the strings. The string widths are all calculated in
1037 * the style_set handlers of EDayView and EDayViewTimeCanvas. */
1038 gint
1039 e_day_view_time_item_get_column_width (EDayViewTimeItem *time_item)
1040 {
1041 EDayView *day_view;
1042 GtkStyle *style;
1043 gint digit, large_digit_width, max_large_digit_width = 0;
1044 gint max_suffix_width, max_minute_or_suffix_width;
1045 gint column_width_default, column_width_60_min_rows;
1046
1047 day_view = e_day_view_time_item_get_day_view (time_item);
1048 g_return_val_if_fail (day_view != NULL, 0);
1049
1050 style = gtk_widget_get_style (GTK_WIDGET (day_view));
1051 g_return_val_if_fail (style != NULL, 0);
1052
1053 /* Find the maximum width a digit can have. FIXME: We could use pango's
1054 * approximation function, but I worry it won't be precise enough. Also
1055 * it needs a language tag that I don't know where to get. */
1056 for (digit = '0'; digit <= '9'; digit++) {
1057 PangoLayout *layout;
1058 gchar digit_str[2];
1059
1060 digit_str[0] = digit;
1061 digit_str[1] = '\0';
1062
1063 layout = gtk_widget_create_pango_layout (GTK_WIDGET (day_view), digit_str);
1064 pango_layout_set_font_description (layout, day_view->large_font_desc);
1065 pango_layout_get_pixel_size (layout, &large_digit_width, NULL);
1066
1067 g_object_unref (layout);
1068
1069 max_large_digit_width = MAX (
1070 max_large_digit_width,
1071 large_digit_width);
1072 }
1073
1074 /* Calculate the width of each time column, using the maximum of the
1075 * default format with large hour numbers, and the 60-min divisions
1076 * format which uses small text. */
1077 max_suffix_width = MAX (
1078 day_view->am_string_width,
1079 day_view->pm_string_width);
1080
1081 max_minute_or_suffix_width = MAX (
1082 max_suffix_width,
1083 day_view->max_minute_width);
1084
1085 column_width_default = max_large_digit_width * 2
1086 + max_minute_or_suffix_width
1087 + E_DVTMI_MIN_X_PAD * 2
1088 + E_DVTMI_HOUR_L_PAD
1089 + E_DVTMI_HOUR_R_PAD
1090 + E_DVTMI_TIME_GRID_X_PAD * 2;
1091
1092 column_width_60_min_rows = day_view->max_small_hour_width
1093 + day_view->colon_width
1094 + max_minute_or_suffix_width
1095 + E_DVTMI_60_MIN_X_PAD * 2
1096 + E_DVTMI_TIME_GRID_X_PAD * 2;
1097
1098 time_item->priv->column_width =
1099 MAX (column_width_default, column_width_60_min_rows);
1100
1101 if (time_item->priv->second_zone)
1102 return (2 * time_item->priv->column_width) -
1103 E_DVTMI_TIME_GRID_X_PAD;
1104
1105 return time_item->priv->column_width;
1106 }
1107
1108 icaltimezone *
1109 e_day_view_time_item_get_second_zone (EDayViewTimeItem *time_item)
1110 {
1111 g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL);
1112
1113 return time_item->priv->second_zone;
1114 }