/* * Evolution calendar - Week day picker widget * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Federico Mena-Quintero * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "weekday-picker.h" #define PADDING 2 #define WEEKDAY_PICKER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), TYPE_WEEKDAY_PICKER, WeekdayPickerPrivate)) /* Private part of the WeekdayPicker structure */ struct _WeekdayPickerPrivate { /* Selected days; see weekday_picker_set_days() */ guint8 day_mask; /* Blocked days; these cannot be modified */ guint8 blocked_day_mask; /* Day that defines the start of the week; 0 = Sunday, ..., 6 = Saturday */ gint week_start_day; /* Current keyboard focus day */ gint focus_day; /* Metrics */ gint font_ascent, font_descent; gint max_letter_width; /* Components */ GnomeCanvasItem *boxes[7]; GnomeCanvasItem *labels[7]; }; /* Signal IDs */ enum { CHANGED, LAST_SIGNAL }; static guint wp_signals[LAST_SIGNAL]; G_DEFINE_TYPE (WeekdayPicker, weekday_picker, GNOME_TYPE_CANVAS) static gchar * get_day_text (gint day_index) { GDateWeekday weekday; /* Convert from tm_wday to GDateWeekday. */ weekday = (day_index == 0) ? G_DATE_SUNDAY : day_index; return g_strdup (e_get_weekday_name (weekday, TRUE)); } static void colorize_items (WeekdayPicker *wp) { WeekdayPickerPrivate *priv; GdkColor *outline, *focus_outline; GdkColor *fill, *sel_fill; GdkColor *text_fill, *sel_text_fill; GtkStateType state; GtkStyle *style; gint i; priv = wp->priv; state = gtk_widget_get_state (GTK_WIDGET (wp)); style = gtk_widget_get_style (GTK_WIDGET (wp)); outline = &style->fg[state]; focus_outline = &style->bg[state]; fill = &style->base[state]; text_fill = &style->fg[state]; sel_fill = &style->bg[GTK_STATE_SELECTED]; sel_text_fill = &style->fg[GTK_STATE_SELECTED]; for (i = 0; i < 7; i++) { gint day; GdkColor *f, *t, *o; day = i + priv->week_start_day; if (day >= 7) day -= 7; if (priv->day_mask & (0x1 << day)) { f = sel_fill; t = sel_text_fill; } else { f = fill; t = text_fill; } if (day == priv->focus_day) o = focus_outline; else o = outline; gnome_canvas_item_set (priv->boxes[i], "fill_color_gdk", f, "outline_color_gdk", o, NULL); gnome_canvas_item_set (priv->labels[i], "fill_color_gdk", t, NULL); } } static void configure_items (WeekdayPicker *wp) { WeekdayPickerPrivate *priv; GtkAllocation allocation; gint width, height; gint box_width; gint i; priv = wp->priv; gtk_widget_get_allocation (GTK_WIDGET (wp), &allocation); width = allocation.width; height = allocation.height; box_width = (width - 1) / 7; for (i = 0; i < 7; i++) { gchar *c; gint day; day = i + priv->week_start_day; if (day >= 7) day -= 7; gnome_canvas_item_set (priv->boxes[i], "x1", (double) (i * box_width), "y1", (double) 0, "x2", (double) ((i + 1) * box_width), "y2", (double) (height - 1), "line_width", 0.0, NULL); c = get_day_text (day); gnome_canvas_item_set (priv->labels[i], "text", c, "x", (double) (i * box_width) + PADDING, "y", (double) (1 + PADDING), NULL); g_free (c); } colorize_items (wp); } static void weekday_picker_realize (GtkWidget *widget) { WeekdayPicker *wp; wp = WEEKDAY_PICKER (widget); /* Chain up to parent's realize() method. */ GTK_WIDGET_CLASS (weekday_picker_parent_class)->realize (widget); configure_items (wp); } static void weekday_picker_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width) { WeekdayPicker *wp; WeekdayPickerPrivate *priv; wp = WEEKDAY_PICKER (widget); priv = wp->priv; *minimum_width = *natural_width = (priv->max_letter_width + 2 * PADDING + 1) * 7 + 1; } static void weekday_picker_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height) { WeekdayPicker *wp; WeekdayPickerPrivate *priv; wp = WEEKDAY_PICKER (widget); priv = wp->priv; *minimum_height = *natural_height = (priv->font_ascent + priv->font_descent + 2 * PADDING + 2); } static void weekday_picker_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkWidgetClass *widget_class; WeekdayPicker *wp; wp = WEEKDAY_PICKER (widget); /* Chain up to parent's size_allocate() method. */ widget_class = GTK_WIDGET_CLASS (weekday_picker_parent_class); widget_class->size_allocate (widget, allocation); gnome_canvas_set_scroll_region ( GNOME_CANVAS (wp), 0, 0, allocation->width, allocation->height); configure_items (wp); } static void weekday_picker_style_set (GtkWidget *widget, GtkStyle *previous_style) { GtkWidgetClass *widget_class; WeekdayPicker *wp; WeekdayPickerPrivate *priv; gint max_width; gint i; PangoFontDescription *font_desc; PangoContext *pango_context; PangoFontMetrics *font_metrics; PangoLayout *layout; wp = WEEKDAY_PICKER (widget); priv = wp->priv; /* Set up Pango prerequisites */ font_desc = gtk_widget_get_style (widget)->font_desc; pango_context = gtk_widget_get_pango_context (widget); font_metrics = pango_context_get_metrics ( pango_context, font_desc, pango_context_get_language (pango_context)); layout = pango_layout_new (pango_context); priv->font_ascent = PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)); priv->font_descent = PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)); max_width = 0; for (i = 0; i < 7; i++) { gchar *c; gint w; c = get_day_text (i); pango_layout_set_text (layout, c, strlen (c)); pango_layout_get_pixel_size (layout, &w, NULL); g_free (c); if (w > max_width) max_width = w; } priv->max_letter_width = max_width; configure_items (wp); g_object_unref (layout); pango_font_metrics_unref (font_metrics); /* Chain up to parent's style_set() method. */ widget_class = GTK_WIDGET_CLASS (weekday_picker_parent_class); widget_class->style_set (widget, previous_style); } static gboolean weekday_picker_focus (GtkWidget *widget, GtkDirectionType direction) { WeekdayPicker *wp; WeekdayPickerPrivate *priv; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_WEEKDAY_PICKER (widget), FALSE); wp = WEEKDAY_PICKER (widget); priv = wp->priv; if (!gtk_widget_get_can_focus (widget)) return FALSE; if (gtk_widget_has_focus (widget)) { priv->focus_day = -1; colorize_items (wp); return FALSE; } priv->focus_day = priv->week_start_day; gnome_canvas_item_grab_focus (priv->boxes[priv->focus_day]); colorize_items (wp); return TRUE; } static void weekday_picker_class_init (WeekdayPickerClass *class) { GtkWidgetClass *widget_class; g_type_class_add_private (class, sizeof (WeekdayPickerPrivate)); widget_class = GTK_WIDGET_CLASS (class); widget_class->realize = weekday_picker_realize; widget_class->get_preferred_width = weekday_picker_get_preferred_width; widget_class->get_preferred_height = weekday_picker_get_preferred_height; widget_class->size_allocate = weekday_picker_size_allocate; widget_class->style_set = weekday_picker_style_set; widget_class->focus = weekday_picker_focus; class->changed = NULL; wp_signals[CHANGED] = g_signal_new ( "changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (WeekdayPickerClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void day_clicked (WeekdayPicker *wp, gint index) { WeekdayPickerPrivate *priv = wp->priv; guint8 day_mask; if (priv->blocked_day_mask & (0x1 << index)) return; if (priv->day_mask & (0x1 << index)) day_mask = priv->day_mask & ~(0x1 << index); else day_mask = priv->day_mask | (0x1 << index); weekday_picker_set_days (wp, day_mask); } static gint handle_key_press_event (WeekdayPicker *wp, GdkEvent *event) { WeekdayPickerPrivate *priv = wp->priv; guint keyval = event->key.keyval; if (priv->focus_day == -1) priv->focus_day = priv->week_start_day; switch (keyval) { case GDK_KEY_Up: case GDK_KEY_Right: priv->focus_day += 1; break; case GDK_KEY_Down: case GDK_KEY_Left: priv->focus_day -= 1; break; case GDK_KEY_space: case GDK_KEY_Return: day_clicked (wp, priv->focus_day); return TRUE; default: return FALSE; } if (priv->focus_day > 6) priv->focus_day = 0; if (priv->focus_day < 0) priv->focus_day = 6; colorize_items (wp); gnome_canvas_item_grab_focus (priv->boxes[priv->focus_day]); return TRUE; } /* Event handler for the day items */ static gint day_event_cb (GnomeCanvasItem *item, GdkEvent *event, gpointer data) { WeekdayPicker *wp; WeekdayPickerPrivate *priv; gint i; wp = WEEKDAY_PICKER (data); priv = wp->priv; if (event->type == GDK_KEY_PRESS) return handle_key_press_event (wp, event); if (!(event->type == GDK_BUTTON_PRESS && event->button.button == 1)) return FALSE; /* Find which box was clicked */ for (i = 0; i < 7; i++) if (priv->boxes[i] == item || priv->labels[i] == item) break; g_return_val_if_fail (i != 7, TRUE); i += priv->week_start_day; if (i >= 7) i -= 7; priv->focus_day = i; gnome_canvas_item_grab_focus (priv->boxes[i]); day_clicked (wp, i); return TRUE; } /* Creates the canvas items for the weekday picker. The items are empty until * they are configured elsewhere. */ static void create_items (WeekdayPicker *wp) { WeekdayPickerPrivate *priv; GnomeCanvasGroup *parent; gint i; priv = wp->priv; parent = gnome_canvas_root (GNOME_CANVAS (wp)); for (i = 0; i < 7; i++) { priv->boxes[i] = gnome_canvas_item_new (parent, GNOME_TYPE_CANVAS_RECT, NULL); g_signal_connect ( priv->boxes[i], "event", G_CALLBACK (day_event_cb), wp); priv->labels[i] = gnome_canvas_item_new (parent, GNOME_TYPE_CANVAS_TEXT, NULL); g_signal_connect ( priv->labels[i], "event", G_CALLBACK (day_event_cb), wp); } } static void weekday_picker_init (WeekdayPicker *wp) { wp->priv = WEEKDAY_PICKER_GET_PRIVATE (wp); create_items (wp); wp->priv->focus_day = -1; } /** * weekday_picker_new: * @void: * * Creates a new weekday picker widget. * * Return value: A newly-created weekday picker. **/ GtkWidget * weekday_picker_new (void) { return g_object_new (TYPE_WEEKDAY_PICKER, NULL); } /** * weekday_picker_set_days: * @wp: A weekday picker. * @day_mask: Bitmask with the days to be selected. * * Sets the days that are selected in a weekday picker. In the @day_mask, * Sunday is bit 0, Monday is bit 1, etc. **/ void weekday_picker_set_days (WeekdayPicker *wp, guint8 day_mask) { g_return_if_fail (IS_WEEKDAY_PICKER (wp)); wp->priv->day_mask = day_mask; colorize_items (wp); g_signal_emit (G_OBJECT (wp), wp_signals[CHANGED], 0); } /** * weekday_picker_get_days: * @wp: A weekday picker. * * Queries the days that are selected in a weekday picker. * * Return value: Bit mask of selected days. Sunday is bit 0, Monday is bit 1, * etc. **/ guint8 weekday_picker_get_days (WeekdayPicker *wp) { g_return_val_if_fail (IS_WEEKDAY_PICKER (wp), 0); return wp->priv->day_mask; } /** * weekday_picker_set_blocked_days: * @wp: A weekday picker. * @blocked_day_mask: Bitmask with the days to be blocked. * * Sets the days that the weekday picker will prevent from being modified by the * user. The @blocked_day_mask is specified in the same way as in * weekday_picker_set_days(). **/ void weekday_picker_set_blocked_days (WeekdayPicker *wp, guint8 blocked_day_mask) { g_return_if_fail (IS_WEEKDAY_PICKER (wp)); wp->priv->blocked_day_mask = blocked_day_mask; } /** * weekday_picker_get_blocked_days: * @wp: A weekday picker. * * Queries the set of days that the weekday picker prevents from being modified * by the user. * * Return value: Bit mask of blocked days, with the same format as that returned * by weekday_picker_get_days(). **/ guint weekday_picker_get_blocked_days (WeekdayPicker *wp) { g_return_val_if_fail (IS_WEEKDAY_PICKER (wp), 0); return wp->priv->blocked_day_mask; } /** * weekday_picker_set_week_start_day: * @wp: A weekday picker. * @week_start_day: Index of the day that defines the start of the week; 0 is * Sunday, 1 is Monday, etc. * * Sets the day that defines the start of the week for a weekday picker. **/ void weekday_picker_set_week_start_day (WeekdayPicker *wp, gint week_start_day) { g_return_if_fail (IS_WEEKDAY_PICKER (wp)); g_return_if_fail (week_start_day >= 0 && week_start_day < 7); wp->priv->week_start_day = week_start_day; configure_items (wp); } /** * weekday_picker_get_week_start_day: * @wp: A weekday picker. * * Queries the day that defines the start of the week in a weekday picker. * * Return value: Index of the day that defines the start of the week. See * weekday_picker_set_week_start_day() to see how this is represented. **/ gint weekday_picker_get_week_start_day (WeekdayPicker *wp) { g_return_val_if_fail (IS_WEEKDAY_PICKER (wp), -1); return wp->priv->week_start_day; }