Updated Traditional Chinese translation(Hong Kong and Taiwan)
[evolution.git] / calendar / gui / e-day-view-time-item.c
blob35a8e3ebda2da6b7117c53944b75b829dc937f5c
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
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.
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.
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/>
18 * Authors:
19 * Damon Chaplin <damon@ximian.com>
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
28 #include <string.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31 #include "e-day-view-time-item.h"
32 #include "calendar-config.h"
33 #include <libecal/e-cal-time-util.h>
34 #include <widgets/e-timezone-dialog/e-timezone-dialog.h>
35 #include <libedataserver/e-data-server-util.h>
37 /* The spacing between items in the time column. GRID_X_PAD is the space down
38 either side of the column, i.e. outside the main horizontal grid lines.
39 HOUR_L_PAD & HOUR_R_PAD are the spaces on the left & right side of the
40 big hour number (this is inside the horizontal grid lines).
41 MIN_X_PAD is the spacing either side of the minute number. The smaller
42 horizontal grid lines match with this.
43 60_MIN_X_PAD is the space either side of the HH:MM display used when
44 we are displaying 60 mins per row (inside the main grid lines).
45 LARGE_HOUR_Y_PAD is the offset of the large hour string from the top of the
46 row.
47 SMALL_FONT_Y_PAD is the offset of the small time/minute string from the top
48 of the row. */
49 #define E_DVTMI_TIME_GRID_X_PAD 4
50 #define E_DVTMI_HOUR_L_PAD 4
51 #define E_DVTMI_HOUR_R_PAD 2
52 #define E_DVTMI_MIN_X_PAD 2
53 #define E_DVTMI_60_MIN_X_PAD 4
54 #define E_DVTMI_LARGE_HOUR_Y_PAD 1
55 #define E_DVTMI_SMALL_FONT_Y_PAD 1
57 #define E_DAY_VIEW_TIME_ITEM_GET_PRIVATE(obj) \
58 (G_TYPE_INSTANCE_GET_PRIVATE \
59 ((obj), E_TYPE_DAY_VIEW_TIME_ITEM, EDayViewTimeItemPrivate))
61 struct _EDayViewTimeItemPrivate {
62 /* The parent EDayView widget. */
63 EDayView *day_view;
65 /* The width of the time column. */
66 gint column_width;
68 /* TRUE if we are currently dragging the selection times. */
69 gboolean dragging_selection;
71 /* The second timezone if shown, or else NULL. */
72 guint second_zone_changed_id;
73 icaltimezone *second_zone;
76 static void e_day_view_time_item_update (GnomeCanvasItem *item,
77 const cairo_matrix_t *i2c,
78 gint flags);
79 static void e_day_view_time_item_draw (GnomeCanvasItem *item,
80 GdkDrawable *drawable,
81 gint x, gint y,
82 gint width, gint height);
83 static GnomeCanvasItem *e_day_view_time_item_point (GnomeCanvasItem *item,
84 double x, double y,
85 gint cx, gint cy);
86 static gint e_day_view_time_item_event (GnomeCanvasItem *item,
87 GdkEvent *event);
88 static void e_day_view_time_item_increment_time (gint *hour,
89 gint *minute,
90 gint time_divisions);
91 static void e_day_view_time_item_show_popup_menu (EDayViewTimeItem *time_item,
92 GdkEvent *event);
93 static void e_day_view_time_item_on_set_divisions (GtkWidget *item,
94 EDayViewTimeItem *time_item);
95 static void e_day_view_time_item_on_button_press (EDayViewTimeItem *time_item,
96 GdkEvent *event);
97 static void e_day_view_time_item_on_button_release (EDayViewTimeItem *time_item,
98 GdkEvent *event);
99 static void e_day_view_time_item_on_motion_notify (EDayViewTimeItem *time_item,
100 GdkEvent *event);
101 static gint e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *time_item,
102 gint y);
104 static void edvti_second_zone_changed_cb (GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data);
106 enum {
107 PROP_0,
108 PROP_DAY_VIEW
111 static gpointer parent_class;
113 static void
114 day_view_time_item_set_property (GObject *object,
115 guint property_id,
116 const GValue *value,
117 GParamSpec *pspec)
119 switch (property_id) {
120 case PROP_DAY_VIEW:
121 e_day_view_time_item_set_day_view (
122 E_DAY_VIEW_TIME_ITEM (object),
123 g_value_get_object (value));
124 return;
127 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
130 static void
131 day_view_time_item_get_property (GObject *object,
132 guint property_id,
133 GValue *value,
134 GParamSpec *pspec)
136 switch (property_id) {
137 case PROP_DAY_VIEW:
138 g_value_set_object (
139 value, e_day_view_time_item_get_day_view (
140 E_DAY_VIEW_TIME_ITEM (object)));
141 return;
144 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
147 static void
148 day_view_time_item_dispose (GObject *object)
150 EDayViewTimeItemPrivate *priv;
152 priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (object);
154 if (priv->day_view != NULL) {
155 g_object_unref (priv->day_view);
156 priv->day_view = NULL;
159 /* Chain up to parent's dispose() method. */
160 G_OBJECT_CLASS (parent_class)->dispose (object);
163 static void
164 day_view_time_item_finalize (GObject *object)
166 EDayViewTimeItem *time_item;
168 time_item = E_DAY_VIEW_TIME_ITEM (object);
170 if (time_item->priv->second_zone_changed_id)
171 calendar_config_remove_notification (time_item->priv->second_zone_changed_id);
172 time_item->priv->second_zone_changed_id = 0;
174 /* Chain up to parent's dispose() method. */
175 G_OBJECT_CLASS (parent_class)->finalize (object);
178 static void
179 day_view_time_item_class_init (EDayViewTimeItemClass *class)
181 GObjectClass *object_class;
182 GnomeCanvasItemClass *item_class;
184 parent_class = g_type_class_peek_parent (class);
185 g_type_class_add_private (class, sizeof (EDayViewTimeItemPrivate));
187 object_class = G_OBJECT_CLASS (class);
188 object_class->set_property = day_view_time_item_set_property;
189 object_class->get_property = day_view_time_item_get_property;
190 object_class->dispose = day_view_time_item_dispose;
191 object_class->finalize = day_view_time_item_finalize;
193 item_class = GNOME_CANVAS_ITEM_CLASS (class);
194 item_class->update = e_day_view_time_item_update;
195 item_class->draw = e_day_view_time_item_draw;
196 item_class->point = e_day_view_time_item_point;
197 item_class->event = e_day_view_time_item_event;
199 g_object_class_install_property (
200 object_class,
201 PROP_DAY_VIEW,
202 g_param_spec_object (
203 "day-view",
204 "Day View",
205 NULL,
206 E_TYPE_DAY_VIEW,
207 G_PARAM_READWRITE));
210 static void
211 day_view_time_item_init (EDayViewTimeItem *time_item)
213 gchar *last;
215 time_item->priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (time_item);
217 time_item->priv->dragging_selection = FALSE;
218 time_item->priv->second_zone = NULL;
220 last = calendar_config_get_day_second_zone ();
222 if (last) {
223 if (*last)
224 time_item->priv->second_zone = icaltimezone_get_builtin_timezone (last);
225 g_free (last);
228 time_item->priv->second_zone_changed_id = calendar_config_add_notification_day_second_zone (edvti_second_zone_changed_cb, time_item);
231 GType
232 e_day_view_time_item_get_type (void)
234 static GType type = 0;
236 if (G_UNLIKELY (type == 0)) {
237 const GTypeInfo type_info = {
238 sizeof (EDayViewTimeItemClass),
239 (GBaseInitFunc) NULL,
240 (GBaseFinalizeFunc) NULL,
241 (GClassInitFunc) day_view_time_item_class_init,
242 (GClassFinalizeFunc) NULL,
243 NULL, /* class_data */
244 sizeof (EDayViewTimeItem),
245 0, /* n_preallocs */
246 (GInstanceInitFunc) day_view_time_item_init,
247 NULL /* value_table */
250 type = g_type_register_static (
251 GNOME_TYPE_CANVAS_ITEM, "EDayViewTimeItem",
252 &type_info, 0);
255 return type;
258 static void
259 e_day_view_time_item_update (GnomeCanvasItem *item,
260 const cairo_matrix_t *i2c,
261 gint flags)
263 if (GNOME_CANVAS_ITEM_CLASS (parent_class)->update)
264 (* GNOME_CANVAS_ITEM_CLASS (parent_class)->update) (item, i2c, flags);
266 /* The item covers the entire canvas area. */
267 item->x1 = 0;
268 item->y1 = 0;
269 item->x2 = INT_MAX;
270 item->y2 = INT_MAX;
274 * DRAWING ROUTINES - functions to paint the canvas item.
276 static void
277 edvti_draw_zone (GnomeCanvasItem *canvas_item,
278 GdkDrawable *drawable,
279 gint x,
280 gint y,
281 gint width,
282 gint height,
283 gint x_offset,
284 icaltimezone *use_zone)
286 EDayView *day_view;
287 EDayViewTimeItem *time_item;
288 ECalendarView *cal_view;
289 ECalModel *model;
290 GtkStyle *style;
291 const gchar *suffix;
292 gchar buffer[64], *midnight_day = NULL, *midnight_month = NULL;
293 gint time_divisions;
294 gint hour, display_hour, minute, row;
295 gint row_y, start_y, large_hour_y_offset, small_font_y_offset;
296 gint long_line_x1, long_line_x2, short_line_x1;
297 gint large_hour_x2, minute_x2;
298 gint hour_width, minute_width, suffix_width;
299 gint max_suffix_width, max_minute_or_suffix_width;
300 PangoLayout *layout;
301 PangoContext *context;
302 PangoFontDescription *small_font_desc;
303 PangoFontMetrics *large_font_metrics, *small_font_metrics;
304 cairo_t *cr;
305 GdkColor fg, dark;
306 GdkColor mb_color;
308 cr = gdk_cairo_create (drawable);
310 time_item = E_DAY_VIEW_TIME_ITEM (canvas_item);
311 day_view = e_day_view_time_item_get_day_view (time_item);
312 g_return_if_fail (day_view != NULL);
314 cal_view = E_CALENDAR_VIEW (day_view);
315 model = e_calendar_view_get_model (cal_view);
316 time_divisions = e_calendar_view_get_time_divisions (cal_view);
318 style = gtk_widget_get_style (GTK_WIDGET (day_view));
319 small_font_desc = style->font_desc;
321 context = gtk_widget_get_pango_context (GTK_WIDGET (day_view));
322 large_font_metrics = pango_context_get_metrics (context, day_view->large_font_desc,
323 pango_context_get_language (context));
324 small_font_metrics = pango_context_get_metrics (context, small_font_desc,
325 pango_context_get_language (context));
327 fg = style->fg[GTK_STATE_NORMAL];
328 dark = style->dark[GTK_STATE_NORMAL];
330 /* The start and end of the long horizontal line between hours. */
331 long_line_x1 = (use_zone ? 0 : E_DVTMI_TIME_GRID_X_PAD) - x + x_offset;
332 long_line_x2 = time_item->priv->column_width - E_DVTMI_TIME_GRID_X_PAD - x - (use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0) + x_offset;
334 if (time_divisions == 60) {
335 /* The right edge of the complete time string in 60-min
336 divisions, e.g. "14:00" or "2 pm". */
337 minute_x2 = long_line_x2 - E_DVTMI_60_MIN_X_PAD;
339 /* These aren't used for 60-minute divisions, but we initialize
340 them to keep gcc happy. */
341 short_line_x1 = 0;
342 large_hour_x2 = 0;
343 } else {
344 max_suffix_width = MAX (day_view->am_string_width,
345 day_view->pm_string_width);
347 max_minute_or_suffix_width = MAX (max_suffix_width,
348 day_view->max_minute_width);
350 /* The start of the short horizontal line between the periods
351 within each hour. */
352 short_line_x1 = long_line_x2 - E_DVTMI_MIN_X_PAD * 2
353 - max_minute_or_suffix_width;
355 /* The right edge of the large hour string. */
356 large_hour_x2 = short_line_x1 - E_DVTMI_HOUR_R_PAD;
358 /* The right edge of the minute part of the time. */
359 minute_x2 = long_line_x2 - E_DVTMI_MIN_X_PAD;
362 /* Start with the first hour & minute shown in the EDayView. */
363 hour = day_view->first_hour_shown;
364 minute = day_view->first_minute_shown;
366 if (use_zone) {
367 /* shift time with a difference between local time and the other timezone */
368 icaltimezone *cal_zone = e_calendar_view_get_timezone (E_CALENDAR_VIEW (day_view));
369 struct icaltimetype tt;
370 gint diff;
371 struct tm mn;
373 tt = icaltime_from_timet_with_zone (day_view->day_starts[0], 0, cal_zone);
375 /* diff is number of minutes */
376 diff = (icaltimezone_get_utc_offset (use_zone, &tt, NULL) -
377 icaltimezone_get_utc_offset (cal_zone, &tt, NULL)
378 ) / 60;
380 tt = icaltime_from_timet_with_zone (day_view->day_starts[0], 0, cal_zone);
381 tt.is_date = FALSE;
382 icaltime_set_timezone (&tt, cal_zone);
383 tt = icaltime_convert_to_zone (tt, use_zone);
385 if (diff != 0) {
386 /* shows the next midnight */
387 icaltime_adjust (&tt, 1, 0, 0, 0);
390 mn = icaltimetype_to_tm (&tt);
392 /* up to two characters/numbers */
393 e_utf8_strftime (buffer, sizeof (buffer), "%d", &mn);
394 midnight_day = g_strdup (buffer);
395 /* up to three characters, abbreviated month name */
396 e_utf8_strftime (buffer, sizeof (buffer), "%b", &mn);
397 midnight_month = g_strdup (buffer);
399 minute += (diff % 60);
400 hour += (diff / 60) + (minute / 60);
402 minute = minute % 60;
403 if (minute < 0) {
404 hour--;
405 minute += 60;
408 hour = (hour + 48) % 24;
411 /* The offset of the large hour string from the top of the row. */
412 large_hour_y_offset = E_DVTMI_LARGE_HOUR_Y_PAD;
414 /* The offset of the small time/minute string from top of row. */
415 small_font_y_offset = E_DVTMI_SMALL_FONT_Y_PAD;
417 /* Calculate the minimum y position of the first row we need to draw.
418 This is normally one row height above the 0 position, but if we
419 are using the large font we may have to go back a bit further. */
420 start_y = 0 - MAX (day_view->row_height,
421 (pango_font_metrics_get_ascent (large_font_metrics) +
422 pango_font_metrics_get_descent (large_font_metrics)) / PANGO_SCALE +
423 E_DVTMI_LARGE_HOUR_Y_PAD);
425 /* Draw the Marcus Bains Line first, so it appears under other elements. */
426 if (e_day_view_marcus_bains_get_show_line (day_view)) {
427 struct icaltimetype time_now;
428 gint marcus_bains_y;
430 cairo_save (cr);
431 gdk_cairo_set_source_color (cr, &day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE]);
433 if (day_view->marcus_bains_time_bar_color && gdk_color_parse (day_view->marcus_bains_time_bar_color, &mb_color)) {
434 GdkColormap *colormap;
436 colormap = gtk_widget_get_colormap (GTK_WIDGET (day_view));
437 if (gdk_colormap_alloc_color (colormap, &mb_color, TRUE, TRUE)) {
438 gdk_cairo_set_source_color (cr, &mb_color);
440 } else
441 mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE];
443 time_now = icaltime_current_time_with_zone (e_calendar_view_get_timezone (E_CALENDAR_VIEW (day_view)));
444 marcus_bains_y = (time_now.hour * 60 + time_now.minute) * day_view->row_height / time_divisions - y;
445 cairo_set_line_width (cr, 1.5);
446 cairo_move_to (cr, long_line_x1 - (use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0), marcus_bains_y);
447 cairo_line_to (cr, long_line_x2, marcus_bains_y);
448 cairo_stroke (cr);
449 cairo_restore (cr);
450 } else {
451 mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE];
453 if (day_view->marcus_bains_time_bar_color && gdk_color_parse (day_view->marcus_bains_time_bar_color, &mb_color)) {
454 GdkColormap *colormap;
456 colormap = gtk_widget_get_colormap (GTK_WIDGET (day_view));
457 if (gdk_colormap_alloc_color (colormap, &mb_color, TRUE, TRUE)) {
458 gdk_cairo_set_source_color (cr, &mb_color);
463 /* Step through each row, drawing the times and the horizontal lines
464 between them. */
465 for (row = 0, row_y = 0 - y;
466 row < day_view->rows && row_y < height;
467 row++, row_y += day_view->row_height) {
468 gboolean show_midnight_date = use_zone && hour == 0 && (minute == 0 || time_divisions == 60) && midnight_day && midnight_month;
470 /* If the row is above the first row we want to draw just
471 increment the time and skip to the next row. */
472 if (row_y < start_y) {
473 e_day_view_time_item_increment_time (&hour, &minute,
474 time_divisions);
475 continue;
478 /* Calculate the actual hour number to display. For 12-hour
479 format we convert 0-23 to 12-11am/12-11pm. */
480 e_day_view_convert_time_to_display (day_view, hour,
481 &display_hour,
482 &suffix, &suffix_width);
484 if (time_divisions == 60) {
485 /* 60 minute intervals - draw a long horizontal line
486 between hours and display as one long string,
487 e.g. "14:00" or "2 pm". */
488 cairo_save (cr);
489 gdk_cairo_set_source_color (cr, &dark);
490 cairo_save (cr);
491 cairo_set_line_width (cr, 0.7);
492 cairo_move_to (cr, long_line_x1, row_y);
493 cairo_line_to (cr, long_line_x2, row_y);
494 cairo_stroke (cr);
495 cairo_restore (cr);
497 if (show_midnight_date) {
498 strcpy (buffer, midnight_day);
499 strcat (buffer, " ");
500 strcat (buffer, midnight_month);
501 } else if (e_cal_model_get_use_24_hour_format (model)) {
502 g_snprintf (buffer, sizeof (buffer), "%i:%02i",
503 display_hour, minute);
504 } else {
505 g_snprintf (buffer, sizeof (buffer), "%i %s",
506 display_hour, suffix);
509 cairo_save (cr);
510 if (show_midnight_date)
511 gdk_cairo_set_source_color (cr, &mb_color);
512 else
513 gdk_cairo_set_source_color (cr, &fg);
514 layout = pango_cairo_create_layout (cr);
515 pango_layout_set_text (layout, buffer, -1);
516 pango_layout_get_pixel_size (layout, &minute_width, NULL);
517 cairo_translate (cr, minute_x2 - minute_width, row_y + small_font_y_offset);
518 pango_cairo_update_layout (cr, layout);
519 pango_cairo_show_layout (cr, layout);
520 cairo_restore (cr);
522 g_object_unref (layout);
523 } else {
524 /* 5/10/15/30 minute intervals. */
526 if (minute == 0) {
527 /* On the hour - draw a long horizontal line
528 before the hour and display the hour in the
529 large font. */
531 cairo_save (cr);
532 gdk_cairo_set_source_color (cr, &dark);
533 if (show_midnight_date)
534 strcpy (buffer, midnight_day);
535 else
536 g_snprintf (buffer, sizeof (buffer), "%i",
537 display_hour);
539 cairo_set_line_width (cr, 0.7);
540 cairo_move_to (cr, long_line_x1, row_y);
541 cairo_line_to (cr, long_line_x2, row_y);
542 cairo_stroke (cr);
543 cairo_restore (cr);
545 cairo_save (cr);
546 if (show_midnight_date)
547 gdk_cairo_set_source_color (cr, &mb_color);
548 else
549 gdk_cairo_set_source_color (cr, &fg);
550 layout = pango_cairo_create_layout (cr);
551 pango_layout_set_text (layout, buffer, -1);
552 pango_layout_set_font_description (layout, day_view->large_font_desc);
553 pango_layout_get_pixel_size (layout, &hour_width, NULL);
554 cairo_translate (cr, large_hour_x2 - hour_width, row_y + large_hour_y_offset);
555 pango_cairo_update_layout (cr, layout);
556 pango_cairo_show_layout (cr, layout);
557 cairo_restore (cr);
559 g_object_unref (layout);
560 } else {
561 /* Within the hour - draw a short line before
562 the time. */
563 cairo_save (cr);
564 gdk_cairo_set_source_color (cr, &dark);
565 cairo_set_line_width (cr, 0.7);
566 cairo_move_to (cr, short_line_x1, row_y);
567 cairo_line_to (cr, long_line_x2, row_y);
568 cairo_stroke (cr);
569 cairo_restore (cr);
572 /* Normally we display the minute in each
573 interval, but when using 30-minute intervals
574 we don't display the '30'. */
575 if (time_divisions != 30 || minute != 30) {
576 /* In 12-hour format we display 'am' or 'pm'
577 instead of '00'. */
578 if (show_midnight_date)
579 strcpy (buffer, midnight_month);
580 else if (minute == 0
581 && !e_cal_model_get_use_24_hour_format (model)) {
582 strcpy (buffer, suffix);
583 } else {
584 g_snprintf (buffer, sizeof (buffer),
585 "%02i", minute);
588 cairo_save (cr);
589 if (show_midnight_date)
590 gdk_cairo_set_source_color (cr, &mb_color);
591 else
592 gdk_cairo_set_source_color (cr, &fg);
593 layout = pango_cairo_create_layout (cr);
594 pango_layout_set_text (layout, buffer, -1);
595 pango_layout_set_font_description (layout, day_view->small_font_desc);
596 pango_layout_get_pixel_size (layout, &minute_width, NULL);
597 cairo_translate (cr, minute_x2 - minute_width, row_y + small_font_y_offset);
598 pango_cairo_update_layout (cr, layout);
599 pango_cairo_show_layout (cr, layout);
600 cairo_restore (cr);
602 g_object_unref (layout);
606 e_day_view_time_item_increment_time (&hour, &minute,
607 time_divisions);
610 pango_font_metrics_unref (large_font_metrics);
611 pango_font_metrics_unref (small_font_metrics);
612 cairo_destroy (cr);
614 g_free (midnight_day);
615 g_free (midnight_month);
618 static void
619 e_day_view_time_item_draw (GnomeCanvasItem *canvas_item,
620 GdkDrawable *drawable,
621 gint x,
622 gint y,
623 gint width,
624 gint height)
626 EDayViewTimeItem *time_item;
628 time_item = E_DAY_VIEW_TIME_ITEM (canvas_item);
629 g_return_if_fail (time_item != NULL);
631 edvti_draw_zone (canvas_item, drawable, x, y, width, height, 0, NULL);
633 if (time_item->priv->second_zone)
634 edvti_draw_zone (canvas_item, drawable, x, y, width, height, time_item->priv->column_width, time_item->priv->second_zone);
637 /* Increment the time by the 5/10/15/30/60 minute interval.
638 Note that time_divisions is never > 60, so we never have to
639 worry about adding more than 60 minutes. */
640 static void
641 e_day_view_time_item_increment_time (gint *hour,
642 gint *minute,
643 gint time_divisions)
645 *minute += time_divisions;
646 if (*minute >= 60) {
647 *minute -= 60;
648 /* Currently we never wrap around to the next day, but
649 we may do if we display extra timezones. */
650 *hour = (*hour + 1) % 24;
654 static GnomeCanvasItem *
655 e_day_view_time_item_point (GnomeCanvasItem *item, double x, double y,
656 gint cx, gint cy)
658 return item;
661 static gint
662 e_day_view_time_item_event (GnomeCanvasItem *item,
663 GdkEvent *event)
665 EDayViewTimeItem *time_item;
667 time_item = E_DAY_VIEW_TIME_ITEM (item);
669 switch (event->type) {
670 case GDK_BUTTON_PRESS:
671 if (event->button.button == 1) {
672 e_day_view_time_item_on_button_press (time_item, event);
673 } else if (event->button.button == 3) {
674 e_day_view_time_item_show_popup_menu (time_item, event);
675 return TRUE;
677 break;
678 case GDK_BUTTON_RELEASE:
679 if (event->button.button == 1)
680 e_day_view_time_item_on_button_release (time_item,
681 event);
682 break;
684 case GDK_MOTION_NOTIFY:
685 e_day_view_time_item_on_motion_notify (time_item, event);
686 break;
688 default:
689 break;
692 return FALSE;
695 static void
696 edvti_second_zone_changed_cb (GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer user_data)
698 EDayViewTimeItem *time_item = user_data;
699 EDayView *day_view;
700 gchar *location;
702 g_return_if_fail (user_data != NULL);
703 g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item));
705 location = calendar_config_get_day_second_zone ();
706 time_item->priv->second_zone = location ? icaltimezone_get_builtin_timezone (location) : NULL;
707 g_free (location);
709 day_view = e_day_view_time_item_get_day_view (time_item);
710 gtk_widget_set_size_request (day_view->time_canvas, e_day_view_time_item_get_column_width (time_item), -1);
711 gtk_widget_queue_draw (day_view->time_canvas);
714 static void
715 edvti_on_select_zone (GtkWidget *item, EDayViewTimeItem *time_item)
717 calendar_config_select_day_second_zone ();
720 static void
721 edvti_on_set_zone (GtkWidget *item, EDayViewTimeItem *time_item)
723 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
724 return;
726 calendar_config_set_day_second_zone (g_object_get_data (G_OBJECT (item), "timezone"));
729 static void
730 e_day_view_time_item_show_popup_menu (EDayViewTimeItem *time_item,
731 GdkEvent *event)
733 static gint divisions[] = { 60, 30, 15, 10, 5 };
734 EDayView *day_view;
735 ECalendarView *cal_view;
736 GtkWidget *menu, *item, *submenu;
737 gchar buffer[256];
738 GSList *group = NULL, *recent_zones, *s;
739 gint current_divisions, i;
740 icaltimezone *zone;
742 day_view = e_day_view_time_item_get_day_view (time_item);
743 g_return_if_fail (day_view != NULL);
745 cal_view = E_CALENDAR_VIEW (day_view);
746 current_divisions = e_calendar_view_get_time_divisions (cal_view);
748 menu = gtk_menu_new ();
750 /* Make sure the menu is destroyed when it disappears. */
751 g_signal_connect (
752 menu, "selection-done",
753 G_CALLBACK (gtk_widget_destroy), NULL);
755 for (i = 0; i < G_N_ELEMENTS (divisions); i++) {
756 g_snprintf (buffer, sizeof (buffer),
757 /* TO TRANSLATORS: %02i is the number of minutes; this is a context menu entry
758 * to change the length of the time division in the calendar day view, e.g.
759 * a day is displayed in 24 "60 minute divisions" or 48 "30 minute divisions"
761 _("%02i minute divisions"), divisions[i]);
762 item = gtk_radio_menu_item_new_with_label (group, buffer);
763 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
764 gtk_widget_show (item);
765 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
767 if (current_divisions == divisions[i])
768 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
770 g_object_set_data (G_OBJECT (item), "divisions",
771 GINT_TO_POINTER (divisions[i]));
773 g_signal_connect (item, "toggled",
774 G_CALLBACK (e_day_view_time_item_on_set_divisions), time_item);
777 item = gtk_separator_menu_item_new ();
778 gtk_widget_show (item);
779 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
781 submenu = gtk_menu_new ();
782 item = gtk_menu_item_new_with_label (_("Show the second time zone"));
783 gtk_widget_show (item);
784 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
785 gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
787 zone = e_calendar_view_get_timezone (E_CALENDAR_VIEW (day_view));
788 if (zone)
789 item = gtk_menu_item_new_with_label (icaltimezone_get_display_name (zone));
790 else
791 item = gtk_menu_item_new_with_label ("---");
792 gtk_widget_set_sensitive (item, FALSE);
793 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
795 item = gtk_separator_menu_item_new ();
796 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
798 group = NULL;
799 item = gtk_radio_menu_item_new_with_label (group, C_("cal-second-zone", "None"));
800 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
801 if (!time_item->priv->second_zone)
802 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
803 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
804 g_signal_connect (item, "toggled", G_CALLBACK (edvti_on_set_zone), time_item);
806 recent_zones = calendar_config_get_day_second_zones ();
807 for (s = recent_zones; s != NULL; s = s->next) {
808 zone = icaltimezone_get_builtin_timezone (s->data);
809 if (!zone)
810 continue;
812 item = gtk_radio_menu_item_new_with_label (group, icaltimezone_get_display_name (zone));
813 group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
814 /* both comes from builtin, thus no problem to compare pointers */
815 if (zone == time_item->priv->second_zone)
816 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
817 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
818 g_object_set_data_full (G_OBJECT (item), "timezone", g_strdup (s->data), g_free);
819 g_signal_connect (item, "toggled", G_CALLBACK (edvti_on_set_zone), time_item);
821 calendar_config_free_day_second_zones (recent_zones);
823 item = gtk_separator_menu_item_new ();
824 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
826 item = gtk_menu_item_new_with_label (_("Select..."));
827 g_signal_connect (item, "activate", G_CALLBACK (edvti_on_select_zone), time_item);
828 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item);
830 gtk_widget_show_all (submenu);
832 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
833 event->button.button, event->button.time);
836 static void
837 e_day_view_time_item_on_set_divisions (GtkWidget *item,
838 EDayViewTimeItem *time_item)
840 EDayView *day_view;
841 ECalendarView *cal_view;
842 gint divisions;
844 day_view = e_day_view_time_item_get_day_view (time_item);
845 g_return_if_fail (day_view != NULL);
847 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
848 return;
850 divisions = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "divisions"));
852 cal_view = E_CALENDAR_VIEW (day_view);
853 e_calendar_view_set_time_divisions (cal_view, divisions);
856 static void
857 e_day_view_time_item_on_button_press (EDayViewTimeItem *time_item,
858 GdkEvent *event)
860 GdkWindow *window;
861 EDayView *day_view;
862 GnomeCanvas *canvas;
863 gint row;
865 day_view = e_day_view_time_item_get_day_view (time_item);
866 g_return_if_fail (day_view != NULL);
868 canvas = GNOME_CANVAS_ITEM (time_item)->canvas;
870 row = e_day_view_time_item_convert_position_to_row (time_item,
871 event->button.y);
873 if (row == -1)
874 return;
876 if (!gtk_widget_has_focus (GTK_WIDGET (day_view)))
877 gtk_widget_grab_focus (GTK_WIDGET (day_view));
879 window = gtk_layout_get_bin_window (GTK_LAYOUT (canvas));
881 if (gdk_pointer_grab (window, FALSE,
882 GDK_POINTER_MOTION_MASK
883 | GDK_BUTTON_RELEASE_MASK,
884 NULL, NULL, event->button.time) == 0) {
885 e_day_view_start_selection (day_view, -1, row);
886 time_item->priv->dragging_selection = TRUE;
890 static void
891 e_day_view_time_item_on_button_release (EDayViewTimeItem *time_item,
892 GdkEvent *event)
894 EDayView *day_view;
896 day_view = e_day_view_time_item_get_day_view (time_item);
897 g_return_if_fail (day_view != NULL);
899 if (time_item->priv->dragging_selection) {
900 gdk_pointer_ungrab (event->button.time);
901 e_day_view_finish_selection (day_view);
902 e_day_view_stop_auto_scroll (day_view);
905 time_item->priv->dragging_selection = FALSE;
908 static void
909 e_day_view_time_item_on_motion_notify (EDayViewTimeItem *time_item,
910 GdkEvent *event)
912 EDayView *day_view;
913 GnomeCanvas *canvas;
914 gdouble window_y;
915 gint y, row;
917 if (!time_item->priv->dragging_selection)
918 return;
920 day_view = e_day_view_time_item_get_day_view (time_item);
921 g_return_if_fail (day_view != NULL);
923 canvas = GNOME_CANVAS_ITEM (time_item)->canvas;
925 y = event->motion.y;
926 row = e_day_view_time_item_convert_position_to_row (time_item, y);
928 if (row != -1) {
929 gnome_canvas_world_to_window (canvas, 0, event->motion.y,
930 NULL, &window_y);
931 e_day_view_update_selection (day_view, -1, row);
932 e_day_view_check_auto_scroll (day_view, -1, (gint) window_y);
936 /* Returns the row corresponding to the y position, or -1. */
937 static gint
938 e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *time_item,
939 gint y)
941 EDayView *day_view;
942 gint row;
944 day_view = e_day_view_time_item_get_day_view (time_item);
945 g_return_val_if_fail (day_view != NULL, -1);
947 if (y < 0)
948 return -1;
950 row = y / day_view->row_height;
951 if (row >= day_view->rows)
952 return -1;
954 return row;
957 EDayView *
958 e_day_view_time_item_get_day_view (EDayViewTimeItem *time_item)
960 g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL);
962 return time_item->priv->day_view;
965 void
966 e_day_view_time_item_set_day_view (EDayViewTimeItem *time_item,
967 EDayView *day_view)
969 g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item));
970 g_return_if_fail (E_IS_DAY_VIEW (day_view));
972 if (time_item->priv->day_view != NULL)
973 g_object_unref (time_item->priv->day_view);
975 time_item->priv->day_view = g_object_ref (day_view);
977 g_object_notify (G_OBJECT (time_item), "day-view");
980 /* Returns the minimum width needed for the column, by adding up all the
981 maximum widths of the strings. The string widths are all calculated in
982 the style_set handlers of EDayView and EDayViewTimeCanvas. */
983 gint
984 e_day_view_time_item_get_column_width (EDayViewTimeItem *time_item)
986 EDayView *day_view;
987 GtkStyle *style;
988 gint digit, large_digit_width, max_large_digit_width = 0;
989 gint max_suffix_width, max_minute_or_suffix_width;
990 gint column_width_default, column_width_60_min_rows;
992 day_view = e_day_view_time_item_get_day_view (time_item);
993 g_return_val_if_fail (day_view != NULL, 0);
995 style = gtk_widget_get_style (GTK_WIDGET (day_view));
996 g_return_val_if_fail (style != NULL, 0);
998 /* Find the maximum width a digit can have. FIXME: We could use pango's
999 * approximation function, but I worry it won't be precise enough. Also
1000 * it needs a language tag that I don't know where to get. */
1001 for (digit = '0'; digit <= '9'; digit++) {
1002 PangoLayout *layout;
1003 gchar digit_str[2];
1005 digit_str[0] = digit;
1006 digit_str[1] = '\0';
1008 layout = gtk_widget_create_pango_layout (GTK_WIDGET (day_view), digit_str);
1009 pango_layout_set_font_description (layout, day_view->large_font_desc);
1010 pango_layout_get_pixel_size (layout, &large_digit_width, NULL);
1012 g_object_unref (layout);
1014 max_large_digit_width = MAX (max_large_digit_width,
1015 large_digit_width);
1018 /* Calculate the width of each time column, using the maximum of the
1019 default format with large hour numbers, and the 60-min divisions
1020 format which uses small text. */
1021 max_suffix_width = MAX (day_view->am_string_width,
1022 day_view->pm_string_width);
1024 max_minute_or_suffix_width = MAX (max_suffix_width,
1025 day_view->max_minute_width);
1027 column_width_default = max_large_digit_width * 2
1028 + max_minute_or_suffix_width
1029 + E_DVTMI_MIN_X_PAD * 2
1030 + E_DVTMI_HOUR_L_PAD
1031 + E_DVTMI_HOUR_R_PAD
1032 + E_DVTMI_TIME_GRID_X_PAD * 2;
1034 column_width_60_min_rows = day_view->max_small_hour_width
1035 + day_view->colon_width
1036 + max_minute_or_suffix_width
1037 + E_DVTMI_60_MIN_X_PAD * 2
1038 + E_DVTMI_TIME_GRID_X_PAD * 2;
1040 time_item->priv->column_width =
1041 MAX (column_width_default, column_width_60_min_rows);
1043 if (time_item->priv->second_zone)
1044 return (2 * time_item->priv->column_width) -
1045 E_DVTMI_TIME_GRID_X_PAD;
1047 return time_item->priv->column_width;
1050 icaltimezone *
1051 e_day_view_time_item_get_second_zone (EDayViewTimeItem *time_item)
1053 g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL);
1055 return time_item->priv->second_zone;