2009-02-24 Alejandro G. Castro <alex@igalia.com>
[hildon] / src / hildon-time-selector.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2005, 2008 Nokia Corporation.
5  *
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.
10  *
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.
15  *
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.
19  */
20
21 /**
22  * SECTION:hildon-time-selector
23  * @short_description: A widget to select the current time.
24  *
25  * #HildonTimeSelector allows users to choose a time by selecting hour
26  * and minute. It also allows choosing between AM or PM format.
27  *
28  * The currently selected time can be altered with
29  * hildon_time_selector_set_time(), and retrieved using
30  * hildon_time_selector_get_time().
31  *
32  * Use this widget instead of deprecated HildonTimeEditor widget.
33  */
34
35 #define _GNU_SOURCE     /* needed for GNU nl_langinfo_l */
36 #define __USE_GNU       /* needed for locale */
37
38 #ifdef HAVE_SYS_TIME_H
39 #include <sys/time.h>
40 #endif
41
42 #include <string.h>
43 #include <stdlib.h>
44 #include <libintl.h>
45 #include <time.h>
46 #include <langinfo.h>
47 #include <locale.h>
48 #include <gconf/gconf-client.h>
49
50 #include "hildon-time-selector.h"
51
52 #define HILDON_TIME_SELECTOR_GET_PRIVATE(obj)                           \
53   (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_TIME_SELECTOR, HildonTimeSelectorPrivate))
54
55 G_DEFINE_TYPE (HildonTimeSelector, hildon_time_selector, HILDON_TYPE_TOUCH_SELECTOR)
56
57 #define INIT_YEAR 100
58 #define LAST_YEAR 50    /* since current year */
59
60 #define _(String)  dgettext("hildon-libs", String)
61 #define N_(String) String
62
63
64 /* FIXME: we should get this two props from the clock ui headers */
65 #define CLOCK_GCONF_PATH "/apps/clock"
66 #define CLOCK_GCONF_IS_24H_FORMAT CLOCK_GCONF_PATH  "/time-format"
67
68 enum {
69   COLUMN_STRING,
70   COLUMN_INT,
71   TOTAL_MODEL_COLUMNS
72 };
73
74 enum {
75   COLUMN_HOURS = 0,
76   COLUMN_MINUTES,
77   COLUMN_AMPM,
78   TOTAL_TIME_COLUMNS
79 };
80
81 enum
82 {
83   PROP_0,
84   PROP_MINUTES_STEP
85 };
86
87 struct _HildonTimeSelectorPrivate
88 {
89   GtkTreeModel *hours_model;
90   GtkTreeModel *minutes_model;
91   GtkTreeModel *ampm_model;
92
93   guint minutes_step;
94   gboolean ampm_format;         /* if using am/pm format or 24 h one */
95
96   gboolean pm;                  /* if we are on pm (only useful if ampm_format == TRUE) */
97
98   gint creation_hours;
99   gint creation_minutes;
100 };
101
102 static void hildon_time_selector_finalize (GObject * object);
103 static GObject* hildon_time_selector_constructor (GType type,
104                                                   guint n_construct_properties,
105                                                   GObjectConstructParam *construct_params);
106 static void hildon_time_selector_get_property (GObject *object,
107                                                guint param_id,
108                                                GValue *value,
109                                                GParamSpec *pspec);
110 static void hildon_time_selector_set_property (GObject *object,
111                                                guint param_id,
112                                                const GValue *value,
113                                                GParamSpec *pspec);
114
115 /* private functions */
116 static GtkTreeModel *_create_hours_model (HildonTimeSelector * selector);
117 static GtkTreeModel *_create_minutes_model (guint minutes_step);
118 static GtkTreeModel *_create_ampm_model (HildonTimeSelector * selector);
119
120 static void _get_real_time (gint * hours, gint * minutes);
121 static void _manage_ampm_selection_cb (HildonTouchSelector * selector,
122                                        gint num_column, gpointer data);
123 static void _check_ampm_format (HildonTimeSelector * selector);
124 static void _set_pm (HildonTimeSelector * selector, gboolean pm);
125
126 static gchar *_custom_print_func (HildonTouchSelector * selector);
127
128 static void
129 hildon_time_selector_class_init (HildonTimeSelectorClass * class)
130 {
131   GObjectClass *gobject_class;
132   GtkObjectClass *object_class;
133   GtkWidgetClass *widget_class;
134   GtkContainerClass *container_class;
135
136   gobject_class = (GObjectClass *) class;
137   object_class = (GtkObjectClass *) class;
138   widget_class = (GtkWidgetClass *) class;
139   container_class = (GtkContainerClass *) class;
140
141   /* GObject */
142   gobject_class->get_property = hildon_time_selector_get_property;
143   gobject_class->set_property = hildon_time_selector_set_property;
144   gobject_class->constructor = hildon_time_selector_constructor;
145   gobject_class->finalize = hildon_time_selector_finalize;
146
147   g_object_class_install_property (gobject_class,
148                                    PROP_MINUTES_STEP,
149                                    g_param_spec_uint ("minutes-step",
150                                                       "Step between minutes in the model",
151                                                       "Step between the minutes in the list of"
152                                                       " options of the widget ",
153                                                       1, 30, 1,
154                                                       G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
155
156   /* GtkWidget */
157
158   /* GtkContainer */
159
160   /* signals */
161
162   g_type_class_add_private (object_class, sizeof (HildonTimeSelectorPrivate));
163 }
164
165 static GObject*
166 hildon_time_selector_constructor (GType type,
167                                   guint n_construct_properties,
168                                   GObjectConstructParam *construct_params)
169 {
170   GObject *object;
171   HildonTimeSelector *selector;
172   HildonTouchSelectorColumn *column;
173
174   object = (* G_OBJECT_CLASS (hildon_time_selector_parent_class)->constructor)
175     (type, n_construct_properties, construct_params);
176
177   selector = HILDON_TIME_SELECTOR (object);
178
179   selector->priv->hours_model = _create_hours_model (selector);
180
181   column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
182                                                      selector->priv->hours_model, TRUE);
183   g_object_set (column, "text-column", 0, NULL);
184
185
186   /* we need initialization parameters in order to create minute models*/
187   selector->priv->minutes_step = selector->priv->minutes_step ? selector->priv->minutes_step : 1;
188
189   selector->priv->minutes_model = _create_minutes_model (selector->priv->minutes_step);
190
191   column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
192                                                      selector->priv->minutes_model, TRUE);
193   g_object_set (column, "text-column", 0, NULL);
194
195   if (selector->priv->ampm_format) {
196     selector->priv->ampm_model = _create_ampm_model (selector);
197
198     hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
199                                               selector->priv->ampm_model, TRUE);
200
201     g_signal_connect (G_OBJECT (selector),
202                       "changed", G_CALLBACK (_manage_ampm_selection_cb),
203                       NULL);
204   }
205
206
207   /* By default we should select the current day */
208   hildon_time_selector_set_time (selector,
209                                  selector->priv->creation_hours,
210                                  selector->priv->creation_minutes);
211
212   return object;
213
214 }
215
216 static void
217 hildon_time_selector_init (HildonTimeSelector * selector)
218 {
219   selector->priv = HILDON_TIME_SELECTOR_GET_PRIVATE (selector);
220
221   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (selector), GTK_NO_WINDOW);
222   gtk_widget_set_redraw_on_allocate (GTK_WIDGET (selector), FALSE);
223
224   hildon_touch_selector_set_print_func (HILDON_TOUCH_SELECTOR (selector),
225                                         _custom_print_func);
226
227   _get_real_time (&selector->priv->creation_hours,
228                   &selector->priv->creation_minutes);
229
230   _check_ampm_format (selector);
231
232 }
233
234 static void
235 hildon_time_selector_get_property (GObject *object,
236                                    guint param_id,
237                                    GValue *value,
238                                    GParamSpec *pspec)
239 {
240   HildonTimeSelectorPrivate *priv = HILDON_TIME_SELECTOR_GET_PRIVATE (object);
241
242   switch (param_id)
243     {
244     case PROP_MINUTES_STEP:
245       g_value_set_uint (value, priv->minutes_step);
246       break;
247
248     default:
249       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
250       break;
251     }
252 }
253
254 static void
255 hildon_time_selector_set_property (GObject *object,
256                                    guint param_id,
257                                    const GValue *value,
258                                    GParamSpec *pspec)
259 {
260   HildonTimeSelectorPrivate *priv = HILDON_TIME_SELECTOR_GET_PRIVATE (object);
261
262   switch (param_id)
263     {
264     case PROP_MINUTES_STEP:
265       priv->minutes_step = g_value_get_uint (value);
266       break;
267
268     default:
269       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
270       break;
271     }
272 }
273
274 static void
275 hildon_time_selector_finalize (GObject * object)
276 {
277   /* FIXME: FILL ME !! */
278
279   (*G_OBJECT_CLASS (hildon_time_selector_parent_class)->finalize) (object);
280 }
281
282 /* ------------------------------ PRIVATE METHODS ---------------------------- */
283
284 static gchar *
285 _custom_print_func (HildonTouchSelector * touch_selector)
286 {
287   gchar *result = NULL;
288   struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
289   HildonTimeSelector *selector = NULL;
290   static gchar string[255];
291   guint hours = 0;
292   guint minutes = 0;
293
294   selector = HILDON_TIME_SELECTOR (touch_selector);
295
296   hildon_time_selector_get_time (selector, &hours, &minutes);
297
298   tm.tm_min = minutes;
299   tm.tm_hour = hours;
300
301   if (selector->priv->ampm_format) {
302     if (selector->priv->pm) {
303       strftime (string, 255, _("wdgt_va_12h_time_pm"), &tm);
304     } else {
305       strftime (string, 255, _("wdgt_va_12h_time_am"), &tm);
306     }
307   } else {
308     strftime (string, 255, _("wdgt_va_24h_time"), &tm);
309   }
310
311
312   result = g_strdup (string);
313
314   return result;
315 }
316
317 static GtkTreeModel *
318 _create_minutes_model (guint minutes_step)
319 {
320   GtkListStore *store_minutes = NULL;
321   gint i = 0;
322   static gchar label[255];
323   struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
324   GtkTreeIter iter;
325
326   store_minutes = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
327   for (i = 0; i <= 59; i=i+minutes_step) {
328     tm.tm_min = i;
329     strftime (label, 255, _("wdgt_va_minutes"), &tm);
330
331     gtk_list_store_append (store_minutes, &iter);
332     gtk_list_store_set (store_minutes, &iter,
333                         COLUMN_STRING, label, COLUMN_INT, i, -1);
334   }
335
336   return GTK_TREE_MODEL (store_minutes);
337 }
338
339 static GtkTreeModel *
340 _create_hours_model (HildonTimeSelector * selector)
341 {
342   GtkListStore *store_hours = NULL;
343   gint i = 0;
344   GtkTreeIter iter;
345   struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
346   static gchar label[255];
347   static gint range_12h[12] = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11};
348   static gint range_24h[24] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,
349                                12,13,14,15,16,17,18,19,20,21,22,23};
350   gint *range = NULL;
351   gint num_elements = 0;
352   gchar *format_string = NULL;
353
354   if (selector->priv->ampm_format) {
355     range = range_12h;
356     num_elements = 12;
357     format_string = N_("wdgt_va_12h_hours");
358   } else {
359     range = range_24h;
360     num_elements = 24;
361     format_string = N_("wdgt_va_24h_hours");
362   }
363
364   store_hours = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
365   for (i = 0; i < num_elements; i++) {
366     tm.tm_hour = range[i];
367     strftime (label, 255, _(format_string), &tm);
368
369     gtk_list_store_append (store_hours, &iter);
370     gtk_list_store_set (store_hours, &iter,
371                         COLUMN_STRING, label, COLUMN_INT, range[i], -1);
372   }
373
374   return GTK_TREE_MODEL (store_hours);
375 }
376
377 static GtkTreeModel *
378 _create_ampm_model (HildonTimeSelector * selector)
379 {
380   GtkListStore *store_ampm = NULL;
381   GtkTreeIter iter;
382   static gchar label[255];
383
384   store_ampm = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
385
386   snprintf (label, 255, _("wdgt_va_am"));
387   gtk_list_store_append (store_ampm, &iter);
388   gtk_list_store_set (store_ampm, &iter,
389                       COLUMN_STRING, label,
390                       COLUMN_INT, 0, -1);
391
392   snprintf (label, 255, _("wdgt_va_pm"));
393   gtk_list_store_append (store_ampm, &iter);
394   gtk_list_store_set (store_ampm, &iter,
395                       COLUMN_STRING, label,
396                       COLUMN_INT, 1, -1);
397
398   return GTK_TREE_MODEL (store_ampm);
399 }
400
401 static void
402 _get_real_time (gint * hours, gint * minutes)
403 {
404   time_t secs;
405   struct tm *tm = NULL;
406
407   secs = time (NULL);
408   tm = localtime (&secs);
409
410   if (hours != NULL) {
411     *hours = tm->tm_hour;
412   }
413
414   if (minutes != NULL) {
415     *minutes = tm->tm_min;
416   }
417 }
418
419 static void
420 _manage_ampm_selection_cb (HildonTouchSelector * touch_selector,
421                            gint num_column, gpointer data)
422 {
423   HildonTimeSelector *selector = NULL;
424   gint pm;
425   GtkTreeIter iter;
426
427   g_return_if_fail (HILDON_IS_TIME_SELECTOR (touch_selector));
428   selector = HILDON_TIME_SELECTOR (touch_selector);
429
430   if (num_column == COLUMN_AMPM &&
431       hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
432                                           COLUMN_AMPM, &iter)) {
433      gtk_tree_model_get (selector->priv->ampm_model, &iter, COLUMN_INT, &pm, -1);
434
435     selector->priv->pm = pm;
436   }
437 }
438
439 static void
440 _check_ampm_format (HildonTimeSelector * selector)
441 {
442   GConfClient *client = NULL;
443   gboolean value = TRUE;
444   GError *error = NULL;
445
446   client = gconf_client_get_default ();
447   value = gconf_client_get_bool (client, CLOCK_GCONF_IS_24H_FORMAT, &error);
448   if (error != NULL) {
449     g_warning
450       ("Error trying to get gconf variable %s, using 24h format by default",
451        CLOCK_GCONF_IS_24H_FORMAT);
452     g_error_free (error);
453   }
454
455   selector->priv->ampm_format = !value;
456   selector->priv->pm = TRUE;
457 }
458
459 static void
460 _set_pm (HildonTimeSelector * selector, gboolean pm)
461 {
462   GtkTreeIter iter;
463
464   selector->priv->pm = pm;
465
466   gtk_tree_model_iter_nth_child (selector->priv->ampm_model, &iter, NULL, pm);
467
468   hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
469                                      COLUMN_AMPM, &iter, FALSE);
470 }
471
472 /* ------------------------------ PUBLIC METHODS ---------------------------- */
473
474 /**
475  * hildon_time_selector_new:
476  *
477  * Creates a new #HildonTimeSelector
478  *
479  * Returns: a new #HildonTimeSelector
480  *
481  * Since: 2.2
482  **/
483 GtkWidget *
484 hildon_time_selector_new ()
485 {
486   return g_object_new (HILDON_TYPE_TIME_SELECTOR, NULL);
487 }
488
489
490 /**
491  * hildon_time_selector_new_step:
492  *
493  * Creates a new #HildonTimeSelector
494  * @minutes_step: step between the minutes we are going to show in the
495  * selector
496  *
497  * Returns: a new #HildonTimeSelector
498  *
499  * Since: 2.2
500  **/
501 GtkWidget *
502 hildon_time_selector_new_step (guint minutes_step)
503 {
504   return g_object_new (HILDON_TYPE_TIME_SELECTOR, "minutes-step",
505                        minutes_step, NULL);
506 }
507
508 /**
509  * hildon_time_selector_set_time
510  * @selector: the #HildonTimeSelector
511  * @hours:  the current hour (0-23)
512  * @minutes: the current minute (0-59)
513  *
514  * Sets the current active hour on the #HildonTimeSelector widget
515  *
516  * The format of the hours accepted is always 24h format, with a range
517  * (0-23):(0-59).
518  *
519  * Since: 2.2
520  *
521  * Returns: %TRUE on success, %FALSE otherwise
522  **/
523 gboolean
524 hildon_time_selector_set_time (HildonTimeSelector * selector,
525                                guint hours, guint minutes)
526 {
527   GtkTreeIter iter;
528   gint hours_item = 0;
529
530   g_return_val_if_fail (hours <= 23, FALSE);
531   g_return_val_if_fail (minutes <= 59, FALSE);
532
533   if (selector->priv->ampm_format) {
534     _set_pm (selector, hours >= 12);
535
536     hours_item = hours - selector->priv->pm * 12;
537   } else {
538     hours_item = hours;
539   }
540
541   gtk_tree_model_iter_nth_child (selector->priv->hours_model, &iter, NULL,
542                                  hours_item);
543   hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
544                                      COLUMN_HOURS, &iter, FALSE);
545
546   g_assert (selector->priv->minutes_step>0);
547   minutes = minutes/selector->priv->minutes_step;
548   gtk_tree_model_iter_nth_child (selector->priv->minutes_model, &iter, NULL,
549                                  minutes);
550   hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
551                                      COLUMN_MINUTES, &iter, FALSE);
552
553   return TRUE;
554 }
555
556 /**
557  * hildon_time_selector_get_time
558  * @selector: the #HildonTimeSelector
559  * @hours:  to set the current hour (0-23)
560  * @minutes: to set the current minute (0-59)
561  *
562  * Gets the current active hour on the #HildonTimeSelector widget. Both @year
563  * and @minutes can be NULL.
564  *
565  * This method returns the date always in 24h format, with a range (0-23):(0-59)
566  *
567  * Since: 2.2
568  **/
569 void
570 hildon_time_selector_get_time (HildonTimeSelector * selector,
571                                guint * hours, guint * minutes)
572 {
573   GtkTreeIter iter;
574
575   if (hours != NULL) {
576     hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
577                                         COLUMN_HOURS, &iter);
578     gtk_tree_model_get (selector->priv->hours_model,
579                         &iter, COLUMN_INT, hours, -1);
580     if (selector->priv->ampm_format) {
581       *hours %= 12;
582       *hours += selector->priv->pm * 12;
583     }
584   }
585
586   if (minutes != NULL) {
587     hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
588                                         COLUMN_MINUTES, &iter);
589     gtk_tree_model_get (selector->priv->minutes_model,
590                         &iter, COLUMN_INT, minutes, -1);
591   }
592 }