2 * This file is a part of hildon
4 * Copyright (C) 2005, 2008 Nokia Corporation.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version. or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free
18 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 * SECTION:hildon-date-selector
23 * @short_description: A widget to select the current date.
25 * #HildonDateSelector is a date widget with multiple columns. Users
26 * can choose a date by selecting values in the day, month and year
29 * The currently selected month and year can be altered with
30 * hildon_date_selector_select_month(). The day can be selected from
31 * the active month using hildon_date_selector_select_day().
34 #define _GNU_SOURCE /* needed for GNU nl_langinfo_l */
35 #define __USE_GNU /* needed for locale */
39 #ifdef HAVE_SYS_TIME_H
50 #include "hildon-date-selector.h"
52 #define HILDON_DATE_SELECTOR_GET_PRIVATE(obj) \
53 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_DATE_SELECTOR, HildonDateSelectorPrivate))
55 G_DEFINE_TYPE (HildonDateSelector, hildon_date_selector, HILDON_TYPE_TOUCH_SELECTOR)
58 #define LAST_YEAR 50 /* since current year */
60 #define _(String) dgettext("hildon-libs", String)
62 /* #define _(String) "%A %e. %B %Y" debug purposes */
78 struct _HildonDateSelectorPrivate
80 GtkTreeModel *year_model;
81 GtkTreeModel *month_model;
82 GtkTreeModel *day_model;
87 gint year_column; /* it depends on the locale */
89 gchar *format; /* day/month/year format, depends on locale */
93 gint creation_year; /* date at creation time */
95 gint current_num_days;
106 static GObject * hildon_date_selector_constructor (GType type,
107 guint n_construct_properties,
108 GObjectConstructParam *construct_properties);
109 static void hildon_date_selector_finalize (GObject * object);
111 /* private functions */
112 static GtkTreeModel *_create_day_model (HildonDateSelector * selector);
113 static GtkTreeModel *_create_year_model (HildonDateSelector * selector);
114 static GtkTreeModel *_create_month_model (HildonDateSelector * selector);
116 static void _get_real_date (gint * year, gint * month, gint * day);
117 static void _locales_init (HildonDateSelectorPrivate * priv);
119 static void _manage_selector_change_cb (HildonTouchSelector * selector,
120 gint num_column, gpointer data);
122 static GtkTreeModel *_update_day_model (HildonDateSelector * selector);
124 static gint _month_days (gint month, gint year);
125 static void _init_column_order (HildonDateSelector * selector);
127 static gchar *_custom_print_func (HildonTouchSelector * selector);
129 /***************************************************************************/
130 /* The following date routines are taken from the lib_date package. Keep
131 * them separate in case we want to update them if a newer lib_date comes
134 typedef unsigned int N_int;
136 typedef unsigned long N_long;
138 typedef signed long Z_long;
141 { false = FALSE, true = TRUE } boolean;
143 #define and && /* logical (boolean) operators: lower case */
147 static const N_int month_length[2][13] = {
148 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
149 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
152 static const N_int days_in_months[2][14] = {
153 {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
154 {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
157 static Z_long _calc_days (N_int year, N_int mm, N_int dd);
159 static N_int _day_of_week (N_int year, N_int mm, N_int dd);
161 static boolean _leap (N_int year);
167 return ((((year % 4) == 0) and ((year % 100) != 0)) or ((year % 400) == 0));
171 _day_of_week (N_int year, N_int mm, N_int dd)
175 days = _calc_days (year, mm, dd);
181 return ((N_int) days);
185 _year_to_days (N_int year)
187 return (year * 365L + (year / 4) - (year / 100) + (year / 400));
191 _calc_days (N_int year, N_int mm, N_int dd)
197 if ((mm < 1) or (mm > 12))
199 if ((dd < 1) or (dd > month_length[(lp = _leap (year))][mm]))
201 return (_year_to_days (--year) + days_in_months[lp][mm] + dd);
205 hildon_date_selector_set_property (GObject *object,
210 HildonDateSelectorPrivate *priv = HILDON_DATE_SELECTOR (object)->priv;
215 priv->min_year = g_value_get_int (value);
218 priv->max_year = g_value_get_int (value);
221 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
226 hildon_date_selector_get_property (GObject *object,
231 HildonDateSelectorPrivate *priv = HILDON_DATE_SELECTOR (object)->priv;
236 g_value_set_int (value, priv->min_year);
239 g_value_set_int (value, priv->max_year);
242 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
247 hildon_date_selector_class_init (HildonDateSelectorClass * class)
249 GObjectClass *gobject_class;
250 GtkObjectClass *object_class;
251 GtkWidgetClass *widget_class;
252 GtkContainerClass *container_class;
254 gobject_class = (GObjectClass *) class;
255 object_class = (GtkObjectClass *) class;
256 widget_class = (GtkWidgetClass *) class;
257 container_class = (GtkContainerClass *) class;
260 gobject_class->finalize = hildon_date_selector_finalize;
261 gobject_class->get_property = hildon_date_selector_get_property;
262 gobject_class->set_property = hildon_date_selector_set_property;
263 gobject_class->constructor = hildon_date_selector_constructor;
271 g_object_class_install_property (
277 "The minimum available year in the selector",
281 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
283 g_object_class_install_property (
289 "The maximum available year in the selector",
293 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
297 g_type_class_add_private (object_class, sizeof (HildonDateSelectorPrivate));
301 hildon_date_selector_construct_ui (HildonDateSelector *selector)
304 gint current_item = 0;
305 HildonTouchSelectorColumn *column = NULL;
307 selector->priv->year_model = _create_year_model (selector);
308 selector->priv->month_model = _create_month_model (selector);
309 selector->priv->day_model = _create_day_model (selector);
311 /* We add the columns, checking the locale order */
312 iter = selector->priv->column_order;
313 for (iter = selector->priv->column_order; iter; iter = g_slist_next (iter)) {
314 current_item = GPOINTER_TO_INT (iter->data);
316 switch (current_item) {
318 column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
319 selector->priv->day_model, TRUE);
320 g_object_set (G_OBJECT (column), "text-column", 0, NULL);
323 column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
324 selector->priv->month_model, TRUE);
325 g_object_set (G_OBJECT (column), "text-column", 0, NULL);
328 column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
329 selector->priv->year_model, TRUE);
330 g_object_set (G_OBJECT (column), "text-column", 0, NULL);
333 g_error ("Current column order incorrect");
340 hildon_date_selector_constructor (GType type,
341 guint n_construct_properties,
342 GObjectConstructParam *construct_properties)
345 HildonDateSelector *selector;
347 object = G_OBJECT_CLASS (hildon_date_selector_parent_class)->constructor
348 (type, n_construct_properties, construct_properties);
350 selector = HILDON_DATE_SELECTOR (object);
352 hildon_date_selector_construct_ui (selector);
354 g_signal_connect (object, "changed", G_CALLBACK (_manage_selector_change_cb), NULL);
356 /* By default we should select the current day */
357 hildon_date_selector_select_current_date (selector, selector->priv->creation_year,
358 selector->priv->creation_month,
359 selector->priv->creation_day);
365 hildon_date_selector_init (HildonDateSelector * selector)
367 selector->priv = HILDON_DATE_SELECTOR_GET_PRIVATE (selector);
369 GTK_WIDGET_SET_FLAGS (GTK_WIDGET (selector), GTK_NO_WINDOW);
370 gtk_widget_set_redraw_on_allocate (GTK_WIDGET (selector), FALSE);
372 hildon_touch_selector_set_print_func (HILDON_TOUCH_SELECTOR (selector),
375 _locales_init (selector->priv);
377 _init_column_order (selector);
379 _get_real_date (&selector->priv->creation_year,
380 &selector->priv->creation_month, &selector->priv->creation_day);
381 selector->priv->current_num_days = 31;
385 hildon_date_selector_finalize (GObject * object)
387 HildonDateSelector *selector = NULL;
389 selector = HILDON_DATE_SELECTOR (object);
391 g_slist_free (selector->priv->column_order);
392 g_free (selector->priv->format);
394 (*G_OBJECT_CLASS (hildon_date_selector_parent_class)->finalize) (object);
397 /* ------------------------------ PRIVATE METHODS ---------------------------- */
399 _custom_print_func (HildonTouchSelector * touch_selector)
401 HildonDateSelector *selector = NULL;
402 gchar *result = NULL;
403 guint year, month, day;
404 gint day_of_week = 0;
405 static gchar string[255];
406 struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
408 selector = HILDON_DATE_SELECTOR (touch_selector);
410 hildon_date_selector_get_date (selector, &year, &month, &day);
411 day_of_week = _day_of_week (year, month + 1, day) % 7;
415 tm.tm_year = year - 1900;
416 tm.tm_wday = day_of_week;
418 strftime (string, 255, _("wdgt_va_date_long"), &tm);
420 result = g_strdup (string);
425 /* This was copied from hildon-calendar */
427 _locales_init (HildonDateSelectorPrivate * priv)
429 /* Hildon: This is not exactly portable, see
430 * http://bugzilla.gnome.org/show_bug.cgi?id=343415
431 * The labels need to be instance variables as the startup wizard changes
436 l = newlocale (LC_TIME_MASK, setlocale (LC_MESSAGES, NULL), NULL);
438 priv->format = g_locale_to_utf8 (nl_langinfo_l (D_FMT, l),
439 -1, NULL, NULL, NULL);
445 _init_column_order (HildonDateSelector * selector)
447 gchar *current_order[3] = { NULL, NULL, NULL };
448 gchar *day_pos = NULL;
449 gchar *month_pos = NULL;
450 gchar *year_pos = NULL;
454 g_debug ("Current format: %s", selector->priv->format);
456 /* search each token on the format */
457 day_pos = g_strrstr (selector->priv->format, "%d");
459 month_pos = g_strrstr (selector->priv->format, "%m");
460 year_pos = g_strrstr (selector->priv->format, "%y");
461 if (year_pos == NULL) {
462 year_pos = g_strrstr (selector->priv->format, "%Y");
466 if ((day_pos == NULL) || (month_pos == NULL) || (year_pos == NULL)) {
467 g_error ("Wrong date format"); /* so default values */
469 selector->priv->day_column = 0;
470 selector->priv->month_column = 1;
471 selector->priv->year_column = 2;
472 selector->priv->column_order = g_slist_append (NULL, GINT_TO_POINTER (DAY));
473 selector->priv->column_order =
474 g_slist_append (selector->priv->column_order, GINT_TO_POINTER (MONTH));
475 selector->priv->column_order =
476 g_slist_append (selector->priv->column_order, GINT_TO_POINTER (YEAR));
479 /* sort current_order with this values (bubble sort) */
480 current_order[0] = day_pos;
481 current_order[1] = month_pos;
482 current_order[2] = year_pos;
484 for (c = 1; c <= 2; c++) {
485 for (i = 0; i < 3 - c; i++) {
486 if (current_order[i] > current_order[i + 1]) {
487 aux = current_order[i];
488 current_order[i] = current_order[i + 1];
489 current_order[i + 1] = aux;
494 /* fill the column positions */
495 selector->priv->column_order = NULL;
497 for (i = 0; i < 3; i++) {
498 if (current_order[i] == day_pos) {
499 selector->priv->column_order =
500 g_slist_append (selector->priv->column_order, GINT_TO_POINTER (DAY));
501 selector->priv->day_column = c++;
503 if (current_order[i] == month_pos) {
504 selector->priv->column_order =
505 g_slist_append (selector->priv->column_order, GINT_TO_POINTER (MONTH));
506 selector->priv->month_column = c++;
508 if (current_order[i] == year_pos) {
509 selector->priv->column_order =
510 g_slist_append (selector->priv->column_order, GINT_TO_POINTER (YEAR));
511 selector->priv->year_column = c++;
517 static GtkTreeModel *
518 _create_day_model (HildonDateSelector * selector)
520 GtkListStore *store_days = NULL;
523 struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
526 store_days = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
527 for (i = 1; i < 32; i++) {
529 strftime (label, 255, _("wdgt_va_day_numeric"), &tm);
531 gtk_list_store_append (store_days, &iter);
532 gtk_list_store_set (store_days, &iter,
533 COLUMN_STRING, label, COLUMN_INT, i, -1);
536 return GTK_TREE_MODEL (store_days);
539 static GtkTreeModel *
540 _create_year_model (HildonDateSelector * selector)
542 GtkListStore *store_years = NULL;
545 static gchar label[255];
546 struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
549 real_year = selector->priv->creation_year;
551 store_years = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
552 for (i = selector->priv->min_year; i < selector->priv->max_year + 1; i++) {
553 tm.tm_year = i - 1900;
554 strftime (label, 255, _("wdgt_va_year"), &tm);
556 gtk_list_store_append (store_years, &iter);
557 gtk_list_store_set (store_years, &iter,
558 COLUMN_STRING, label, COLUMN_INT, i, -1);
561 return GTK_TREE_MODEL (store_years);
564 static GtkTreeModel *
565 _create_month_model (HildonDateSelector * selector)
569 GtkListStore *store_months = NULL;
570 static gchar label[255];
571 struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
573 store_months = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
574 for (i = 0; i < 12; i++) {
576 strftime (label, 255, _("wdgt_va_month"), &tm);
578 gtk_list_store_append (store_months, &iter);
579 gtk_list_store_set (store_months, &iter, COLUMN_STRING, label,
584 return GTK_TREE_MODEL (store_months);
587 static GtkTreeModel *
588 _update_day_model (HildonDateSelector * selector)
590 GtkListStore *store_days = NULL;
591 GtkTreePath *path = NULL;
594 static gchar label[255];
595 struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
596 guint current_day = 0;
597 guint current_year = 0;
598 guint current_month = 0;
601 hildon_date_selector_get_date (selector, ¤t_year, ¤t_month,
604 num_days = _month_days (current_month, current_year);
605 store_days = GTK_LIST_STORE (selector->priv->day_model);
607 if (num_days == selector->priv->current_num_days) {
608 return GTK_TREE_MODEL (store_days);
611 if (num_days > selector->priv->current_num_days) {
612 for (i = selector->priv->current_num_days + 1; i <= num_days; i++) {
614 strftime (label, 255, _("wdgt_va_day_numeric"), &tm);
616 gtk_list_store_append (store_days, &iter);
617 gtk_list_store_set (store_days, &iter,
618 COLUMN_STRING, label, COLUMN_INT, i, -1);
621 path = gtk_tree_path_new_from_indices (num_days,
623 gtk_tree_model_get_iter (GTK_TREE_MODEL (store_days), &iter, path);
625 }while (gtk_list_store_remove (store_days, &iter));
627 gtk_tree_path_free (path);
631 selector->priv->current_num_days = num_days;
633 /* now we select a day */
634 if (current_day >= num_days) {
635 current_day = num_days;
638 hildon_date_selector_select_day (selector, current_day);
640 return GTK_TREE_MODEL (store_days);
645 _get_real_date (gint * year, gint * month, gint * day)
648 struct tm *tm = NULL;
651 tm = localtime (&secs);
654 *year = 1900 + tm->tm_year;
668 _manage_selector_change_cb (HildonTouchSelector * touch_selector,
669 gint num_column, gpointer data)
671 HildonDateSelector *selector = NULL;
673 g_return_if_fail (HILDON_IS_DATE_SELECTOR (touch_selector));
674 selector = HILDON_DATE_SELECTOR (touch_selector);
676 if ((num_column == selector->priv->month_column) ||
677 (num_column == selector->priv->year_column)) /* it is required to check that with
678 * the years too,remember: leap years
679 * update_day_model will check if
680 * the number of days is different
683 _update_day_model (selector);
688 _month_days (gint month, gint year)
690 gint month_days[2][12] = {
691 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
692 {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
695 g_return_val_if_fail (month >= 0 && month <= 12, -1);
697 return month_days[_leap (year)][month];
701 /* ------------------------------ PUBLIC METHODS ---------------------------- */
704 * hildon_date_selector_new:
706 * Creates a new #HildonDateSelector
708 * Returns: a new #HildonDateSelector
713 hildon_date_selector_new ()
715 return g_object_new (HILDON_TYPE_DATE_SELECTOR, NULL);
719 * hildon_date_selector_new_with_year_range:
720 * @min_year: the minimum available year or -1 to ignore
721 * @max_year: the maximum available year or -1 to ignore
723 * Creates a new #HildonDateSelector with a specific year range.
724 * If @min_year or @max_year are set to -1, then the default
725 * upper or lower bound will be used, respectively.
727 * Returns: a new #HildonDateSelector
732 hildon_date_selector_new_with_year_range (gint min_year,
737 g_return_val_if_fail (min_year <= max_year, NULL);
739 if (min_year == -1 && min_year == -1) {
740 selector = g_object_new (HILDON_TYPE_DATE_SELECTOR,
742 } else if (min_year == -1) {
743 selector = g_object_new (HILDON_TYPE_DATE_SELECTOR,
744 "max-year", max_year,
746 } else if (max_year == -1) {
747 selector = g_object_new (HILDON_TYPE_DATE_SELECTOR,
748 "min-year", min_year,
751 selector = g_object_new (HILDON_TYPE_DATE_SELECTOR,
752 "min-year", min_year,
753 "max-year", max_year,
760 * hildon_date_selector_select_current_date:
761 * @selector: the #HildonDateSelector
762 * @year: the current year
763 * @month: the current month (0-11)
764 * @day: the current day (1-31, 1-30, 1-29, 1-28) depends on the month
766 * Sets the current active date on the #HildonDateSelector widget
770 * Returns: %TRUE on success, %FALSE otherwise
773 hildon_date_selector_select_current_date (HildonDateSelector * selector,
774 guint year, guint month, guint day)
781 min_year = selector->priv->min_year;
782 max_year = selector->priv->max_year;
784 g_return_val_if_fail (min_year <= year && year <= max_year, FALSE);
785 g_return_val_if_fail (month < 12, FALSE);
787 num_days = _month_days (month, year);
788 g_return_val_if_fail (day > 0 && day <= num_days, FALSE);
791 gtk_tree_model_iter_nth_child (selector->priv->year_model, &iter, NULL,
793 hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
794 selector->priv->year_column, &iter,
797 gtk_tree_model_iter_nth_child (selector->priv->month_model, &iter, NULL,
799 hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
800 selector->priv->month_column, &iter,
803 gtk_tree_model_iter_nth_child (selector->priv->day_model, &iter, NULL,
805 hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
806 selector->priv->day_column, &iter,
814 * hildon_date_selector_get_date:
815 * @selector: the #HildonDateSelector
816 * @year: to set the current year
817 * @month: to set the current month (0-11)
818 * @day: to the current day (1-31, 1-30, 1-29, 1-28) depends on the month
820 * Gets the current active date on the #HildonDateSelector widget
825 hildon_date_selector_get_date (HildonDateSelector * selector,
826 guint * year, guint * month, guint * day)
831 if (hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
832 selector->priv->year_column, &iter))
833 gtk_tree_model_get (selector->priv->year_model,
834 &iter, COLUMN_INT, year, -1);
838 if (hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
839 selector->priv->month_column, &iter))
840 gtk_tree_model_get (selector->priv->month_model,
841 &iter, COLUMN_INT, month, -1);
845 if (hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
846 selector->priv->day_column, &iter))
848 gtk_tree_model_get (selector->priv->day_model,
849 &iter, COLUMN_INT, day, -1);
851 /* *day = *day - 1; */
858 * hildon_date_selector_select_month:
859 * @selector: the #HildonDateSelector
860 * @month: the current month (0-11)
861 * @year: the current year
863 * Modify the current month and year on the current active date
865 * Utility function to keep this API similar to the previously
866 * existing #HildonCalendar widget.
870 * Returns: %TRUE on success, %FALSE otherwise
872 gboolean hildon_date_selector_select_month (HildonDateSelector *selector,
873 guint month, guint year)
877 hildon_date_selector_get_date (selector, NULL, NULL, &day);
879 return hildon_date_selector_select_current_date (selector, year, month, day);
883 * hildon_date_selector_select_day:
884 * @selector: the #HildonDateSelector
885 * @day: the current day (1-31, 1-30, 1-29, 1-28) depends on the month
887 * Modify the current day on the current active date
889 * Utility function to keep this API similar to the previously
890 * existing #HildonCalendar widget.
895 hildon_date_selector_select_day (HildonDateSelector *selector, guint day)
900 hildon_date_selector_get_date (selector, &year, &month, NULL);
902 hildon_date_selector_select_current_date (selector, year, month, day);