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-time-selector
23 * @short_description: A widget to select the current time.
25 * #HildonTimeSelector allows users to choose a time by selecting hour
26 * and minute. It also allows choosing between AM or PM format.
28 * The currently selected time can be altered with
29 * hildon_time_selector_set_time(), and retrieved using
30 * hildon_time_selector_get_time().
32 * Use this widget instead of deprecated HildonTimeEditor widget.
35 #define _GNU_SOURCE /* needed for GNU nl_langinfo_l */
36 #define __USE_GNU /* needed for locale */
38 #ifdef HAVE_SYS_TIME_H
48 #include <gconf/gconf-client.h>
50 #include "hildon-enum-types.h"
51 #include "hildon-time-selector.h"
52 #include "hildon-touch-selector-private.h"
54 #define HILDON_TIME_SELECTOR_GET_PRIVATE(obj) \
55 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_TIME_SELECTOR, HildonTimeSelectorPrivate))
57 G_DEFINE_TYPE (HildonTimeSelector, hildon_time_selector, HILDON_TYPE_TOUCH_SELECTOR)
60 #define LAST_YEAR 50 /* since current year */
62 #define _(String) dgettext("hildon-libs", String)
63 #define N_(String) String
66 /* FIXME: we should get this two props from the clock ui headers */
67 #define CLOCK_GCONF_PATH "/apps/clock"
68 #define CLOCK_GCONF_IS_24H_FORMAT CLOCK_GCONF_PATH "/time-format"
87 PROP_TIME_FORMAT_POLICY
90 struct _HildonTimeSelectorPrivate
92 GtkTreeModel *hours_model;
93 GtkTreeModel *minutes_model;
94 GtkTreeModel *ampm_model;
97 HildonTimeSelectorFormatPolicy format_policy;
98 gboolean ampm_format; /* if using am/pm format or 24 h one */
100 gboolean pm; /* if we are on pm (only useful if ampm_format == TRUE) */
103 gint creation_minutes;
106 static void hildon_time_selector_finalize (GObject * object);
107 static GObject* hildon_time_selector_constructor (GType type,
108 guint n_construct_properties,
109 GObjectConstructParam *construct_params);
110 static void hildon_time_selector_get_property (GObject *object,
114 static void hildon_time_selector_set_property (GObject *object,
119 /* private functions */
120 static GtkTreeModel *_create_hours_model (HildonTimeSelector * selector);
121 static GtkTreeModel *_create_minutes_model (guint minutes_step);
122 static GtkTreeModel *_create_ampm_model (HildonTimeSelector * selector);
124 static void _get_real_time (gint * hours, gint * minutes);
125 static void _manage_ampm_selection_cb (HildonTouchSelector * selector,
126 gint num_column, gpointer data);
127 static void _set_pm (HildonTimeSelector * selector, gboolean pm);
129 static gchar *_custom_print_func (HildonTouchSelector * selector,
133 check_automatic_ampm_format (HildonTimeSelector * selector);
136 update_format_policy (HildonTimeSelector *selector,
137 HildonTimeSelectorFormatPolicy new_policy);
139 update_format_dependant_columns (HildonTimeSelector *selector,
144 hildon_time_selector_class_init (HildonTimeSelectorClass * class)
146 GObjectClass *gobject_class;
147 GtkObjectClass *object_class;
148 GtkWidgetClass *widget_class;
149 GtkContainerClass *container_class;
151 gobject_class = (GObjectClass *) class;
152 object_class = (GtkObjectClass *) class;
153 widget_class = (GtkWidgetClass *) class;
154 container_class = (GtkContainerClass *) class;
157 gobject_class->get_property = hildon_time_selector_get_property;
158 gobject_class->set_property = hildon_time_selector_set_property;
159 gobject_class->constructor = hildon_time_selector_constructor;
160 gobject_class->finalize = hildon_time_selector_finalize;
162 g_object_class_install_property (gobject_class,
164 g_param_spec_uint ("minutes-step",
165 "Step between minutes in the model",
166 "Step between the minutes in the list of"
167 " options of the widget ",
169 G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
172 * HildonTimeSelector:time-format-policy:
174 * The visual policy of the time format
178 g_object_class_install_property (gobject_class,
179 PROP_TIME_FORMAT_POLICY,
180 g_param_spec_enum ("time_format_policy",
181 "time format policy",
182 "Visual policy of the time format",
183 HILDON_TYPE_TIME_SELECTOR_FORMAT_POLICY,
184 HILDON_TIME_SELECTOR_FORMAT_POLICY_AUTOMATIC,
185 G_PARAM_READWRITE|G_PARAM_CONSTRUCT));
193 g_type_class_add_private (object_class, sizeof (HildonTimeSelectorPrivate));
196 /* FIXME: the constructor was required because as we need the initial values
197 of the properties passed on g_object_new. But, probably use the method
198 constructed could be easier */
200 hildon_time_selector_constructor (GType type,
201 guint n_construct_properties,
202 GObjectConstructParam *construct_params)
205 HildonTimeSelector *selector;
206 HildonTouchSelectorColumn *column;
208 object = (* G_OBJECT_CLASS (hildon_time_selector_parent_class)->constructor)
209 (type, n_construct_properties, construct_params);
211 selector = HILDON_TIME_SELECTOR (object);
213 selector->priv->hours_model = _create_hours_model (selector);
215 column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
216 selector->priv->hours_model, TRUE);
217 hildon_touch_selector_column_set_text_column (column, 0);
219 /* we need initialization parameters in order to create minute models*/
220 selector->priv->minutes_step = selector->priv->minutes_step ? selector->priv->minutes_step : 1;
222 selector->priv->minutes_model = _create_minutes_model (selector->priv->minutes_step);
224 column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
225 selector->priv->minutes_model, TRUE);
226 hildon_touch_selector_column_set_text_column (column, 0);
228 if (selector->priv->ampm_format) {
229 selector->priv->ampm_model = _create_ampm_model (selector);
231 hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
232 selector->priv->ampm_model, TRUE);
234 g_signal_connect (G_OBJECT (selector),
235 "changed", G_CALLBACK (_manage_ampm_selection_cb),
240 /* By default we should select the current day */
241 hildon_time_selector_set_time (selector,
242 selector->priv->creation_hours,
243 selector->priv->creation_minutes);
250 hildon_time_selector_init (HildonTimeSelector * selector)
252 selector->priv = HILDON_TIME_SELECTOR_GET_PRIVATE (selector);
254 GTK_WIDGET_SET_FLAGS (GTK_WIDGET (selector), GTK_NO_WINDOW);
255 gtk_widget_set_redraw_on_allocate (GTK_WIDGET (selector), FALSE);
257 hildon_touch_selector_set_print_func (HILDON_TOUCH_SELECTOR (selector),
260 /* By default we use the automatic ampm format */
261 selector->priv->pm = TRUE;
262 check_automatic_ampm_format (selector);
264 _get_real_time (&selector->priv->creation_hours,
265 &selector->priv->creation_minutes);
269 hildon_time_selector_get_property (GObject *object,
274 HildonTimeSelectorPrivate *priv = HILDON_TIME_SELECTOR_GET_PRIVATE (object);
278 case PROP_MINUTES_STEP:
279 g_value_set_uint (value, priv->minutes_step);
281 case PROP_TIME_FORMAT_POLICY:
282 g_value_set_enum (value, priv->format_policy);
285 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
291 hildon_time_selector_set_property (GObject *object,
296 HildonTimeSelectorPrivate *priv = HILDON_TIME_SELECTOR_GET_PRIVATE (object);
300 case PROP_MINUTES_STEP:
301 priv->minutes_step = g_value_get_uint (value);
303 case PROP_TIME_FORMAT_POLICY:
304 update_format_policy (HILDON_TIME_SELECTOR (object),
305 g_value_get_enum (value));
308 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
314 hildon_time_selector_finalize (GObject * object)
316 /* Note: we don't require to free the models. We don't manage it using own
317 references, so will be freed on the hildon-touch-selector finalize code.
318 See the implementation notes related to that on the touch selector
321 (*G_OBJECT_CLASS (hildon_time_selector_parent_class)->finalize) (object);
324 /* ------------------------------ PRIVATE METHODS ---------------------------- */
327 _custom_print_func (HildonTouchSelector * touch_selector,
330 gchar *result = NULL;
331 struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
332 HildonTimeSelector *selector = NULL;
333 static gchar string[255];
337 selector = HILDON_TIME_SELECTOR (touch_selector);
339 hildon_time_selector_get_time (selector, &hours, &minutes);
344 if (selector->priv->ampm_format) {
345 if (selector->priv->pm) {
346 strftime (string, 255, _("wdgt_va_12h_time_pm"), &tm);
348 strftime (string, 255, _("wdgt_va_12h_time_am"), &tm);
351 strftime (string, 255, _("wdgt_va_24h_time"), &tm);
355 result = g_strdup (string);
360 static GtkTreeModel *
361 _create_minutes_model (guint minutes_step)
363 GtkListStore *store_minutes = NULL;
365 static gchar label[255];
366 struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
369 store_minutes = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
370 for (i = 0; i <= 59; i=i+minutes_step) {
372 strftime (label, 255, _("wdgt_va_minutes"), &tm);
374 gtk_list_store_append (store_minutes, &iter);
375 gtk_list_store_set (store_minutes, &iter,
376 COLUMN_STRING, label, COLUMN_INT, i, -1);
379 return GTK_TREE_MODEL (store_minutes);
382 static GtkTreeModel *
383 _create_hours_model (HildonTimeSelector * selector)
385 GtkListStore *store_hours = NULL;
388 struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
389 static gchar label[255];
390 static gint range_12h[12] = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11};
391 static gint range_24h[24] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,
392 12,13,14,15,16,17,18,19,20,21,22,23};
394 gint num_elements = 0;
395 gchar *format_string = NULL;
397 if (selector->priv->ampm_format) {
400 format_string = N_("wdgt_va_12h_hours");
404 format_string = N_("wdgt_va_24h_hours");
407 store_hours = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
408 for (i = 0; i < num_elements; i++) {
409 tm.tm_hour = range[i];
410 strftime (label, 255, _(format_string), &tm);
412 gtk_list_store_append (store_hours, &iter);
413 gtk_list_store_set (store_hours, &iter,
414 COLUMN_STRING, label, COLUMN_INT, range[i], -1);
417 return GTK_TREE_MODEL (store_hours);
420 static GtkTreeModel *
421 _create_ampm_model (HildonTimeSelector * selector)
423 GtkListStore *store_ampm = NULL;
425 static gchar label[255];
427 store_ampm = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
429 snprintf (label, 255, _("wdgt_va_am"));
430 gtk_list_store_append (store_ampm, &iter);
431 gtk_list_store_set (store_ampm, &iter,
432 COLUMN_STRING, label,
435 snprintf (label, 255, _("wdgt_va_pm"));
436 gtk_list_store_append (store_ampm, &iter);
437 gtk_list_store_set (store_ampm, &iter,
438 COLUMN_STRING, label,
441 return GTK_TREE_MODEL (store_ampm);
445 _get_real_time (gint * hours, gint * minutes)
448 struct tm *tm = NULL;
451 tm = localtime (&secs);
454 *hours = tm->tm_hour;
457 if (minutes != NULL) {
458 *minutes = tm->tm_min;
463 _manage_ampm_selection_cb (HildonTouchSelector * touch_selector,
464 gint num_column, gpointer data)
466 HildonTimeSelector *selector = NULL;
470 g_return_if_fail (HILDON_IS_TIME_SELECTOR (touch_selector));
471 selector = HILDON_TIME_SELECTOR (touch_selector);
473 if (num_column == COLUMN_AMPM &&
474 hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
475 COLUMN_AMPM, &iter)) {
476 gtk_tree_model_get (selector->priv->ampm_model, &iter, COLUMN_INT, &pm, -1);
478 selector->priv->pm = pm;
483 check_automatic_ampm_format (HildonTimeSelector * selector)
485 GConfClient *client = NULL;
486 gboolean value = TRUE;
487 GError *error = NULL;
489 client = gconf_client_get_default ();
490 value = gconf_client_get_bool (client, CLOCK_GCONF_IS_24H_FORMAT, &error);
493 ("Error trying to get gconf variable %s, using 24h format by default",
494 CLOCK_GCONF_IS_24H_FORMAT);
495 g_error_free (error);
498 selector->priv->ampm_format = !value;
502 _set_pm (HildonTimeSelector * selector, gboolean pm)
506 selector->priv->pm = pm;
508 if (selector->priv->ampm_model != NULL) {
509 gtk_tree_model_iter_nth_child (selector->priv->ampm_model, &iter, NULL, pm);
511 hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
512 COLUMN_AMPM, &iter, FALSE);
517 update_format_policy (HildonTimeSelector *selector,
518 HildonTimeSelectorFormatPolicy new_policy)
520 gboolean prev_ampm_format = FALSE;
521 gint num_columns = -1;
523 num_columns = hildon_touch_selector_get_num_columns (HILDON_TOUCH_SELECTOR (selector));
524 prev_ampm_format = selector->priv->ampm_format;
526 if (new_policy != selector->priv->format_policy) {
530 selector->priv->format_policy = new_policy;
532 /* We get the hour previous all the changes, to avoid problems with the
533 changing widget structure */
534 if (num_columns >= 2) {/* we are on the object construction */
535 hildon_time_selector_get_time (selector, &hours, &minutes);
540 case HILDON_TIME_SELECTOR_FORMAT_POLICY_AMPM:
541 selector->priv->ampm_format = TRUE;
543 case HILDON_TIME_SELECTOR_FORMAT_POLICY_24H:
544 selector->priv->ampm_format = FALSE;
546 case HILDON_TIME_SELECTOR_FORMAT_POLICY_AUTOMATIC:
547 check_automatic_ampm_format (selector);
551 if (prev_ampm_format != selector->priv->ampm_format) {
552 update_format_dependant_columns (selector, hours, minutes);
558 update_format_dependant_columns (HildonTimeSelector *selector,
562 gint num_columns = -1;
564 num_columns = hildon_touch_selector_get_num_columns (HILDON_TOUCH_SELECTOR (selector));
565 if (num_columns < 2) {/* we are on the object construction */
569 /* To avoid an extra and wrong VALUE_CHANGED signal on the model update */
570 hildon_touch_selector_block_changed (HILDON_TOUCH_SELECTOR(selector));
572 selector->priv->hours_model = _create_hours_model (selector);
573 hildon_touch_selector_set_model (HILDON_TOUCH_SELECTOR (selector),
575 selector->priv->hours_model);
577 /* We need to set NOW the correct hour on the hours column, because the number of
578 columns will be updated too, so a signal COLUMNS_CHANGED will be emitted. Some
579 other widget could have connected to this signal and ask for the hour, so this
580 emission could have a wrong hour. We could use a custom func to only modify the
581 hours selection, but hildon_time_selector_time manage yet all the ampm issues
582 to select the correct one */
583 hildon_time_selector_set_time (selector, hours, minutes);
585 /* if we are at this function, we are sure that a change on the number of columns
586 will happen, so check the column number is not required */
587 if (selector->priv->ampm_format) {
588 selector->priv->ampm_model = _create_ampm_model (selector);
590 hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
591 selector->priv->ampm_model, TRUE);
593 g_signal_connect (G_OBJECT (selector),
594 "changed", G_CALLBACK (_manage_ampm_selection_cb),
597 selector->priv->ampm_model = NULL;
598 hildon_touch_selector_remove_column (HILDON_TOUCH_SELECTOR (selector), 2);
601 _set_pm (selector, hours >= 12);
603 hildon_touch_selector_unblock_changed (HILDON_TOUCH_SELECTOR (selector));
605 /* ------------------------------ PUBLIC METHODS ---------------------------- */
608 * hildon_time_selector_new:
610 * Creates a new #HildonTimeSelector
612 * Returns: a new #HildonTimeSelector
617 hildon_time_selector_new ()
619 return g_object_new (HILDON_TYPE_TIME_SELECTOR, NULL);
624 * hildon_time_selector_new_step:
625 * @minutes_step: step between the minutes in the selector.
627 * Creates a new #HildonTimeSelector, with @minutes_step steps between
628 * the minutes in the minutes column.
630 * Returns: a new #HildonTimeSelector
635 hildon_time_selector_new_step (guint minutes_step)
637 return g_object_new (HILDON_TYPE_TIME_SELECTOR, "minutes-step",
642 * hildon_time_selector_set_time
643 * @selector: the #HildonTimeSelector
644 * @hours: the current hour (0-23)
645 * @minutes: the current minute (0-59)
647 * Sets the current active hour on the #HildonTimeSelector widget
649 * The format of the hours accepted is always 24h format, with a range
654 * Returns: %TRUE on success, %FALSE otherwise
657 hildon_time_selector_set_time (HildonTimeSelector * selector,
658 guint hours, guint minutes)
663 g_return_val_if_fail (HILDON_IS_TIME_SELECTOR (selector), FALSE);
664 g_return_val_if_fail (hours <= 23, FALSE);
665 g_return_val_if_fail (minutes <= 59, FALSE);
667 _set_pm (selector, hours >= 12);
669 if (selector->priv->ampm_format) {
670 hours_item = hours - selector->priv->pm * 12;
675 gtk_tree_model_iter_nth_child (selector->priv->hours_model, &iter, NULL,
677 hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
678 COLUMN_HOURS, &iter, FALSE);
680 g_assert (selector->priv->minutes_step>0);
681 minutes = minutes/selector->priv->minutes_step;
682 gtk_tree_model_iter_nth_child (selector->priv->minutes_model, &iter, NULL,
684 hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
685 COLUMN_MINUTES, &iter, FALSE);
691 * hildon_time_selector_get_time
692 * @selector: the #HildonTimeSelector
693 * @hours: to set the current hour (0-23)
694 * @minutes: to set the current minute (0-59)
696 * Gets the current active hour on the #HildonTimeSelector widget. Both @year
697 * and @minutes can be NULL.
699 * This method returns the date always in 24h format, with a range (0-23):(0-59)
704 hildon_time_selector_get_time (HildonTimeSelector * selector,
705 guint * hours, guint * minutes)
709 g_return_if_fail (HILDON_IS_TIME_SELECTOR (selector));
712 if (hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
713 COLUMN_HOURS, &iter)) {
715 gtk_tree_model_get (selector->priv->hours_model,
716 &iter, COLUMN_INT, hours, -1);
718 if (selector->priv->ampm_format) {
720 *hours += selector->priv->pm * 12;
724 if (minutes != NULL) {
725 if (hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
726 COLUMN_MINUTES, &iter)) {
727 gtk_tree_model_get (selector->priv->minutes_model,
728 &iter, COLUMN_INT, minutes, -1);