diff --git a/data/Makefile.am b/data/Makefile.am index 54b4c986..046d2404 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -51,10 +51,12 @@ EXTRA_DIST= \ shell-search-provider-dbus-interfaces.xml \ ui/alarm-row.ui \ ui/calendar-row.ui \ + ui/date-chooser.ui \ ui/date-selector.ui \ ui/edit-dialog.ui \ ui/help-overlay.ui \ ui/menus.ui \ + ui/multi-choice.ui \ ui/quick-add-popover.ui \ ui/search-view.ui \ ui/source-dialog.ui \ diff --git a/data/calendar.gresource.xml b/data/calendar.gresource.xml index f083f3f9..7c64779b 100644 --- a/data/calendar.gresource.xml +++ b/data/calendar.gresource.xml @@ -3,8 +3,10 @@ ui/alarm-row.ui ui/calendar-row.ui + ui/date-chooser.ui ui/date-selector.ui ui/edit-dialog.ui + ui/multi-choice.ui ui/online-account-row.ui ui/quick-add-popover.ui ui/search-view.ui diff --git a/data/theme/gtk-styles.css b/data/theme/gtk-styles.css index f0d81c74..f2a8d443 100644 --- a/data/theme/gtk-styles.css +++ b/data/theme/gtk-styles.css @@ -290,3 +290,34 @@ button:checked:not(:backdrop) .calendar-color-image, .sources-button:not(:backdrop):not(:disabled) .calendar-color-image { -gtk-icon-shadow: 0 1px alpha(black, 0.1); } + +/* Date chooser */ +datechooser .weeknum, +datechooser .weekday { + color: alpha(@theme_fg_color, 0.55);; + font-size: 80%; +} + +navigator button { + padding: 0; +} + +navigator label { + font-weight: bold; +} + +datechooser day { + color: @theme_fg_color; + min-width: 32px; + min-height: 32px; +} + +datechooser day.other-month { + color: @theme_insensitive_fg_color; +} + +datechooser day:selected { + color: @theme_selected_fg_color; + background-color: @theme_selected_bg_color; + border-radius: 50%; +} diff --git a/data/ui/date-chooser.ui b/data/ui/date-chooser.ui new file mode 100644 index 00000000..db203431 --- /dev/null +++ b/data/ui/date-chooser.ui @@ -0,0 +1,54 @@ + + + + + diff --git a/data/ui/multi-choice.ui b/data/ui/multi-choice.ui new file mode 100644 index 00000000..8376567d --- /dev/null +++ b/data/ui/multi-choice.ui @@ -0,0 +1,71 @@ + + + + + diff --git a/src/Makefile.am b/src/Makefile.am index 3b5f79f9..e4f52fd9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -28,6 +28,10 @@ gnome_calendar_SOURCES = \ css-code.h \ gcal-application.h \ gcal-application.c \ + gcal-date-chooser-day.c \ + gcal-date-chooser-day.h \ + gcal-date-chooser.c \ + gcal-date-chooser.h \ gcal-date-selector.c \ gcal-date-selector.h \ gcal-edit-dialog.c \ @@ -40,6 +44,8 @@ gnome_calendar_SOURCES = \ gcal-manager.h \ gcal-month-view.c \ gcal-month-view.h \ + gcal-multi-choice.c \ + gcal-multi-choice.h \ gcal-quick-add-popover.c \ gcal-quick-add-popover.h \ gcal-search-view.h \ diff --git a/src/gcal-date-chooser-day.c b/src/gcal-date-chooser-day.c new file mode 100644 index 00000000..1f85d14c --- /dev/null +++ b/src/gcal-date-chooser-day.c @@ -0,0 +1,443 @@ +/* GTK - The GIMP Toolkit + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This library 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) any later version. + * + * This library 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 this library. If not, see . + */ + +#include "config.h" + +#include "gcal-date-chooser-day.h" + +#include +#include + +enum { + SELECTED, + LAST_DAY_SIGNAL +}; + +static guint signals[LAST_DAY_SIGNAL] = { 0, }; + +struct _GcalDateChooserDay +{ + GtkBin parent; + + GtkWidget *label; + GDateTime *date; + GdkWindow *event_window; + GtkGesture *multipress_gesture; +}; + +G_DEFINE_TYPE (GcalDateChooserDay, gcal_date_chooser_day, GTK_TYPE_BIN) + +static void +day_pressed (GtkGestureMultiPress *gesture, + gint n_press, + gdouble x, + gdouble y, + GcalDateChooserDay *self) +{ + gint button; + + button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + + if (button == GDK_BUTTON_PRIMARY) + { + if (n_press == 1) + g_signal_emit (self, signals[SELECTED], 0); + } +} + +static gboolean +gcal_date_chooser_day_key_press (GtkWidget *widget, + GdkEventKey *event) +{ + GcalDateChooserDay *self = GCAL_DATE_CHOOSER_DAY (widget); + + if (event->keyval == GDK_KEY_space || + event->keyval == GDK_KEY_Return || + event->keyval == GDK_KEY_ISO_Enter|| + event->keyval == GDK_KEY_KP_Enter || + event->keyval == GDK_KEY_KP_Space) + { + g_signal_emit (self, signals[SELECTED], 0); + return TRUE; + } + + if (GTK_WIDGET_CLASS (gcal_date_chooser_day_parent_class)->key_press_event (widget, event)) + return TRUE; + + return FALSE; +} + +static void +gcal_date_chooser_day_dispose (GObject *object) +{ + GcalDateChooserDay *self = GCAL_DATE_CHOOSER_DAY (object); + + g_clear_object (&self->multipress_gesture); + + G_OBJECT_CLASS (gcal_date_chooser_day_parent_class)->dispose (object); +} + +static gboolean +gcal_date_chooser_day_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkStyleContext *context; + GtkStateFlags state; + gint x, y, width, height; + + context = gtk_widget_get_style_context (widget); + state = gtk_style_context_get_state (context); + + x = 0; + y = 0; + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + + gtk_render_background (context, cr, x, y, width, height); + gtk_render_frame (context, cr, x, y, width, height); + + GTK_WIDGET_CLASS (gcal_date_chooser_day_parent_class)->draw (widget, cr); + + if (gtk_widget_has_visible_focus (widget)) + { + GtkBorder border; + + gtk_style_context_get_border (context, state, &border); + gtk_render_focus (context, cr, border.left, border.top, + gtk_widget_get_allocated_width (widget) - border.left - border.right, + gtk_widget_get_allocated_height (widget) - border.top - border.bottom); + } + + return FALSE; +} + +static void +gcal_date_chooser_day_map (GtkWidget *widget) +{ + GcalDateChooserDay *self = GCAL_DATE_CHOOSER_DAY (widget); + + GTK_WIDGET_CLASS (gcal_date_chooser_day_parent_class)->map (widget); + + gdk_window_show (self->event_window); +} + +static void +gcal_date_chooser_day_unmap (GtkWidget *widget) +{ + GcalDateChooserDay *self = GCAL_DATE_CHOOSER_DAY (widget); + + gdk_window_hide (self->event_window); + + GTK_WIDGET_CLASS (gcal_date_chooser_day_parent_class)->unmap (widget); +} + +static void +gcal_date_chooser_day_realize (GtkWidget *widget) +{ + GcalDateChooserDay *self = GCAL_DATE_CHOOSER_DAY (widget); + GtkAllocation allocation; + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_set_realized (widget, TRUE); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_TOUCH_MASK; + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + window = gtk_widget_get_parent_window (widget); + gtk_widget_set_window (widget, window); + g_object_ref (window); + + self->event_window = gdk_window_new (window, &attributes, attributes_mask); + gtk_widget_register_window (widget, self->event_window); +} + +static void +gcal_date_chooser_day_unrealize (GtkWidget *widget) +{ + GcalDateChooserDay *self = GCAL_DATE_CHOOSER_DAY (widget); + + if (self->event_window) + { + gtk_widget_unregister_window (widget, self->event_window); + gdk_window_destroy (self->event_window); + self->event_window = NULL; + } + + GTK_WIDGET_CLASS (gcal_date_chooser_day_parent_class)->unrealize (widget); +} + +static void +gcal_date_chooser_day_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GcalDateChooserDay *self = GCAL_DATE_CHOOSER_DAY (widget); + + GTK_WIDGET_CLASS (gcal_date_chooser_day_parent_class)->size_allocate (widget, allocation); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (self->event_window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + } +} + +static void +gcal_date_chooser_day_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + GcalDateChooserDay *self = GCAL_DATE_CHOOSER_DAY (widget); + gchar *text; + + text = g_date_time_format (self->date, "%x"); + gtk_selection_data_set_text (selection_data, text, -1); + g_free (text); +} + +static void +gcal_date_chooser_day_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + GcalDateChooserDay *self; + GtkStyleContext *context; + GtkStateFlags flags; + GtkBorder border, margin, padding; + gint label_min, label_nat; + gint min_width, min, nat; + + self = GCAL_DATE_CHOOSER_DAY (widget); + context = gtk_widget_get_style_context (widget); + flags = gtk_style_context_get_state (context); + + gtk_style_context_get_border (context, flags, &border); + gtk_style_context_get_margin (context, flags, &margin); + gtk_style_context_get_padding (context, flags, &padding); + + gtk_style_context_get (context, + flags, + "min-width", &min_width, + NULL); + + gtk_widget_get_preferred_width (self->label, &label_min, &label_nat); + + min = label_min + margin.left + border.left + padding.left + margin.right + border.right + padding.right; + nat = label_nat + margin.left + border.left + padding.left + margin.right + border.right + padding.right; + + if (minimum) + *minimum = MAX (min, min_width); + + if (natural) + *natural = MAX (nat, min_width); +} + +static void +gcal_date_chooser_day_get_preferred_height (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + GcalDateChooserDay *self; + GtkStyleContext *context; + GtkStateFlags flags; + GtkBorder border, margin, padding; + gint label_min, label_nat; + gint min_height, min, nat; + + self = GCAL_DATE_CHOOSER_DAY (widget); + context = gtk_widget_get_style_context (widget); + flags = gtk_style_context_get_state (context); + + gtk_style_context_get_border (context, flags, &border); + gtk_style_context_get_margin (context, flags, &margin); + gtk_style_context_get_padding (context, flags, &padding); + + gtk_style_context_get (context, + flags, + "min-height", &min_height, + NULL); + + gtk_widget_get_preferred_height (self->label, &label_min, &label_nat); + + min = label_min + margin.top + border.top + padding.top + margin.bottom + border.bottom + padding.bottom; + nat = label_nat + margin.top + border.top + padding.top + margin.bottom + border.bottom + padding.bottom; + + if (minimum) + *minimum = MAX (min, min_height); + + if (natural) + *natural = MAX (nat, min_height); +} + +static void +gcal_date_chooser_day_class_init (GcalDateChooserDayClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->dispose = gcal_date_chooser_day_dispose; + + widget_class->draw = gcal_date_chooser_day_draw; + widget_class->realize = gcal_date_chooser_day_realize; + widget_class->unrealize = gcal_date_chooser_day_unrealize; + widget_class->map = gcal_date_chooser_day_map; + widget_class->unmap = gcal_date_chooser_day_unmap; + widget_class->key_press_event = gcal_date_chooser_day_key_press; + widget_class->size_allocate = gcal_date_chooser_day_size_allocate; + widget_class->drag_data_get = gcal_date_chooser_day_drag_data_get; + widget_class->get_preferred_width = gcal_date_chooser_day_get_preferred_width; + widget_class->get_preferred_height = gcal_date_chooser_day_get_preferred_height; + + signals[SELECTED] = g_signal_new ("selected", + GCAL_TYPE_DATE_CHOOSER_DAY, + G_SIGNAL_RUN_FIRST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, 0); + + gtk_widget_class_set_css_name (widget_class, "day"); +} + +static void +gcal_date_chooser_day_init (GcalDateChooserDay *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + + gtk_widget_set_can_focus (widget, TRUE); + gtk_style_context_add_class (gtk_widget_get_style_context (widget), "day"); + + self->label = gtk_label_new (""); + gtk_widget_show (self->label); + gtk_widget_set_halign (self->label, GTK_ALIGN_CENTER); + gtk_widget_set_valign (self->label, GTK_ALIGN_CENTER); + gtk_widget_set_hexpand (self->label, TRUE); + gtk_widget_set_vexpand (self->label, TRUE); + + gtk_container_add (GTK_CONTAINER (self), self->label); + + self->multipress_gesture = gtk_gesture_multi_press_new (widget); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->multipress_gesture), 0); + + g_signal_connect (self->multipress_gesture, + "pressed", + G_CALLBACK (day_pressed), + self); +} + +GtkWidget* +gcal_date_chooser_day_new (void) +{ + return g_object_new (GCAL_TYPE_DATE_CHOOSER_DAY, NULL); +} + +void +gcal_date_chooser_day_set_date (GcalDateChooserDay *self, + GDateTime *date) +{ + gchar *text; + + g_clear_pointer (&self->date, g_date_time_unref); + self->date = g_date_time_ref (date); + + text = g_strdup_printf ("%d", g_date_time_get_day_of_month (date)); + gtk_label_set_label (GTK_LABEL (self->label), text); + g_free (text); +} + +GDateTime* +gcal_date_chooser_day_get_date (GcalDateChooserDay *self) +{ + return self->date; +} + +void +gcal_date_chooser_day_set_other_month (GcalDateChooserDay *self, + gboolean other_month) +{ + GtkStyleContext *context; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + + if (other_month) + { + gtk_style_context_add_class (context, GTK_STYLE_CLASS_DIM_LABEL); + gtk_drag_source_unset (GTK_WIDGET (self)); + } + else + { + gtk_style_context_remove_class (context, GTK_STYLE_CLASS_DIM_LABEL); + gtk_drag_source_set (GTK_WIDGET (self), + GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, + NULL, 0, + GDK_ACTION_COPY); + gtk_drag_source_add_text_targets (GTK_WIDGET (self)); + } +} + +void +gcal_date_chooser_day_set_selected (GcalDateChooserDay *self, + gboolean selected) +{ + if (selected) + gtk_widget_set_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED, FALSE); + else + gtk_widget_unset_state_flags (GTK_WIDGET (self), GTK_STATE_FLAG_SELECTED); +} + +void +gcal_date_chooser_day_set_options (GcalDateChooserDay *self, + GcalDateChooserDayOptions options) +{ + GtkStyleContext *context; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + + if (options & GCAL_DATE_CHOOSER_DAY_WEEKEND) + gtk_style_context_add_class (context, "weekend"); + else + gtk_style_context_remove_class (context, "weekend"); + + if (options & GCAL_DATE_CHOOSER_DAY_HOLIDAY) + gtk_style_context_add_class (context, "holiday"); + else + gtk_style_context_remove_class (context, "holiday"); + + if (options & GCAL_DATE_CHOOSER_DAY_MARKED) + gtk_style_context_add_class (context, "marked"); + else + gtk_style_context_remove_class (context, "marked"); +} diff --git a/src/gcal-date-chooser-day.h b/src/gcal-date-chooser-day.h new file mode 100644 index 00000000..afb18c27 --- /dev/null +++ b/src/gcal-date-chooser-day.h @@ -0,0 +1,50 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2015 Red Hat, Inc. + * + * This library 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) any later version. + * + * This library 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 this library. If not, see . + */ + +#ifndef __GCAL_DATE_CHOOSER_DAY_PRIVATE_H__ +#define __GCAL_DATE_CHOOSER_DAY_PRIVATE_H__ + +#include "gcal-date-chooser.h" + +#include +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_DATE_CHOOSER_DAY (gcal_date_chooser_day_get_type()) + +G_DECLARE_FINAL_TYPE (GcalDateChooserDay, gcal_date_chooser_day, GCAL, DATE_CHOOSER_DAY, GtkBin) + +GtkWidget* gcal_date_chooser_day_new (void); + +GDateTime* gcal_date_chooser_day_get_date (GcalDateChooserDay *day); + +void gcal_date_chooser_day_set_date (GcalDateChooserDay *day, + GDateTime *date); + +void gcal_date_chooser_day_set_other_month (GcalDateChooserDay *day, + gboolean other_month); + +void gcal_date_chooser_day_set_selected (GcalDateChooserDay *day, + gboolean selected); + +void gcal_date_chooser_day_set_options (GcalDateChooserDay *day, + GcalDateChooserDayOptions options); + +G_END_DECLS + +#endif /* __GCAL_DATE_CHOOSER_DAY_PRIVATE_H__ */ diff --git a/src/gcal-date-chooser.c b/src/gcal-date-chooser.c new file mode 100644 index 00000000..656f9b65 --- /dev/null +++ b/src/gcal-date-chooser.c @@ -0,0 +1,817 @@ +/* GTK - The GIMP Toolkit + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This library 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) any later version. + * + * This library 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 this library. If not, see . + */ + +#include "config.h" + +#include "gcal-utils.h" +#include "gcal-date-chooser.h" +#include "gcal-date-chooser-day.h" +#include "gcal-multi-choice.h" + +#include +#include + +struct _GcalDateChooser +{ + GtkBin parent; + + GtkWidget *month_choice; + GtkWidget *year_choice; + GtkWidget *grid; + + GtkWidget *day_grid; + GtkWidget *corner; + GtkWidget *cols[7]; + GtkWidget *rows[6]; + GtkWidget *days[6][7]; + + GDateTime *date; + + gint this_year; + gint week_start; + + gboolean show_heading; + gboolean show_day_names; + gboolean show_week_numbers; + gboolean no_month_change; + + GcalDateChooserDayOptionsCallback day_options_cb; + gpointer day_options_data; + GDestroyNotify day_options_destroy; +}; + +G_DEFINE_TYPE (GcalDateChooser, gcal_date_chooser, GTK_TYPE_BIN) + +enum +{ + MONTH_CHANGED, + DAY_SELECTED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_DATE, + PROP_SHOW_HEADING, + PROP_SHOW_DAY_NAMES, + PROP_SHOW_WEEK_NUMBERS, + PROP_NO_MONTH_CHANGE, + NUM_PROPERTIES +}; + +static guint signals[LAST_SIGNAL] = { 0, }; +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +static const guint month_length[2][13] = +{ + { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static gboolean +leap (guint year) +{ + return ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0)); +} + +static void +calendar_compute_days (GcalDateChooser *self) +{ + GcalDateChooserDay *d; + GDateTime *date; + gint year, month, day; + gint other_year, other_month; + gint ndays_in_month; + gint ndays_in_prev_month; + gint first_day; + gint row; + gint col; + + g_date_time_get_ymd (self->date, &year, &month, NULL); + + ndays_in_month = month_length[leap (year)][month]; + + date = g_date_time_new_local (year, month, 1, 1, 1, 1); + first_day = g_date_time_get_day_of_week (date); + g_date_time_unref (date); + + first_day = (first_day + 7 - self->week_start) % 7; + if (first_day == 0) + first_day = 7; + + /* Compute days of previous month */ + if (month > 1) + ndays_in_prev_month = month_length[leap (year)][month - 1]; + else + ndays_in_prev_month = month_length[leap (year - 1)][12]; + day = ndays_in_prev_month - first_day + 1; + + other_year = year; + other_month = month - 1; + if (other_month == 0) + { + other_month = 12; + other_year -= 1; + } + + for (col = 0; col < first_day; col++) + { + d = GCAL_DATE_CHOOSER_DAY (self->days[0][col]); + date = g_date_time_new_local (other_year, other_month, day, 1, 1, 1); + gcal_date_chooser_day_set_date (d, date); + gcal_date_chooser_day_set_other_month (d, TRUE); + g_date_time_unref (date); + day++; + } + + /* Compute days of current month */ + row = first_day / 7; + col = first_day % 7; + + for (day = 1; day <= ndays_in_month; day++) + { + d = GCAL_DATE_CHOOSER_DAY (self->days[row][col]); + date = g_date_time_new_local (year, month, day, 1, 1, 1); + gcal_date_chooser_day_set_date (d, date); + gcal_date_chooser_day_set_other_month (d, FALSE); + g_date_time_unref (date); + + col++; + if (col == 7) + { + row++; + col = 0; + } + } + + /* Compute days of next month */ + + other_year = year; + other_month = month + 1; + if (other_month == 13) + { + other_month = 1; + other_year += 1; + } + + day = 1; + for (; row < 6; row++) + { + for (; col < 7; col++) + { + d = GCAL_DATE_CHOOSER_DAY (self->days[row][col]); + date = g_date_time_new_local (other_year, other_month, day, 1, 1, 1); + gcal_date_chooser_day_set_date (d, date); + gcal_date_chooser_day_set_other_month (d, TRUE); + g_date_time_unref (date); + day++; + } + col = 0; + } + + /* update week numbers */ + for (row = 0; row < 6; row++) + { + gchar *text; + + d = GCAL_DATE_CHOOSER_DAY (self->days[row][6]); + date = gcal_date_chooser_day_get_date (d); + text = g_strdup_printf ("%d", g_date_time_get_week_of_year (date)); + gtk_label_set_label (GTK_LABEL (self->rows[row]), text); + g_free (text); + } + + gcal_date_chooser_invalidate_day_options (self); +} + +/* 0 == sunday */ +static gchar * +calendar_get_weekday_name (gint i) +{ + GDateTime *date; + gchar *text; + + date = g_date_time_new_local (2015, 1, 4 + i, 1, 1, 1); + text = g_date_time_format (date, "%a"); + g_date_time_unref (date); + + return text; +} + +/* 0 = january */ +static gchar * +calendar_get_month_name (gint i) +{ + GDateTime *date; + gchar *text; + + date = g_date_time_new_local (2015, i + 1, 1, 1, 1, 1); + text = g_date_time_format (date, "%B"); + g_date_time_unref (date); + + return text; +} + +static void +calendar_init_weekday_display (GcalDateChooser *self) +{ + gint i; + gchar *text; + + for (i = 0; i < 7; i++) + { + text = calendar_get_weekday_name ((i + self->week_start) % 7); + gtk_label_set_label (GTK_LABEL (self->cols[i]), text); + g_free (text); + } +} + +static gchar * +format_month (GcalMultiChoice *choice, + gint value, + gpointer data) +{ + return calendar_get_month_name (value); +} + +static void +calendar_init_month_display (GcalDateChooser *self) +{ + gchar *months[13]; + gchar *month; + gint i; + + for (i = 0; i < 12; i++) + { + month = calendar_get_month_name (i); + months[i] = g_strdup_printf ("%s", month); + g_free (month); + } + + months[12] = NULL; + + gcal_multi_choice_set_choices (GCAL_MULTI_CHOICE (self->month_choice), + (const gchar**) months); + + for (i = 0; i < 12; i++) + g_free (months[i]); + + gcal_multi_choice_set_format_callback (GCAL_MULTI_CHOICE (self->month_choice), + format_month, + self, + NULL); +} + +static void +calendar_update_selected_day_display (GcalDateChooser *self) +{ + GcalDateChooserDay *d; + GDateTime *date; + gint row, col; + + for (row = 0; row < 6; row++) + { + for (col = 0; col < 7; col++) + { + d = GCAL_DATE_CHOOSER_DAY (self->days[row][col]); + date = gcal_date_chooser_day_get_date (d); + gcal_date_chooser_day_set_selected (d, datetime_compare_date (date, self->date) == 0); + } + } +} + +static void +calendar_update_selected_day (GcalDateChooser *self) +{ + GDateTime *date; + gint month_len; + gint year, month, day; + + g_date_time_get_ymd (self->date, &year, &month, &day); + + month_len = month_length[leap (year)][month]; + + if (month_len < day) + { + date = g_date_time_new_local (year, month, month_len, 1, 1, 1); + gcal_date_chooser_set_date (self, date); + g_date_time_unref (date); + } + else + { + calendar_update_selected_day_display (self); + } +} + +static gint +calendar_get_week_start (void) +{ + union { unsigned int word; char *string; } langinfo; + gint week_1stday = 0; + gint first_weekday = 1; + guint week_origin; + + langinfo.string = nl_langinfo (_NL_TIME_FIRST_WEEKDAY); + first_weekday = langinfo.string[0]; + langinfo.string = nl_langinfo (_NL_TIME_WEEK_1STDAY); + week_origin = langinfo.word; + + if (week_origin == 19971130) /* Sunday */ + week_1stday = 0; + else if (week_origin == 19971201) /* Monday */ + week_1stday = 1; + else + g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY."); + + return (week_1stday + first_weekday - 1) % 7; +} + +static void +day_selected_cb (GcalDateChooserDay *d, + GcalDateChooser *self) +{ + gcal_date_chooser_set_date (self, gcal_date_chooser_day_get_date (d)); +} + +static void +calendar_set_property (GObject *obj, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalDateChooser *self = GCAL_DATE_CHOOSER (obj); + + switch (property_id) + { + case PROP_DATE: + gcal_date_chooser_set_date (self, g_value_get_boxed (value)); + break; + + case PROP_SHOW_HEADING: + gcal_date_chooser_set_show_heading (self, g_value_get_boolean (value)); + break; + + case PROP_SHOW_DAY_NAMES: + gcal_date_chooser_set_show_day_names (self, g_value_get_boolean (value)); + break; + + case PROP_SHOW_WEEK_NUMBERS: + gcal_date_chooser_set_show_week_numbers (self, g_value_get_boolean (value)); + break; + + case PROP_NO_MONTH_CHANGE: + gcal_date_chooser_set_no_month_change (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); + break; + } +} + +static void +calendar_get_property (GObject *obj, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GcalDateChooser *self = GCAL_DATE_CHOOSER (obj); + + switch (property_id) + { + case PROP_DATE: + g_value_set_boxed (value, self->date); + break; + + case PROP_SHOW_HEADING: + g_value_set_boolean (value, self->show_heading); + break; + + case PROP_SHOW_DAY_NAMES: + g_value_set_boolean (value, self->show_day_names); + break; + + case PROP_SHOW_WEEK_NUMBERS: + g_value_set_boolean (value, self->show_week_numbers); + break; + + case PROP_NO_MONTH_CHANGE: + g_value_set_boolean (value, self->no_month_change); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); + break; + } +} + +static void +multi_choice_changed (GcalDateChooser *self) +{ + GDateTime *date; + gint year, month, day; + + year = gcal_multi_choice_get_value (GCAL_MULTI_CHOICE (self->year_choice)); + month = gcal_multi_choice_get_value (GCAL_MULTI_CHOICE (self->month_choice)) + 1; + g_date_time_get_ymd (self->date, NULL, NULL, &day); + + date = g_date_time_new_local (year, month, day, 1, 1, 1); + gcal_date_chooser_set_date (self, date); + g_date_time_unref (date); +} + +static void +calendar_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + GcalDateChooser *self = GCAL_DATE_CHOOSER (widget); + gchar *text; + GDate *gdate; + gint year, month, day; + GDateTime *date; + + gdate = g_date_new (); + text = (gchar *)gtk_selection_data_get_text (selection_data); + if (text) + { + g_date_set_parse (gdate, text); + g_free (text); + } + if (!g_date_valid (gdate)) + { + g_date_free (gdate); + gtk_drag_finish (context, FALSE, FALSE, time); + return; + } + + year = g_date_get_year (gdate); + month = g_date_get_month (gdate); + day = g_date_get_day (gdate); + + g_date_free (gdate); + + gtk_drag_finish (context, TRUE, FALSE, time); + + if (!self->show_heading || self->no_month_change) + g_date_time_get_ymd (self->date, &year, &month, NULL); + + date = g_date_time_new_local (year, month, day, 1, 1, 1); + gcal_date_chooser_set_date (self, date); + g_date_time_unref (date); +} + +static void +gcal_date_chooser_class_init (GcalDateChooserClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + g_type_ensure (GCAL_TYPE_MULTI_CHOICE); + + object_class->set_property = calendar_set_property; + object_class->get_property = calendar_get_property; + + widget_class->drag_data_received = calendar_drag_data_received; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/date-chooser.ui"); + + properties[PROP_DATE] = g_param_spec_boxed ("date", + "Date", + "The selected date", + G_TYPE_DATE_TIME, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + properties[PROP_SHOW_HEADING] = g_param_spec_boolean ("show-heading", + "Show Heading", + "If TRUE, a heading is displayed", + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + properties[PROP_SHOW_DAY_NAMES] = g_param_spec_boolean ("show-day-names", + "Show Day Names", + "If TRUE, day names are displayed", + TRUE, + G_PARAM_READWRITE); + + properties[PROP_SHOW_WEEK_NUMBERS] = g_param_spec_boolean ("show-week-numbers", + "Show Week Numbers", + "If TRUE, week numbers are displayed", + TRUE, + G_PARAM_READWRITE); + + properties[PROP_NO_MONTH_CHANGE] = g_param_spec_boolean ("no-month-change", + "No Month Change", + "If TRUE, the selected month cannot be changed", + FALSE, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); + + signals[MONTH_CHANGED] = g_signal_new ("month-changed", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + signals[DAY_SELECTED] = g_signal_new ("day-selected", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, month_choice); + gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, year_choice); + gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, grid); + + gtk_widget_class_bind_template_callback (widget_class, multi_choice_changed); + + gtk_widget_class_set_css_name (widget_class, "datechooser"); +} + + +static void +gcal_date_chooser_init (GcalDateChooser *self) +{ + GtkWidget *label; + gint row, col; + gint year, month; + + self->show_heading = TRUE; + self->show_day_names = TRUE; + self->show_week_numbers = TRUE; + self->no_month_change = FALSE; + + self->date = g_date_time_new_now_local (); + g_date_time_get_ymd (self->date, &self->this_year, NULL, NULL); + + self->week_start = calendar_get_week_start (); + + gtk_widget_init_template (GTK_WIDGET (self)); + + for (col = 0; col < 7; col++) + { + self->cols[col] = gtk_label_new (""); + + g_object_bind_property (self, + "show-day-names", + self->cols[col], + "visible", + G_BINDING_SYNC_CREATE); + + gtk_style_context_add_class (gtk_widget_get_style_context (self->cols[col]), "weekday"); + + gtk_grid_attach (GTK_GRID (self->grid), self->cols[col], col, -1, 1, 1); + } + + for (row = 0; row < 6; row++) + { + self->rows[row] = gtk_label_new (""); + + g_object_bind_property (self, + "show-week-numbers", + self->rows[row], + "visible", + G_BINDING_SYNC_CREATE); + + gtk_widget_show (self->rows[row]); + gtk_style_context_add_class (gtk_widget_get_style_context (self->rows[row]), "weeknum"); + gtk_grid_attach (GTK_GRID (self->grid), self->rows[row], -1, row, 1, 1); + } + + /* We are using a stack here to keep the week number column from shrinking + * when all the weeks are single-digit + */ + self->corner = gtk_stack_new (); + gtk_grid_attach (GTK_GRID (self->grid), self->corner, -1, -1, 1, 1); + label = gtk_label_new (""); + gtk_widget_show (label); + gtk_style_context_add_class (gtk_widget_get_style_context (label), "weekday"); + gtk_container_add (GTK_CONTAINER (self->corner), label); + + label = gtk_label_new ("99"); + gtk_style_context_add_class (gtk_widget_get_style_context (label), "weeknum"); + gtk_container_add (GTK_CONTAINER (self->corner), label); + + self->day_grid = g_object_new (GTK_TYPE_GRID, + "valign", GTK_ALIGN_FILL, + "halign", GTK_ALIGN_FILL, + "row-spacing", 2, + "column-spacing", 2, + "visible", TRUE, + NULL); + gtk_grid_attach (GTK_GRID (self->grid), self->day_grid, 0, 0, 7, 6); + + for (row = 0; row < 6; row++) + { + for (col = 0; col < 7; col++) + { + self->days[row][col] = gcal_date_chooser_day_new (); + + g_signal_connect (self->days[row][col], + "selected", + G_CALLBACK (day_selected_cb), + self); + + gtk_widget_show (self->days[row][col]); + gtk_grid_attach (GTK_GRID (self->day_grid), self->days[row][col], col, row, 1, 1); + } + } + + calendar_init_month_display (self); + calendar_init_weekday_display (self); + + calendar_compute_days (self); + g_date_time_get_ymd (self->date, &year, &month, NULL); + gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->year_choice), year); + gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->month_choice), month - 1); + calendar_update_selected_day_display (self); + + gtk_drag_dest_set (GTK_WIDGET (self), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY); + gtk_drag_dest_add_text_targets (GTK_WIDGET (self)); +} + +GtkWidget* +gcal_date_chooser_new (void) +{ + return g_object_new (GCAL_TYPE_DATE_CHOOSER, NULL); +} + +void +gcal_date_chooser_set_show_heading (GcalDateChooser *self, + gboolean setting) +{ + if (self->show_heading == setting) + return; + + self->show_heading = setting; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_HEADING]); +} + +gboolean +gcal_date_chooser_get_show_heading (GcalDateChooser *self) +{ + return self->show_heading; +} + +void +gcal_date_chooser_set_show_day_names (GcalDateChooser *self, + gboolean setting) +{ + if (self->show_day_names == setting) + return; + + self->show_day_names = setting; + + gtk_widget_set_visible (self->corner, self->show_day_names && self->show_week_numbers); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_DAY_NAMES]); +} + +gboolean +gcal_date_chooser_get_show_day_names (GcalDateChooser *self) +{ + return self->show_day_names; +} + +void +gcal_date_chooser_set_show_week_numbers (GcalDateChooser *self, + gboolean setting) +{ + if (self->show_week_numbers == setting) + return; + + self->show_week_numbers = setting; + + gtk_widget_set_visible (self->corner, self->show_day_names && self->show_week_numbers); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_WEEK_NUMBERS]); +} + +gboolean +gcal_date_chooser_get_show_week_numbers (GcalDateChooser *self) +{ + return self->show_week_numbers; +} + +void +gcal_date_chooser_set_no_month_change (GcalDateChooser *self, + gboolean setting) +{ + if (self->no_month_change == setting) + return; + + self->no_month_change = setting; + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NO_MONTH_CHANGE]); +} + +gboolean +gcal_date_chooser_get_no_month_change (GcalDateChooser *self) +{ + return self->no_month_change; +} + +void +gcal_date_chooser_set_date (GcalDateChooser *self, + GDateTime *date) +{ + gint y1, m1, d1, y2, m2, d2; + + g_object_freeze_notify (G_OBJECT (self)); + + g_date_time_get_ymd (self->date, &y1, &m1, &d1); + g_date_time_get_ymd (date, &y2, &m2, &d2); + + g_date_time_unref (self->date); + self->date = g_date_time_ref (date); + + if (y1 != y2 || m1 != m2) + { + gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->year_choice), y2); + gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->month_choice), m2 - 1); + calendar_compute_days (self); + } + + if (y1 != y2 || m1 != m2 || d1 != d2) + { + calendar_update_selected_day (self); + g_signal_emit (self, signals[DAY_SELECTED], 0); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DATE]); + } + + g_object_thaw_notify (G_OBJECT (self)); +} + +GDateTime * +gcal_date_chooser_get_date (GcalDateChooser *self) +{ + return self->date; +} + +void +gcal_date_chooser_set_day_options_callback (GcalDateChooser *self, + GcalDateChooserDayOptionsCallback callback, + gpointer data, + GDestroyNotify destroy) +{ + if (self->day_options_destroy) + self->day_options_destroy (self->day_options_data); + + self->day_options_cb = callback; + self->day_options_data = data; + self->day_options_destroy = destroy; + + gcal_date_chooser_invalidate_day_options (self); +} + + +void +gcal_date_chooser_invalidate_day_options (GcalDateChooser *self) +{ + GcalDateChooserDayOptions options; + GcalDateChooserDay *d; + GDateTime *date; + gint row, col; + + for (row = 0; row < 6; row++) + { + for (col = 0; col < 7; col++) + { + d = GCAL_DATE_CHOOSER_DAY (self->days[row][col]); + date = gcal_date_chooser_day_get_date (d); + + if (self->day_options_cb) + options = self->day_options_cb (self, date, self->day_options_data); + else + options = GCAL_DATE_CHOOSER_DAY_NONE; + + gcal_date_chooser_day_set_options (d, options); + } + } +} diff --git a/src/gcal-date-chooser.h b/src/gcal-date-chooser.h new file mode 100644 index 00000000..cbbdf1d0 --- /dev/null +++ b/src/gcal-date-chooser.h @@ -0,0 +1,77 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2015 Red Hat, Inc. + * + * This library 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) any later version. + * + * This library 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 this library. If not, see . + */ + +#ifndef __GCAL_DATE_CHOOSER_H__ +#define __GCAL_DATE_CHOOSER_H__ + +#include + +G_BEGIN_DECLS + +typedef enum +{ + GCAL_DATE_CHOOSER_DAY_NONE = 1 << 0, + GCAL_DATE_CHOOSER_DAY_WEEKEND = 1 << 1, + GCAL_DATE_CHOOSER_DAY_HOLIDAY = 1 << 2, + GCAL_DATE_CHOOSER_DAY_MARKED = 1 << 3 +} GcalDateChooserDayOptions; + +#define GCAL_TYPE_DATE_CHOOSER (gcal_date_chooser_get_type ()) + +G_DECLARE_FINAL_TYPE (GcalDateChooser, gcal_date_chooser, GCAL, DATE_CHOOSER, GtkBin) + +typedef GcalDateChooserDayOptions (*GcalDateChooserDayOptionsCallback) (GcalDateChooser *self, + GDateTime *date, + gpointer user_data); + +GtkWidget* gcal_date_chooser_new (void); + +GDateTime* gcal_date_chooser_get_date (GcalDateChooser *self); + +void gcal_date_chooser_set_date (GcalDateChooser *self, + GDateTime *date); + +void gcal_date_chooser_set_day_options_callback (GcalDateChooser *self, + GcalDateChooserDayOptionsCallback callback, + gpointer data, + GDestroyNotify destroy); + +void gcal_date_chooser_invalidate_day_options (GcalDateChooser *self); + +gboolean gcal_date_chooser_get_no_month_change (GcalDateChooser *self); + +void gcal_date_chooser_set_no_month_change (GcalDateChooser *self, + gboolean setting); + +gboolean gcal_date_chooser_get_show_heading (GcalDateChooser *self); + +void gcal_date_chooser_set_show_heading (GcalDateChooser *self, + gboolean setting); + +gboolean gcal_date_chooser_get_show_day_names (GcalDateChooser *self); + +void gcal_date_chooser_set_show_day_names (GcalDateChooser *self, + gboolean setting); + +gboolean gcal_date_chooser_get_show_week_numbers (GcalDateChooser *self); + +void gcal_date_chooser_set_show_week_numbers (GcalDateChooser *self, + gboolean setting); + +G_END_DECLS + +#endif /* __GCAL_DATE_CHOOSER_H__ */ diff --git a/src/gcal-multi-choice.c b/src/gcal-multi-choice.c new file mode 100644 index 00000000..bfcb19e8 --- /dev/null +++ b/src/gcal-multi-choice.c @@ -0,0 +1,481 @@ +/* GTK - The GIMP Toolkit + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This library 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) any later version. + * + * This library 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 this library. If not, see . + */ + +#include "config.h" + +#include "gcal-multi-choice.h" + +struct _GcalMultiChoice +{ + GtkBox parent; + GtkWidget *down_button; + GtkWidget *stack; + GtkWidget *up_button; + gint value; + gint min_value; + gint max_value; + gboolean wrap; + gboolean animate; + GtkWidget **choices; + gint n_choices; + guint click_id; + GtkWidget *active; + GtkWidget *label1; + GtkWidget *label2; + GcalMultiChoiceFormatCallback format_cb; + gpointer format_data; + GDestroyNotify format_destroy; +}; + +enum +{ + PROP_VALUE = 1, + PROP_MIN_VALUE, + PROP_MAX_VALUE, + PROP_WRAP, + PROP_ANIMATE, + PROP_CHOICES, + NUM_PROPERTIES +}; + +enum +{ + WRAPPED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; +static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; + +G_DEFINE_TYPE (GcalMultiChoice, gcal_multi_choice, GTK_TYPE_BOX) + +static gchar * +get_value_string (GcalMultiChoice *self, + gint value) +{ + if (self->format_cb) + return self->format_cb (self, value, self->format_data); + else if (0 <= value && value < self->n_choices) + return g_strdup (gtk_label_get_label (GTK_LABEL (self->choices[value]))); + else + return g_strdup_printf ("%d", value); +} + +static void +set_value (GcalMultiChoice *self, + gint value, + GtkStackTransitionType transition) +{ + GtkWidget *label; + const gchar *name; + gchar *text; + + value = CLAMP (value, self->min_value, self->max_value); + + if (self->value == value) + return; + + self->value = value; + + if (gtk_stack_get_visible_child (GTK_STACK (self->stack)) == self->label1) + { + name = "label2"; + label = self->label2; + } + else + { + name = "label1"; + label = self->label1; + } + + text = get_value_string (self, value); + gtk_label_set_text (GTK_LABEL (label), text); + g_free (text); + + if (!self->animate) + transition = GTK_STACK_TRANSITION_TYPE_NONE; + + gtk_stack_set_visible_child_full (GTK_STACK (self->stack), name, transition); + + gtk_widget_set_sensitive (self->down_button, + self->wrap || self->value > self->min_value); + gtk_widget_set_sensitive (self->up_button, + self->wrap || self->value < self->max_value); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALUE]); +} + +static void +go_up (GcalMultiChoice *self) +{ + gboolean wrapped = FALSE; + gint value; + + value = self->value + 1; + if (value > self->max_value) + { + if (!self->wrap) + return; + + value = self->min_value; + wrapped = TRUE; + } + + set_value (self, value, GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT); + + if (wrapped) + g_signal_emit (self, signals[WRAPPED], 0); +} + +static void +go_down (GcalMultiChoice *self) +{ + gint value; + gboolean wrapped = FALSE; + + value = self->value - 1; + if (value < self->min_value) + { + if (!self->wrap) + return; + + value = self->max_value; + wrapped = TRUE; + } + + set_value (self, value, GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT); + + if (wrapped) + g_signal_emit (self, signals[WRAPPED], 0); +} + +static gboolean +button_activate (GcalMultiChoice *self, + GtkWidget *button) +{ + if (button == self->down_button) + go_down (self); + else if (button == self->up_button) + go_up (self); + else + g_assert_not_reached (); + + return TRUE; +} + +static gboolean +button_timeout (gpointer user_data) +{ + GcalMultiChoice *self = GCAL_MULTI_CHOICE (user_data); + gboolean res; + + if (self->click_id == 0) + return G_SOURCE_REMOVE; + + res = button_activate (self, self->active); + if (!res) + { + g_source_remove (self->click_id); + self->click_id = 0; + } + + return res; +} + +static gboolean +button_press_cb (GtkWidget *widget, + GdkEventButton *button, + GcalMultiChoice *self) +{ + gint double_click_time; + + if (button->type != GDK_BUTTON_PRESS) + return TRUE; + + g_object_get (gtk_widget_get_settings (widget), + "gtk-double-click-time", &double_click_time, + NULL); + + if (self->click_id != 0) + g_source_remove (self->click_id); + + self->active = widget; + + self->click_id = gdk_threads_add_timeout (double_click_time, + button_timeout, + self); + g_source_set_name_by_id (self->click_id, "[gtk+] button_timeout"); + button_timeout (self); + + return TRUE; +} + +static gboolean +button_release_cb (GtkWidget *widget, + GdkEventButton *event, + GcalMultiChoice *self) +{ + if (self->click_id != 0) + { + g_source_remove (self->click_id); + self->click_id = 0; + } + + self->active = NULL; + + return TRUE; +} + +static void +button_clicked_cb (GtkWidget *button, + GcalMultiChoice *self) +{ + if (self->click_id != 0) + return; + + button_activate (self, button); +} + +static void +gcal_multi_choice_init (GcalMultiChoice *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +gcal_multi_choice_dispose (GObject *object) +{ + GcalMultiChoice *self = GCAL_MULTI_CHOICE (object); + + if (self->click_id != 0) + { + g_source_remove (self->click_id); + self->click_id = 0; + } + + g_free (self->choices); + self->choices = NULL; + + if (self->format_destroy) + { + self->format_destroy (self->format_data); + self->format_destroy = NULL; + } + + G_OBJECT_CLASS (gcal_multi_choice_parent_class)->dispose (object); +} + +static void +gcal_multi_choice_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GcalMultiChoice *self = GCAL_MULTI_CHOICE (object); + + switch (property_id) + { + case PROP_VALUE: + g_value_set_int (value, self->value); + break; + + case PROP_MIN_VALUE: + g_value_set_int (value, self->min_value); + break; + + case PROP_MAX_VALUE: + g_value_set_int (value, self->max_value); + break; + + case PROP_WRAP: + g_value_set_boolean (value, self->wrap); + break; + + case PROP_ANIMATE: + g_value_set_boolean (value, self->animate); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gcal_multi_choice_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalMultiChoice *self = GCAL_MULTI_CHOICE (object); + + switch (property_id) + { + case PROP_VALUE: + gcal_multi_choice_set_value (self, g_value_get_int (value)); + break; + + case PROP_MIN_VALUE: + self->min_value = g_value_get_int (value); + g_object_notify_by_pspec (object, properties[PROP_MIN_VALUE]); + gcal_multi_choice_set_value (self, self->value); + break; + + case PROP_MAX_VALUE: + self->max_value = g_value_get_int (value); + g_object_notify_by_pspec (object, properties[PROP_MAX_VALUE]); + gcal_multi_choice_set_value (self, self->value); + break; + + case PROP_WRAP: + self->wrap = g_value_get_boolean (value); + g_object_notify_by_pspec (object, properties[PROP_WRAP]); + break; + + case PROP_ANIMATE: + self->animate = g_value_get_boolean (value); + g_object_notify_by_pspec (object, properties[PROP_ANIMATE]); + break; + + case PROP_CHOICES: + gcal_multi_choice_set_choices (self, (const gchar **)g_value_get_boxed (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gcal_multi_choice_class_init (GcalMultiChoiceClass *class) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->dispose = gcal_multi_choice_dispose; + object_class->set_property = gcal_multi_choice_set_property; + object_class->get_property = gcal_multi_choice_get_property; + + properties[PROP_VALUE] = + g_param_spec_int ("value", "Value", "Value", + G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_MIN_VALUE] = + g_param_spec_int ("min-value", "Minimum Value", "Minimum Value", + G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_MAX_VALUE] = + g_param_spec_int ("max-value", "Maximum Value", "Maximum Value", + G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_WRAP] = + g_param_spec_boolean ("wrap", "Wrap", "Wrap", + FALSE, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_ANIMATE] = + g_param_spec_boolean ("animate", "Animate", "Animate", + FALSE, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_CHOICES] = + g_param_spec_boxed ("choices", "Choices", "Choices", + G_TYPE_STRV, + G_PARAM_WRITABLE|G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); + + signals[WRAPPED] = + g_signal_new ("wrapped", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/multi-choice.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, down_button); + gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, up_button); + gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, stack); + gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, label1); + gtk_widget_class_bind_template_child (widget_class, GcalMultiChoice, label2); + + gtk_widget_class_bind_template_callback (widget_class, button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, button_press_cb); + gtk_widget_class_bind_template_callback (widget_class, button_release_cb); + + gtk_widget_class_set_css_name (widget_class, "navigator"); +} + +GtkWidget * +gcal_multi_choice_new (void) +{ + return GTK_WIDGET (g_object_new (GCAL_TYPE_MULTI_CHOICE, NULL)); +} + +void +gcal_multi_choice_set_value (GcalMultiChoice *self, + gint value) +{ + set_value (self, value, GTK_STACK_TRANSITION_TYPE_NONE); +} + +gint +gcal_multi_choice_get_value (GcalMultiChoice *self) +{ + return self->value; +} + +void +gcal_multi_choice_set_choices (GcalMultiChoice *self, + const gchar **choices) +{ + gint i; + + for (i = 0; i < self->n_choices; i++) + gtk_container_remove (GTK_CONTAINER (self->stack), self->choices[i]); + g_free (self->choices); + + self->n_choices = g_strv_length ((gchar **)choices); + self->choices = g_new (GtkWidget *, self->n_choices); + for (i = 0; i < self->n_choices; i++) + { + self->choices[i] = gtk_label_new (choices[i]); + gtk_widget_show (self->choices[i]); + gtk_stack_add_named (GTK_STACK (self->stack), + self->choices[i], + choices[i]); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHOICES]); +} + +void +gcal_multi_choice_set_format_callback (GcalMultiChoice *self, + GcalMultiChoiceFormatCallback callback, + gpointer user_data, + GDestroyNotify destroy) +{ + if (self->format_destroy) + self->format_destroy (self->format_data); + + self->format_cb = callback; + self->format_data = user_data; + self->format_destroy = destroy; +} diff --git a/src/gcal-multi-choice.h b/src/gcal-multi-choice.h new file mode 100644 index 00000000..9b3377ef --- /dev/null +++ b/src/gcal-multi-choice.h @@ -0,0 +1,51 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2015 Red Hat, Inc. + * + * This library 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) any later version. + * + * This library 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 this library. If not, see . + */ + +#ifndef GCAL_MULTI_CHOICE_H +#define GCAL_MULTI_CHOICE_H + +#include +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_MULTI_CHOICE (gcal_multi_choice_get_type()) + +G_DECLARE_FINAL_TYPE (GcalMultiChoice, gcal_multi_choice, GCAL, MULTI_CHOICE, GtkBox) + +GtkWidget* gcal_multi_choice_new (void); + +gint gcal_multi_choice_get_value (GcalMultiChoice *self); + +void gcal_multi_choice_set_value (GcalMultiChoice *self, + gint value); + +void gcal_multi_choice_set_choices (GcalMultiChoice *self, + const gchar **selfs); + +typedef gchar* (*GcalMultiChoiceFormatCallback) (GcalMultiChoice *self, + gint value, + gpointer user_data); + +void gcal_multi_choice_set_format_callback (GcalMultiChoice *self, + GcalMultiChoiceFormatCallback callback, + gpointer user_data, + GDestroyNotify notify); + +G_END_DECLS + +#endif /* GCAL_MULTI_CHOICE_H */