diff options
Diffstat (limited to 'calendar/gui/e-weekday-chooser.c')
-rw-r--r-- | calendar/gui/e-weekday-chooser.c | 665 |
1 files changed, 665 insertions, 0 deletions
diff --git a/calendar/gui/e-weekday-chooser.c b/calendar/gui/e-weekday-chooser.c new file mode 100644 index 0000000000..ef459e574d --- /dev/null +++ b/calendar/gui/e-weekday-chooser.c @@ -0,0 +1,665 @@ +/* + * e-weekday-chooser.c + * + * 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 <http://www.gnu.org/licenses/> + * + */ + +#include "e-weekday-chooser.h" + +#include <config.h> +#include <string.h> + +#include <gtk/gtk.h> +#include <glib/gi18n-lib.h> +#include <gdk/gdkkeysyms.h> + +#include <libgnomecanvas/libgnomecanvas.h> +#include <e-util/e-util.h> + +#define PADDING 2 + +#define E_WEEKDAY_CHOOSER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_WEEKDAY_CHOOSER, EWeekdayChooserPrivate)) + +/* Private part of the EWeekdayChooser structure */ +struct _EWeekdayChooserPrivate { + gboolean blocked_weekdays[8]; /* indexed by GDateWeekday */ + gboolean selected_weekdays[8]; /* indexed by GDateWeekday */ + + /* Day that defines the start of the week. */ + GDateWeekday week_start_day; + + /* Current keyboard focus day */ + GDateWeekday focus_day; + + /* Metrics */ + gint font_ascent, font_descent; + gint max_letter_width; + + /* Components */ + GnomeCanvasItem *boxes[7]; + GnomeCanvasItem *labels[7]; +}; + +enum { + PROP_0, + PROP_WEEK_START_DAY +}; + +enum { + CHANGED, + LAST_SIGNAL +}; + +static guint chooser_signals[LAST_SIGNAL]; + +G_DEFINE_TYPE_WITH_CODE ( + EWeekdayChooser, + e_weekday_chooser, + GNOME_TYPE_CANVAS, + G_IMPLEMENT_INTERFACE ( + E_TYPE_EXTENSIBLE, NULL)) + +static void +colorize_items (EWeekdayChooser *chooser) +{ + GdkColor *outline, *focus_outline; + GdkColor *fill, *sel_fill; + GdkColor *text_fill, *sel_text_fill; + GtkStateType state; + GtkStyle *style; + GDateWeekday weekday; + gint ii; + + state = gtk_widget_get_state (GTK_WIDGET (chooser)); + style = gtk_widget_get_style (GTK_WIDGET (chooser)); + + 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]; + + weekday = e_weekday_chooser_get_week_start_day (chooser); + + for (ii = 0; ii < 7; ii++) { + GdkColor *f, *t, *o; + + if (chooser->priv->selected_weekdays[weekday]) { + f = sel_fill; + t = sel_text_fill; + } else { + f = fill; + t = text_fill; + } + + if (weekday == chooser->priv->focus_day) + o = focus_outline; + else + o = outline; + + gnome_canvas_item_set ( + chooser->priv->boxes[ii], + "fill_color_gdk", f, + "outline_color_gdk", o, + NULL); + + gnome_canvas_item_set ( + chooser->priv->labels[ii], + "fill_color_gdk", t, + NULL); + + weekday = e_weekday_get_next (weekday); + } +} + +static void +configure_items (EWeekdayChooser *chooser) +{ + GtkAllocation allocation; + gint width, height; + gint box_width; + GDateWeekday weekday; + gint ii; + + gtk_widget_get_allocation (GTK_WIDGET (chooser), &allocation); + + width = allocation.width; + height = allocation.height; + + box_width = (width - 1) / 7; + + weekday = e_weekday_chooser_get_week_start_day (chooser); + + for (ii = 0; ii < 7; ii++) { + gnome_canvas_item_set ( + chooser->priv->boxes[ii], + "x1", (gdouble) (ii * box_width), + "y1", (gdouble) 0, + "x2", (gdouble) ((ii + 1) * box_width), + "y2", (gdouble) (height - 1), + "line_width", 0.0, + NULL); + + gnome_canvas_item_set ( + chooser->priv->labels[ii], + "text", e_get_weekday_name (weekday, TRUE), + "x", (gdouble) (ii * box_width) + PADDING, + "y", (gdouble) (1 + PADDING), + NULL); + + weekday = e_weekday_get_next (weekday); + } + + colorize_items (chooser); +} + +static void +weekday_chooser_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_WEEK_START_DAY: + e_weekday_chooser_set_week_start_day ( + E_WEEKDAY_CHOOSER (object), + g_value_get_enum (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +weekday_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_WEEK_START_DAY: + g_value_set_enum ( + value, + e_weekday_chooser_get_week_start_day ( + E_WEEKDAY_CHOOSER (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +weekday_chooser_constructed (GObject *object) +{ + /* Chain up to parent's constructed() method. */ + G_OBJECT_CLASS (e_weekday_chooser_parent_class)->constructed (object); + + e_extensible_load_extensions (E_EXTENSIBLE (object)); +} + +static void +weekday_chooser_realize (GtkWidget *widget) +{ + EWeekdayChooser *chooser; + + chooser = E_WEEKDAY_CHOOSER (widget); + + /* Chain up to parent's realize() method. */ + GTK_WIDGET_CLASS (e_weekday_chooser_parent_class)->realize (widget); + + configure_items (chooser); +} + +static void +weekday_chooser_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GtkWidgetClass *widget_class; + EWeekdayChooser *chooser; + + chooser = E_WEEKDAY_CHOOSER (widget); + + /* Chain up to parent's size_allocate() method. */ + widget_class = GTK_WIDGET_CLASS (e_weekday_chooser_parent_class); + widget_class->size_allocate (widget, allocation); + + gnome_canvas_set_scroll_region ( + GNOME_CANVAS (chooser), 0, 0, + allocation->width, allocation->height); + + configure_items (chooser); +} + +static void +weekday_chooser_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + GtkWidgetClass *widget_class; + EWeekdayChooser *chooser; + EWeekdayChooserPrivate *priv; + gint max_width; + PangoFontDescription *font_desc; + PangoContext *pango_context; + PangoFontMetrics *font_metrics; + PangoLayout *layout; + GDateWeekday weekday; + + chooser = E_WEEKDAY_CHOOSER (widget); + priv = chooser->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 (weekday = G_DATE_MONDAY; weekday <= G_DATE_SUNDAY; weekday++) { + const gchar *name; + gint w; + + name = e_get_weekday_name (weekday, TRUE); + pango_layout_set_text (layout, name, strlen (name)); + pango_layout_get_pixel_size (layout, &w, NULL); + + if (w > max_width) + max_width = w; + } + + priv->max_letter_width = max_width; + + configure_items (chooser); + g_object_unref (layout); + pango_font_metrics_unref (font_metrics); + + /* Chain up to parent's style_set() method. */ + widget_class = GTK_WIDGET_CLASS (e_weekday_chooser_parent_class); + widget_class->style_set (widget, previous_style); +} + +static void +weekday_chooser_get_preferred_height (GtkWidget *widget, + gint *minimum_height, + gint *natural_height) +{ + EWeekdayChooser *chooser; + EWeekdayChooserPrivate *priv; + + chooser = E_WEEKDAY_CHOOSER (widget); + priv = chooser->priv; + + *minimum_height = *natural_height = + (priv->font_ascent + priv->font_descent + 2 * PADDING + 2); +} + +static void +weekday_chooser_get_preferred_width (GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + EWeekdayChooser *chooser; + EWeekdayChooserPrivate *priv; + + chooser = E_WEEKDAY_CHOOSER (widget); + priv = chooser->priv; + + *minimum_width = *natural_width = + (priv->max_letter_width + 2 * PADDING + 1) * 7 + 1; +} + +static gboolean +weekday_chooser_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + EWeekdayChooser *chooser; + + chooser = E_WEEKDAY_CHOOSER (widget); + + if (!gtk_widget_get_can_focus (widget)) + return FALSE; + + if (gtk_widget_has_focus (widget)) { + chooser->priv->focus_day = G_DATE_BAD_WEEKDAY; + colorize_items (chooser); + return FALSE; + } + + chooser->priv->focus_day = chooser->priv->week_start_day; + gnome_canvas_item_grab_focus (chooser->priv->boxes[0]); + + colorize_items (chooser); + + return TRUE; +} + +static void +e_weekday_chooser_class_init (EWeekdayChooserClass *class) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + g_type_class_add_private (class, sizeof (EWeekdayChooserPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = weekday_chooser_set_property; + object_class->get_property = weekday_chooser_get_property; + object_class->constructed = weekday_chooser_constructed; + + widget_class = GTK_WIDGET_CLASS (class); + widget_class->realize = weekday_chooser_realize; + widget_class->size_allocate = weekday_chooser_size_allocate; + widget_class->style_set = weekday_chooser_style_set; + widget_class->get_preferred_height = weekday_chooser_get_preferred_height; + widget_class->get_preferred_width = weekday_chooser_get_preferred_width; + widget_class->focus = weekday_chooser_focus; + + g_object_class_install_property ( + object_class, + PROP_WEEK_START_DAY, + g_param_spec_enum ( + "week-start-day", + "Week Start Day", + NULL, + E_TYPE_DATE_WEEKDAY, + G_DATE_MONDAY, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + chooser_signals[CHANGED] = g_signal_new ( + "changed", + G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (EWeekdayChooserClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +static void +day_clicked (EWeekdayChooser *chooser, + GDateWeekday weekday) +{ + gboolean selected; + + if (chooser->priv->blocked_weekdays[weekday]) + return; + + selected = e_weekday_chooser_get_selected (chooser, weekday); + e_weekday_chooser_set_selected (chooser, weekday, !selected); +} + +static gint +handle_key_press_event (EWeekdayChooser *chooser, + GdkEvent *event) +{ + EWeekdayChooserPrivate *priv = chooser->priv; + guint keyval = event->key.keyval; + guint index; + + if (chooser->priv->focus_day == G_DATE_BAD_WEEKDAY) + chooser->priv->focus_day = chooser->priv->week_start_day; + + switch (keyval) { + case GDK_KEY_Up: + case GDK_KEY_Right: + chooser->priv->focus_day = + e_weekday_get_next (chooser->priv->focus_day); + break; + case GDK_KEY_Down: + case GDK_KEY_Left: + chooser->priv->focus_day = + e_weekday_get_prev (chooser->priv->focus_day); + break; + case GDK_KEY_space: + case GDK_KEY_Return: + day_clicked (chooser, priv->focus_day); + return TRUE; + default: + return FALSE; + } + + colorize_items (chooser); + + index = e_weekday_get_days_between ( + chooser->priv->week_start_day, + chooser->priv->focus_day); + + gnome_canvas_item_grab_focus (chooser->priv->boxes[index]); + + return TRUE; +} + +/* Event handler for the day items */ +static gboolean +day_event_cb (GnomeCanvasItem *item, + GdkEvent *event, + gpointer data) +{ + EWeekdayChooser *chooser; + gint ii; + + chooser = E_WEEKDAY_CHOOSER (data); + + if (event->type == GDK_KEY_PRESS) + return handle_key_press_event (chooser, event); + + if (!(event->type == GDK_BUTTON_PRESS && event->button.button == 1)) + return FALSE; + + /* Find which box was clicked */ + + for (ii = 0; ii < 7; ii++) { + if (chooser->priv->boxes[ii] == item) + break; + if (chooser->priv->labels[ii] == item) + break; + } + + g_return_val_if_fail (ii < 7, FALSE); + + chooser->priv->focus_day = e_weekday_add_days ( + chooser->priv->week_start_day, ii); + + gnome_canvas_item_grab_focus (chooser->priv->boxes[ii]); + + day_clicked (chooser, chooser->priv->focus_day); + + return TRUE; +} + +/* Creates the canvas items for the weekday chooser. + * The items are empty until they are configured elsewhere. */ +static void +create_items (EWeekdayChooser *chooser) +{ + GnomeCanvasGroup *parent; + gint ii; + + parent = gnome_canvas_root (GNOME_CANVAS (chooser)); + + for (ii = 0; ii < 7; ii++) { + chooser->priv->boxes[ii] = gnome_canvas_item_new ( + parent, + GNOME_TYPE_CANVAS_RECT, + NULL); + g_signal_connect ( + chooser->priv->boxes[ii], "event", + G_CALLBACK (day_event_cb), chooser); + + chooser->priv->labels[ii] = gnome_canvas_item_new ( + parent, + GNOME_TYPE_CANVAS_TEXT, + NULL); + g_signal_connect ( + chooser->priv->labels[ii], "event", + G_CALLBACK (day_event_cb), chooser); + } +} + +static void +e_weekday_chooser_init (EWeekdayChooser *chooser) +{ + chooser->priv = E_WEEKDAY_CHOOSER_GET_PRIVATE (chooser); + + create_items (chooser); + chooser->priv->focus_day = -1; +} + +/** + * e_weekday_chooser_new: + * + * Creates a new #EWeekdayChooser. + * + * Returns: an #EWeekdayChooser + **/ +GtkWidget * +e_weekday_chooser_new (void) +{ + return g_object_new (E_TYPE_WEEKDAY_CHOOSER, NULL); +} + +/** + * e_weekday_chooser_get_days: + * @chooser: an #EWeekdayChooser + * @weekday: a #GDateWeekday + * + * Returns whether @weekday is selected. + * + * Returns: whether @weekday is selected + **/ +gboolean +e_weekday_chooser_get_selected (EWeekdayChooser *chooser, + GDateWeekday weekday) +{ + g_return_val_if_fail (E_IS_WEEKDAY_CHOOSER (chooser), FALSE); + g_return_val_if_fail (g_date_valid_weekday (weekday), FALSE); + + return chooser->priv->selected_weekdays[weekday]; +} + +/** + * e_weekday_chooser_set_selected: + * @chooser: an #EWeekdayChooser + * @weekday: a #GDateWeekday + * @selected: selected flag + * + * Selects or deselects @weekday. + **/ +void +e_weekday_chooser_set_selected (EWeekdayChooser *chooser, + GDateWeekday weekday, + gboolean selected) +{ + g_return_if_fail (E_IS_WEEKDAY_CHOOSER (chooser)); + g_return_if_fail (g_date_valid_weekday (weekday)); + + chooser->priv->selected_weekdays[weekday] = selected; + + colorize_items (chooser); + + g_signal_emit (chooser, chooser_signals[CHANGED], 0); +} + +/** + * e_weekday_chooser_get_blocked: + * @chooser: an #EWeekdayChooser + * @weekday: a #GDateWeekday + * + * Returns whether @weekday is blocked from being modified by the user. + * + * Returns: whether @weekday is blocked + **/ +gboolean +e_weekday_chooser_get_blocked (EWeekdayChooser *chooser, + GDateWeekday weekday) +{ + g_return_val_if_fail (E_IS_WEEKDAY_CHOOSER (chooser), FALSE); + g_return_val_if_fail (g_date_valid_weekday (weekday), FALSE); + + return chooser->priv->blocked_weekdays[weekday]; +} + +/** + * e_weekday_chooser_set_blocked: + * @chooser: an #EWeekdayChooser + * @weekday: a #GDateWeekday + * @blocked: blocked flag + * + * Sets whether @weekday is blocked from being modified by the user. + **/ +void +e_weekday_chooser_set_blocked (EWeekdayChooser *chooser, + GDateWeekday weekday, + gboolean blocked) +{ + g_return_if_fail (E_IS_WEEKDAY_CHOOSER (chooser)); + g_return_if_fail (g_date_valid_weekday (weekday)); + + chooser->priv->blocked_weekdays[weekday] = blocked; +} + +/** + * e_weekday_chooser_get_week_start_day: + * @chooser: an #EWeekdayChooser + * + * Queries the day that defines the start of the week in @chooser. + * + * Returns: a #GDateWeekday + **/ +GDateWeekday +e_weekday_chooser_get_week_start_day (EWeekdayChooser *chooser) +{ + g_return_val_if_fail (E_IS_WEEKDAY_CHOOSER (chooser), -1); + + return chooser->priv->week_start_day; +} + +/** + * e_weekday_chooser_set_week_start_day: + * @chooser: an #EWeekdayChooser + * @week_start_day: a #GDateWeekday + * + * Sets the day that defines the start of the week for @chooser. + **/ +void +e_weekday_chooser_set_week_start_day (EWeekdayChooser *chooser, + GDateWeekday week_start_day) +{ + g_return_if_fail (E_IS_WEEKDAY_CHOOSER (chooser)); + g_return_if_fail (g_date_valid_weekday (week_start_day)); + + if (week_start_day == chooser->priv->week_start_day) + return; + + chooser->priv->week_start_day = week_start_day; + + configure_items (chooser); + + g_object_notify (G_OBJECT (chooser), "week-start-day"); +} + |