Bug 793125 - Crash due to popup menus left attached too long
[evolution.git] / src / calendar / gui / e-weekday-chooser.c
blobd7af6ab4298505e8b52a1b6be95a73d31d2e8dd9
1 /*
2 * e-weekday-chooser.c
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 #include "evolution-config.h"
20 #include <string.h>
22 #include <gtk/gtk.h>
23 #include <glib/gi18n-lib.h>
24 #include <gdk/gdkkeysyms.h>
26 #include <libgnomecanvas/libgnomecanvas.h>
27 #include <e-util/e-util.h>
29 #include "e-weekday-chooser.h"
31 #define PADDING 2
33 #define E_WEEKDAY_CHOOSER_GET_PRIVATE(obj) \
34 (G_TYPE_INSTANCE_GET_PRIVATE \
35 ((obj), E_TYPE_WEEKDAY_CHOOSER, EWeekdayChooserPrivate))
37 /* Private part of the EWeekdayChooser structure */
38 struct _EWeekdayChooserPrivate {
39 gboolean blocked_weekdays[8]; /* indexed by GDateWeekday */
40 gboolean selected_weekdays[8]; /* indexed by GDateWeekday */
42 /* Day that defines the start of the week. */
43 GDateWeekday week_start_day;
45 /* Current keyboard focus day */
46 GDateWeekday focus_day;
48 /* Metrics */
49 gint font_ascent, font_descent;
50 gint max_letter_width;
52 /* Components */
53 GnomeCanvasItem *boxes[7];
54 GnomeCanvasItem *labels[7];
57 enum {
58 PROP_0,
59 PROP_WEEK_START_DAY
62 enum {
63 CHANGED,
64 LAST_SIGNAL
67 static guint chooser_signals[LAST_SIGNAL];
69 G_DEFINE_TYPE_WITH_CODE (
70 EWeekdayChooser,
71 e_weekday_chooser,
72 GNOME_TYPE_CANVAS,
73 G_IMPLEMENT_INTERFACE (
74 E_TYPE_EXTENSIBLE, NULL))
76 static void
77 colorize_items (EWeekdayChooser *chooser)
79 GdkColor outline, focus_outline;
80 GdkColor fill, sel_fill;
81 GdkColor text_fill, sel_text_fill;
82 GDateWeekday weekday;
83 GtkWidget *widget;
84 gint ii;
86 widget = GTK_WIDGET (chooser);
88 e_utils_get_theme_color_color (widget, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &outline);
89 e_utils_get_theme_color_color (widget, "theme_bg_color", E_UTILS_DEFAULT_THEME_BG_COLOR, &focus_outline);
90 e_utils_get_theme_color_color (widget, "theme_base_color", E_UTILS_DEFAULT_THEME_BASE_COLOR, &fill);
91 e_utils_get_theme_color_color (widget, "theme_fg_color", E_UTILS_DEFAULT_THEME_FG_COLOR, &text_fill);
92 e_utils_get_theme_color_color (widget, "theme_selected_bg_color", E_UTILS_DEFAULT_THEME_SELECTED_BG_COLOR, &sel_fill);
93 e_utils_get_theme_color_color (widget, "theme_selected_fg_color", E_UTILS_DEFAULT_THEME_SELECTED_FG_COLOR, &sel_text_fill);
95 weekday = e_weekday_chooser_get_week_start_day (chooser);
97 for (ii = 0; ii < 7; ii++) {
98 GdkColor *f, *t, *o;
100 if (chooser->priv->selected_weekdays[weekday]) {
101 f = &sel_fill;
102 t = &sel_text_fill;
103 } else {
104 f = &fill;
105 t = &text_fill;
108 if (weekday == chooser->priv->focus_day)
109 o = &focus_outline;
110 else
111 o = &outline;
113 gnome_canvas_item_set (
114 chooser->priv->boxes[ii],
115 "fill_color_gdk", f,
116 "outline_color_gdk", o,
117 NULL);
119 gnome_canvas_item_set (
120 chooser->priv->labels[ii],
121 "fill_color_gdk", t,
122 NULL);
124 weekday = e_weekday_get_next (weekday);
128 static void
129 configure_items (EWeekdayChooser *chooser)
131 GtkAllocation allocation;
132 gint width, height;
133 gint box_width;
134 GDateWeekday weekday;
135 gint ii;
137 gtk_widget_get_allocation (GTK_WIDGET (chooser), &allocation);
139 width = allocation.width;
140 height = allocation.height;
142 box_width = (width - 1) / 7;
144 weekday = e_weekday_chooser_get_week_start_day (chooser);
146 for (ii = 0; ii < 7; ii++) {
147 gnome_canvas_item_set (
148 chooser->priv->boxes[ii],
149 "x1", (gdouble) (ii * box_width),
150 "y1", (gdouble) 0,
151 "x2", (gdouble) ((ii + 1) * box_width),
152 "y2", (gdouble) (height - 1),
153 "line_width", 0.0,
154 NULL);
156 gnome_canvas_item_set (
157 chooser->priv->labels[ii],
158 "text", e_get_weekday_name (weekday, TRUE),
159 "x", (gdouble) (ii * box_width) + PADDING,
160 "y", (gdouble) (1 + PADDING),
161 NULL);
163 weekday = e_weekday_get_next (weekday);
166 colorize_items (chooser);
169 static void
170 weekday_chooser_set_property (GObject *object,
171 guint property_id,
172 const GValue *value,
173 GParamSpec *pspec)
175 switch (property_id) {
176 case PROP_WEEK_START_DAY:
177 e_weekday_chooser_set_week_start_day (
178 E_WEEKDAY_CHOOSER (object),
179 g_value_get_enum (value));
180 return;
183 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
186 static void
187 weekday_chooser_get_property (GObject *object,
188 guint property_id,
189 GValue *value,
190 GParamSpec *pspec)
192 switch (property_id) {
193 case PROP_WEEK_START_DAY:
194 g_value_set_enum (
195 value,
196 e_weekday_chooser_get_week_start_day (
197 E_WEEKDAY_CHOOSER (object)));
198 return;
201 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
204 static void
205 weekday_chooser_constructed (GObject *object)
207 /* Chain up to parent's constructed() method. */
208 G_OBJECT_CLASS (e_weekday_chooser_parent_class)->constructed (object);
210 e_extensible_load_extensions (E_EXTENSIBLE (object));
213 static void
214 weekday_chooser_realize (GtkWidget *widget)
216 EWeekdayChooser *chooser;
218 chooser = E_WEEKDAY_CHOOSER (widget);
220 /* Chain up to parent's realize() method. */
221 GTK_WIDGET_CLASS (e_weekday_chooser_parent_class)->realize (widget);
223 configure_items (chooser);
226 static void
227 weekday_chooser_size_allocate (GtkWidget *widget,
228 GtkAllocation *allocation)
230 GtkWidgetClass *widget_class;
231 EWeekdayChooser *chooser;
233 chooser = E_WEEKDAY_CHOOSER (widget);
235 /* Chain up to parent's size_allocate() method. */
236 widget_class = GTK_WIDGET_CLASS (e_weekday_chooser_parent_class);
237 widget_class->size_allocate (widget, allocation);
239 gnome_canvas_set_scroll_region (
240 GNOME_CANVAS (chooser), 0, 0,
241 allocation->width, allocation->height);
243 configure_items (chooser);
246 static void
247 weekday_chooser_style_updated (GtkWidget *widget)
249 GtkWidgetClass *widget_class;
250 EWeekdayChooser *chooser;
251 EWeekdayChooserPrivate *priv;
252 gint max_width;
253 PangoContext *pango_context;
254 PangoFontMetrics *font_metrics;
255 PangoLayout *layout;
256 GDateWeekday weekday;
258 chooser = E_WEEKDAY_CHOOSER (widget);
259 priv = chooser->priv;
261 /* Set up Pango prerequisites */
262 pango_context = gtk_widget_get_pango_context (widget);
263 font_metrics = pango_context_get_metrics (
264 pango_context, NULL,
265 pango_context_get_language (pango_context));
266 layout = pango_layout_new (pango_context);
268 priv->font_ascent =
269 PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics));
270 priv->font_descent =
271 PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics));
273 max_width = 0;
275 for (weekday = G_DATE_MONDAY; weekday <= G_DATE_SUNDAY; weekday++) {
276 const gchar *name;
277 gint w;
279 name = e_get_weekday_name (weekday, TRUE);
280 pango_layout_set_text (layout, name, strlen (name));
281 pango_layout_get_pixel_size (layout, &w, NULL);
283 if (w > max_width)
284 max_width = w;
287 priv->max_letter_width = max_width;
289 configure_items (chooser);
290 g_object_unref (layout);
291 pango_font_metrics_unref (font_metrics);
293 /* Chain up to parent's style_updated() method. */
294 widget_class = GTK_WIDGET_CLASS (e_weekday_chooser_parent_class);
295 widget_class->style_updated (widget);
298 static void
299 weekday_chooser_get_preferred_height (GtkWidget *widget,
300 gint *minimum_height,
301 gint *natural_height)
303 EWeekdayChooser *chooser;
304 EWeekdayChooserPrivate *priv;
306 chooser = E_WEEKDAY_CHOOSER (widget);
307 priv = chooser->priv;
309 *minimum_height = *natural_height =
310 (priv->font_ascent + priv->font_descent + 2 * PADDING + 2);
313 static void
314 weekday_chooser_get_preferred_width (GtkWidget *widget,
315 gint *minimum_width,
316 gint *natural_width)
318 EWeekdayChooser *chooser;
319 EWeekdayChooserPrivate *priv;
321 chooser = E_WEEKDAY_CHOOSER (widget);
322 priv = chooser->priv;
324 *minimum_width = *natural_width =
325 (priv->max_letter_width + 2 * PADDING + 1) * 7 + 1;
328 static gboolean
329 weekday_chooser_focus (GtkWidget *widget,
330 GtkDirectionType direction)
332 EWeekdayChooser *chooser;
334 chooser = E_WEEKDAY_CHOOSER (widget);
336 if (!gtk_widget_get_can_focus (widget))
337 return FALSE;
339 if (gtk_widget_has_focus (widget)) {
340 chooser->priv->focus_day = G_DATE_BAD_WEEKDAY;
341 colorize_items (chooser);
342 return FALSE;
345 chooser->priv->focus_day = chooser->priv->week_start_day;
346 gnome_canvas_item_grab_focus (chooser->priv->boxes[0]);
348 colorize_items (chooser);
350 return TRUE;
353 static void
354 e_weekday_chooser_class_init (EWeekdayChooserClass *class)
356 GObjectClass *object_class;
357 GtkWidgetClass *widget_class;
359 g_type_class_add_private (class, sizeof (EWeekdayChooserPrivate));
361 object_class = G_OBJECT_CLASS (class);
362 object_class->set_property = weekday_chooser_set_property;
363 object_class->get_property = weekday_chooser_get_property;
364 object_class->constructed = weekday_chooser_constructed;
366 widget_class = GTK_WIDGET_CLASS (class);
367 widget_class->realize = weekday_chooser_realize;
368 widget_class->size_allocate = weekday_chooser_size_allocate;
369 widget_class->style_updated = weekday_chooser_style_updated;
370 widget_class->get_preferred_height = weekday_chooser_get_preferred_height;
371 widget_class->get_preferred_width = weekday_chooser_get_preferred_width;
372 widget_class->focus = weekday_chooser_focus;
374 g_object_class_install_property (
375 object_class,
376 PROP_WEEK_START_DAY,
377 g_param_spec_enum (
378 "week-start-day",
379 "Week Start Day",
380 NULL,
381 E_TYPE_DATE_WEEKDAY,
382 G_DATE_MONDAY,
383 G_PARAM_READWRITE |
384 G_PARAM_STATIC_STRINGS));
386 chooser_signals[CHANGED] = g_signal_new (
387 "changed",
388 G_TYPE_FROM_CLASS (class),
389 G_SIGNAL_RUN_FIRST,
390 G_STRUCT_OFFSET (EWeekdayChooserClass, changed),
391 NULL, NULL,
392 g_cclosure_marshal_VOID__VOID,
393 G_TYPE_NONE, 0);
396 static void
397 day_clicked (EWeekdayChooser *chooser,
398 GDateWeekday weekday)
400 gboolean selected;
402 if (chooser->priv->blocked_weekdays[weekday])
403 return;
405 selected = e_weekday_chooser_get_selected (chooser, weekday);
406 e_weekday_chooser_set_selected (chooser, weekday, !selected);
409 static gint
410 handle_key_press_event (EWeekdayChooser *chooser,
411 GdkEvent *event)
413 EWeekdayChooserPrivate *priv = chooser->priv;
414 guint keyval = event->key.keyval;
415 guint index;
417 if (chooser->priv->focus_day == G_DATE_BAD_WEEKDAY)
418 chooser->priv->focus_day = chooser->priv->week_start_day;
420 switch (keyval) {
421 case GDK_KEY_Up:
422 case GDK_KEY_Right:
423 chooser->priv->focus_day =
424 e_weekday_get_next (chooser->priv->focus_day);
425 break;
426 case GDK_KEY_Down:
427 case GDK_KEY_Left:
428 chooser->priv->focus_day =
429 e_weekday_get_prev (chooser->priv->focus_day);
430 break;
431 case GDK_KEY_space:
432 case GDK_KEY_Return:
433 case GDK_KEY_KP_Enter:
434 day_clicked (chooser, priv->focus_day);
435 return TRUE;
436 default:
437 return FALSE;
440 colorize_items (chooser);
442 index = e_weekday_get_days_between (
443 chooser->priv->week_start_day,
444 chooser->priv->focus_day);
446 gnome_canvas_item_grab_focus (chooser->priv->boxes[index]);
448 return TRUE;
451 /* Event handler for the day items */
452 static gboolean
453 day_event_cb (GnomeCanvasItem *item,
454 GdkEvent *event,
455 gpointer data)
457 EWeekdayChooser *chooser;
458 gint ii;
460 chooser = E_WEEKDAY_CHOOSER (data);
462 if (event->type == GDK_KEY_PRESS)
463 return handle_key_press_event (chooser, event);
465 if (!(event->type == GDK_BUTTON_PRESS && event->button.button == 1))
466 return FALSE;
468 /* Find which box was clicked */
470 for (ii = 0; ii < 7; ii++) {
471 if (chooser->priv->boxes[ii] == item)
472 break;
473 if (chooser->priv->labels[ii] == item)
474 break;
477 if (ii >= 7) {
478 g_warn_if_reached ();
479 return FALSE;
482 chooser->priv->focus_day = e_weekday_add_days (
483 chooser->priv->week_start_day, ii);
485 gnome_canvas_item_grab_focus (chooser->priv->boxes[ii]);
487 day_clicked (chooser, chooser->priv->focus_day);
489 return TRUE;
492 /* Creates the canvas items for the weekday chooser.
493 * The items are empty until they are configured elsewhere. */
494 static void
495 create_items (EWeekdayChooser *chooser)
497 GnomeCanvasGroup *parent;
498 gint ii;
500 parent = gnome_canvas_root (GNOME_CANVAS (chooser));
502 for (ii = 0; ii < 7; ii++) {
503 chooser->priv->boxes[ii] = gnome_canvas_item_new (
504 parent,
505 GNOME_TYPE_CANVAS_RECT,
506 NULL);
507 g_signal_connect (
508 chooser->priv->boxes[ii], "event",
509 G_CALLBACK (day_event_cb), chooser);
511 chooser->priv->labels[ii] = gnome_canvas_item_new (
512 parent,
513 GNOME_TYPE_CANVAS_TEXT,
514 NULL);
515 g_signal_connect (
516 chooser->priv->labels[ii], "event",
517 G_CALLBACK (day_event_cb), chooser);
521 static void
522 e_weekday_chooser_init (EWeekdayChooser *chooser)
524 chooser->priv = E_WEEKDAY_CHOOSER_GET_PRIVATE (chooser);
526 create_items (chooser);
527 chooser->priv->focus_day = -1;
531 * e_weekday_chooser_new:
533 * Creates a new #EWeekdayChooser.
535 * Returns: an #EWeekdayChooser
537 GtkWidget *
538 e_weekday_chooser_new (void)
540 return g_object_new (E_TYPE_WEEKDAY_CHOOSER, NULL);
544 * e_weekday_chooser_get_days:
545 * @chooser: an #EWeekdayChooser
546 * @weekday: a #GDateWeekday
548 * Returns whether @weekday is selected.
550 * Returns: whether @weekday is selected
552 gboolean
553 e_weekday_chooser_get_selected (EWeekdayChooser *chooser,
554 GDateWeekday weekday)
556 g_return_val_if_fail (E_IS_WEEKDAY_CHOOSER (chooser), FALSE);
557 g_return_val_if_fail (g_date_valid_weekday (weekday), FALSE);
559 return chooser->priv->selected_weekdays[weekday];
563 * e_weekday_chooser_set_selected:
564 * @chooser: an #EWeekdayChooser
565 * @weekday: a #GDateWeekday
566 * @selected: selected flag
568 * Selects or deselects @weekday.
570 void
571 e_weekday_chooser_set_selected (EWeekdayChooser *chooser,
572 GDateWeekday weekday,
573 gboolean selected)
575 g_return_if_fail (E_IS_WEEKDAY_CHOOSER (chooser));
576 g_return_if_fail (g_date_valid_weekday (weekday));
578 chooser->priv->selected_weekdays[weekday] = selected;
580 colorize_items (chooser);
582 g_signal_emit (chooser, chooser_signals[CHANGED], 0);
586 * e_weekday_chooser_get_blocked:
587 * @chooser: an #EWeekdayChooser
588 * @weekday: a #GDateWeekday
590 * Returns whether @weekday is blocked from being modified by the user.
592 * Returns: whether @weekday is blocked
594 gboolean
595 e_weekday_chooser_get_blocked (EWeekdayChooser *chooser,
596 GDateWeekday weekday)
598 g_return_val_if_fail (E_IS_WEEKDAY_CHOOSER (chooser), FALSE);
599 g_return_val_if_fail (g_date_valid_weekday (weekday), FALSE);
601 return chooser->priv->blocked_weekdays[weekday];
605 * e_weekday_chooser_set_blocked:
606 * @chooser: an #EWeekdayChooser
607 * @weekday: a #GDateWeekday
608 * @blocked: blocked flag
610 * Sets whether @weekday is blocked from being modified by the user.
612 void
613 e_weekday_chooser_set_blocked (EWeekdayChooser *chooser,
614 GDateWeekday weekday,
615 gboolean blocked)
617 g_return_if_fail (E_IS_WEEKDAY_CHOOSER (chooser));
618 g_return_if_fail (g_date_valid_weekday (weekday));
620 chooser->priv->blocked_weekdays[weekday] = blocked;
624 * e_weekday_chooser_get_week_start_day:
625 * @chooser: an #EWeekdayChooser
627 * Queries the day that defines the start of the week in @chooser.
629 * Returns: a #GDateWeekday
631 GDateWeekday
632 e_weekday_chooser_get_week_start_day (EWeekdayChooser *chooser)
634 g_return_val_if_fail (E_IS_WEEKDAY_CHOOSER (chooser), G_DATE_BAD_WEEKDAY);
636 return chooser->priv->week_start_day;
640 * e_weekday_chooser_set_week_start_day:
641 * @chooser: an #EWeekdayChooser
642 * @week_start_day: a #GDateWeekday
644 * Sets the day that defines the start of the week for @chooser.
646 void
647 e_weekday_chooser_set_week_start_day (EWeekdayChooser *chooser,
648 GDateWeekday week_start_day)
650 g_return_if_fail (E_IS_WEEKDAY_CHOOSER (chooser));
651 g_return_if_fail (g_date_valid_weekday (week_start_day));
653 if (week_start_day == chooser->priv->week_start_day)
654 return;
656 chooser->priv->week_start_day = week_start_day;
658 configure_items (chooser);
660 g_object_notify (G_OBJECT (chooser), "week-start-day");