[gnome-calendar/wip/gbsneto/date-chooser: 1/7] date-chooser: add date chooser widget



commit c2dca8a3dcb598e2b6f1c9dfe219fe4870bf4781
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Thu Jul 21 17:00:22 2016 -0300

    date-chooser: add date chooser widget
    
    This is mainly a copy-paste from an old branch authored
    by Matthias Clasen. The code style was updated to match
    Calendar's one, and the theming was improved a little bit.

 data/Makefile.am            |    2 +
 data/calendar.gresource.xml |    2 +
 data/theme/gtk-styles.css   |   31 ++
 data/ui/date-chooser.ui     |   54 +++
 data/ui/multi-choice.ui     |   71 ++++
 src/Makefile.am             |    6 +
 src/gcal-date-chooser-day.c |  443 +++++++++++++++++++++++
 src/gcal-date-chooser-day.h |   50 +++
 src/gcal-date-chooser.c     |  817 +++++++++++++++++++++++++++++++++++++++++++
 src/gcal-date-chooser.h     |   77 ++++
 src/gcal-multi-choice.c     |  481 +++++++++++++++++++++++++
 src/gcal-multi-choice.h     |   51 +++
 12 files changed, 2085 insertions(+), 0 deletions(-)
---
diff --git a/data/Makefile.am b/data/Makefile.am
index 54b4c98..046d240 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 f083f3f..7c64779 100644
--- a/data/calendar.gresource.xml
+++ b/data/calendar.gresource.xml
@@ -3,8 +3,10 @@
   <gresource prefix="/org/gnome/calendar">
     <file alias="alarm-row.ui" compressed="true" preprocess="xml-stripblanks">ui/alarm-row.ui</file>
     <file alias="calendar-row.ui" compressed="true" preprocess="xml-stripblanks">ui/calendar-row.ui</file>
+    <file alias="date-chooser.ui" compressed="true" preprocess="xml-stripblanks">ui/date-chooser.ui</file>
     <file alias="date-selector.ui" compressed="true" preprocess="xml-stripblanks">ui/date-selector.ui</file>
     <file alias="edit-dialog.ui" compressed="true" preprocess="xml-stripblanks">ui/edit-dialog.ui</file>
+    <file alias="multi-choice.ui" compressed="true" preprocess="xml-stripblanks">ui/multi-choice.ui</file>
     <file alias="online-account-row.ui" compressed="true" 
preprocess="xml-stripblanks">ui/online-account-row.ui</file>
     <file alias="quick-add-popover.ui" compressed="true" 
preprocess="xml-stripblanks">ui/quick-add-popover.ui</file>
     <file alias="search-view.ui" compressed="true" preprocess="xml-stripblanks">ui/search-view.ui</file>
diff --git a/data/theme/gtk-styles.css b/data/theme/gtk-styles.css
index f0d81c7..f2a8d44 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 0000000..db20343
--- /dev/null
+++ b/data/ui/date-chooser.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <template class="GcalDateChooser" parent="GtkBin">
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GcalMultiChoice" id="month_choice">
+            <property name="visible" bind-source="GcalDateChooser" bind-property="show-heading" 
bind-flags="sync-create"/>
+            <property name="halign">fill</property>
+            <property name="min-value">0</property>
+            <property name="max-value">11</property>
+            <property name="animate">False</property>
+            <property name="value">0</property>
+            <signal name="notify::value" handler="multi_choice_changed" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GcalMultiChoice" id="year_choice">
+            <property name="visible" bind-source="GcalDateChooser" bind-property="show-heading" 
bind-flags="sync-create"/>
+            <property name="halign">fill</property>
+            <property name="min-value">0</property>
+            <property name="max-value">120000</property>
+            <property name="animate">False</property>
+            <property name="value">0</property>
+            <signal name="notify::value" handler="multi_choice_changed" swapped="yes"/>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkGrid" id="grid">
+            <property name="visible">True</property>
+            <property name="row-homogeneous">True</property>
+            <property name="column-homogeneous">True</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">1</property>
+            <property name="width">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/multi-choice.ui b/data/ui/multi-choice.ui
new file mode 100644
index 0000000..8376567
--- /dev/null
+++ b/data/ui/multi-choice.ui
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk30">
+  <requires lib="gtk+" version="3.16"/>
+  <template class="GcalMultiChoice" parent="GtkBox">
+    <property name="visible">True</property>
+    <child>
+      <object class="GtkButton" id="down_button">
+        <property name="visible">True</property>
+        <property name="relief">none</property>
+        <signal name="button-press-event" handler="button_press_cb"/>
+        <signal name="button-release-event" handler="button_release_cb"/>
+        <signal name="clicked" handler="button_clicked_cb"/>
+        <style>
+          <class name="image-button"/>
+          <class name="back-button"/>
+          <class name="circular"/>
+        </style>
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="icon_name">pan-start-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkLabel" id="label1">
+            <property name="visible">True</property>
+          </object>
+          <packing>
+            <property name="name">label1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label2">
+            <property name="visible">True</property>
+          </object>
+          <packing>
+            <property name="name">label2</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="up_button">
+        <property name="visible">True</property>
+        <property name="relief">none</property>
+        <signal name="button-press-event" handler="button_press_cb"/>
+        <signal name="button-release-event" handler="button_release_cb"/>
+        <signal name="clicked" handler="button_clicked_cb"/>
+        <style>
+          <class name="image-button"/>
+          <class name="forward-button"/>
+          <class name="circular"/>
+        </style>
+        <child>
+          <object class="GtkImage">
+            <property name="visible">True</property>
+            <property name="icon_name">pan-end-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/Makefile.am b/src/Makefile.am
index 3b5f79f..e4f52fd 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 0000000..1f85d14
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gcal-date-chooser-day.h"
+
+#include <stdlib.h>
+#include <langinfo.h>
+
+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 0000000..afb18c2
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GCAL_DATE_CHOOSER_DAY_PRIVATE_H__
+#define __GCAL_DATE_CHOOSER_DAY_PRIVATE_H__
+
+#include "gcal-date-chooser.h"
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+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 0000000..656f9b6
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include "gcal-utils.h"
+#include "gcal-date-chooser.h"
+#include "gcal-date-chooser-day.h"
+#include "gcal-multi-choice.h"
+
+#include <stdlib.h>
+#include <langinfo.h>
+
+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 0000000..cbbdf1d
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GCAL_DATE_CHOOSER_H__
+#define __GCAL_DATE_CHOOSER_H__
+
+#include <gtk/gtk.h>
+
+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 0000000..bfcb19e
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 0000000..9b3377e
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GCAL_MULTI_CHOICE_H
+#define GCAL_MULTI_CHOICE_H
+
+#include <gtk/gtk.h>
+#include <glib-object.h>
+
+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 */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]