2009-04-01 Alberto Garcia <agarcia@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                                   gpointer user_data);
128
129 static void
130 hildon_time_selector_class_init (HildonTimeSelectorClass * class)
131 {
132   GObjectClass *gobject_class;
133   GtkObjectClass *object_class;
134   GtkWidgetClass *widget_class;
135   GtkContainerClass *container_class;
136
137   gobject_class = (GObjectClass *) class;
138   object_class = (GtkObjectClass *) class;
139   widget_class = (GtkWidgetClass *) class;
140   container_class = (GtkContainerClass *) class;
141
142   /* GObject */
143   gobject_class->get_property = hildon_time_selector_get_property;
144   gobject_class->set_property = hildon_time_selector_set_property;
145   gobject_class->constructor = hildon_time_selector_constructor;
146   gobject_class->finalize = hildon_time_selector_finalize;
147
148   g_object_class_install_property (gobject_class,
149                                    PROP_MINUTES_STEP,
150                                    g_param_spec_uint ("minutes-step",
151                                                       "Step between minutes in the model",
152                                                       "Step between the minutes in the list of"
153                                                       " options of the widget ",
154                                                       1, 30, 1,
155                                                       G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY));
156
157   /* GtkWidget */
158
159   /* GtkContainer */
160
161   /* signals */
162
163   g_type_class_add_private (object_class, sizeof (HildonTimeSelectorPrivate));
164 }
165
166 static GObject*
167 hildon_time_selector_constructor (GType type,
168                                   guint n_construct_properties,
169                                   GObjectConstructParam *construct_params)
170 {
171   GObject *object;
172   HildonTimeSelector *selector;
173   HildonTouchSelectorColumn *column;
174
175   object = (* G_OBJECT_CLASS (hildon_time_selector_parent_class)->constructor)
176     (type, n_construct_properties, construct_params);
177
178   selector = HILDON_TIME_SELECTOR (object);
179
180   selector->priv->hours_model = _create_hours_model (selector);
181
182   column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
183                                                      selector->priv->hours_model, TRUE);
184   g_object_set (column, "text-column", 0, NULL);
185
186
187   /* we need initialization parameters in order to create minute models*/
188   selector->priv->minutes_step = selector->priv->minutes_step ? selector->priv->minutes_step : 1;
189
190   selector->priv->minutes_model = _create_minutes_model (selector->priv->minutes_step);
191
192   column = hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
193                                                      selector->priv->minutes_model, TRUE);
194   g_object_set (column, "text-column", 0, NULL);
195
196   if (selector->priv->ampm_format) {
197     selector->priv->ampm_model = _create_ampm_model (selector);
198
199     hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
200                                               selector->priv->ampm_model, TRUE);
201
202     g_signal_connect (G_OBJECT (selector),
203                       "changed", G_CALLBACK (_manage_ampm_selection_cb),
204                       NULL);
205   }
206
207
208   /* By default we should select the current day */
209   hildon_time_selector_set_time (selector,
210                                  selector->priv->creation_hours,
211                                  selector->priv->creation_minutes);
212
213   return object;
214
215 }
216
217 static void
218 hildon_time_selector_init (HildonTimeSelector * selector)
219 {
220   selector->priv = HILDON_TIME_SELECTOR_GET_PRIVATE (selector);
221
222   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (selector), GTK_NO_WINDOW);
223   gtk_widget_set_redraw_on_allocate (GTK_WIDGET (selector), FALSE);
224
225   hildon_touch_selector_set_print_func (HILDON_TOUCH_SELECTOR (selector),
226                                         _custom_print_func);
227
228   _get_real_time (&selector->priv->creation_hours,
229                   &selector->priv->creation_minutes);
230
231   _check_ampm_format (selector);
232
233 }
234
235 static void
236 hildon_time_selector_get_property (GObject *object,
237                                    guint param_id,
238                                    GValue *value,
239                                    GParamSpec *pspec)
240 {
241   HildonTimeSelectorPrivate *priv = HILDON_TIME_SELECTOR_GET_PRIVATE (object);
242
243   switch (param_id)
244     {
245     case PROP_MINUTES_STEP:
246       g_value_set_uint (value, priv->minutes_step);
247       break;
248
249     default:
250       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
251       break;
252     }
253 }
254
255 static void
256 hildon_time_selector_set_property (GObject *object,
257                                    guint param_id,
258                                    const GValue *value,
259                                    GParamSpec *pspec)
260 {
261   HildonTimeSelectorPrivate *priv = HILDON_TIME_SELECTOR_GET_PRIVATE (object);
262
263   switch (param_id)
264     {
265     case PROP_MINUTES_STEP:
266       priv->minutes_step = g_value_get_uint (value);
267       break;
268
269     default:
270       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
271       break;
272     }
273 }
274
275 static void
276 hildon_time_selector_finalize (GObject * object)
277 {
278   /* FIXME: FILL ME !! */
279
280   (*G_OBJECT_CLASS (hildon_time_selector_parent_class)->finalize) (object);
281 }
282
283 /* ------------------------------ PRIVATE METHODS ---------------------------- */
284
285 static gchar *
286 _custom_print_func (HildonTouchSelector * touch_selector,
287                     gpointer user_data)
288 {
289   gchar *result = NULL;
290   struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
291   HildonTimeSelector *selector = NULL;
292   static gchar string[255];
293   guint hours = 0;
294   guint minutes = 0;
295
296   selector = HILDON_TIME_SELECTOR (touch_selector);
297
298   hildon_time_selector_get_time (selector, &hours, &minutes);
299
300   tm.tm_min = minutes;
301   tm.tm_hour = hours;
302
303   if (selector->priv->ampm_format) {
304     if (selector->priv->pm) {
305       strftime (string, 255, _("wdgt_va_12h_time_pm"), &tm);
306     } else {
307       strftime (string, 255, _("wdgt_va_12h_time_am"), &tm);
308     }
309   } else {
310     strftime (string, 255, _("wdgt_va_24h_time"), &tm);
311   }
312
313
314   result = g_strdup (string);
315
316   return result;
317 }
318
319 static GtkTreeModel *
320 _create_minutes_model (guint minutes_step)
321 {
322   GtkListStore *store_minutes = NULL;
323   gint i = 0;
324   static gchar label[255];
325   struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
326   GtkTreeIter iter;
327
328   store_minutes = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
329   for (i = 0; i <= 59; i=i+minutes_step) {
330     tm.tm_min = i;
331     strftime (label, 255, _("wdgt_va_minutes"), &tm);
332
333     gtk_list_store_append (store_minutes, &iter);
334     gtk_list_store_set (store_minutes, &iter,
335                         COLUMN_STRING, label, COLUMN_INT, i, -1);
336   }
337
338   return GTK_TREE_MODEL (store_minutes);
339 }
340
341 static GtkTreeModel *
342 _create_hours_model (HildonTimeSelector * selector)
343 {
344   GtkListStore *store_hours = NULL;
345   gint i = 0;
346   GtkTreeIter iter;
347   struct tm tm = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
348   static gchar label[255];
349   static gint range_12h[12] = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11};
350   static gint range_24h[24] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,
351                                12,13,14,15,16,17,18,19,20,21,22,23};
352   gint *range = NULL;
353   gint num_elements = 0;
354   gchar *format_string = NULL;
355
356   if (selector->priv->ampm_format) {
357     range = range_12h;
358     num_elements = 12;
359     format_string = N_("wdgt_va_12h_hours");
360   } else {
361     range = range_24h;
362     num_elements = 24;
363     format_string = N_("wdgt_va_24h_hours");
364   }
365
366   store_hours = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
367   for (i = 0; i < num_elements; i++) {
368     tm.tm_hour = range[i];
369     strftime (label, 255, _(format_string), &tm);
370
371     gtk_list_store_append (store_hours, &iter);
372     gtk_list_store_set (store_hours, &iter,
373                         COLUMN_STRING, label, COLUMN_INT, range[i], -1);
374   }
375
376   return GTK_TREE_MODEL (store_hours);
377 }
378
379 static GtkTreeModel *
380 _create_ampm_model (HildonTimeSelector * selector)
381 {
382   GtkListStore *store_ampm = NULL;
383   GtkTreeIter iter;
384   static gchar label[255];
385
386   store_ampm = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
387
388   snprintf (label, 255, _("wdgt_va_am"));
389   gtk_list_store_append (store_ampm, &iter);
390   gtk_list_store_set (store_ampm, &iter,
391                       COLUMN_STRING, label,
392                       COLUMN_INT, 0, -1);
393
394   snprintf (label, 255, _("wdgt_va_pm"));
395   gtk_list_store_append (store_ampm, &iter);
396   gtk_list_store_set (store_ampm, &iter,
397                       COLUMN_STRING, label,
398                       COLUMN_INT, 1, -1);
399
400   return GTK_TREE_MODEL (store_ampm);
401 }
402
403 static void
404 _get_real_time (gint * hours, gint * minutes)
405 {
406   time_t secs;
407   struct tm *tm = NULL;
408
409   secs = time (NULL);
410   tm = localtime (&secs);
411
412   if (hours != NULL) {
413     *hours = tm->tm_hour;
414   }
415
416   if (minutes != NULL) {
417     *minutes = tm->tm_min;
418   }
419 }
420
421 static void
422 _manage_ampm_selection_cb (HildonTouchSelector * touch_selector,
423                            gint num_column, gpointer data)
424 {
425   HildonTimeSelector *selector = NULL;
426   gint pm;
427   GtkTreeIter iter;
428
429   g_return_if_fail (HILDON_IS_TIME_SELECTOR (touch_selector));
430   selector = HILDON_TIME_SELECTOR (touch_selector);
431
432   if (num_column == COLUMN_AMPM &&
433       hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
434                                           COLUMN_AMPM, &iter)) {
435      gtk_tree_model_get (selector->priv->ampm_model, &iter, COLUMN_INT, &pm, -1);
436
437     selector->priv->pm = pm;
438   }
439 }
440
441 static void
442 _check_ampm_format (HildonTimeSelector * selector)
443 {
444   GConfClient *client = NULL;
445   gboolean value = TRUE;
446   GError *error = NULL;
447
448   client = gconf_client_get_default ();
449   value = gconf_client_get_bool (client, CLOCK_GCONF_IS_24H_FORMAT, &error);
450   if (error != NULL) {
451     g_warning
452       ("Error trying to get gconf variable %s, using 24h format by default",
453        CLOCK_GCONF_IS_24H_FORMAT);
454     g_error_free (error);
455   }
456
457   selector->priv->ampm_format = !value;
458   selector->priv->pm = TRUE;
459 }
460
461 static void
462 _set_pm (HildonTimeSelector * selector, gboolean pm)
463 {
464   GtkTreeIter iter;
465
466   selector->priv->pm = pm;
467
468   gtk_tree_model_iter_nth_child (selector->priv->ampm_model, &iter, NULL, pm);
469
470   hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
471                                      COLUMN_AMPM, &iter, FALSE);
472 }
473
474 /* ------------------------------ PUBLIC METHODS ---------------------------- */
475
476 /**
477  * hildon_time_selector_new:
478  *
479  * Creates a new #HildonTimeSelector
480  *
481  * Returns: a new #HildonTimeSelector
482  *
483  * Since: 2.2
484  **/
485 GtkWidget *
486 hildon_time_selector_new ()
487 {
488   return g_object_new (HILDON_TYPE_TIME_SELECTOR, NULL);
489 }
490
491
492 /**
493  * hildon_time_selector_new_step:
494  *
495  * Creates a new #HildonTimeSelector
496  * @minutes_step: step between the minutes we are going to show in the
497  * selector
498  *
499  * Returns: a new #HildonTimeSelector
500  *
501  * Since: 2.2
502  **/
503 GtkWidget *
504 hildon_time_selector_new_step (guint minutes_step)
505 {
506   return g_object_new (HILDON_TYPE_TIME_SELECTOR, "minutes-step",
507                        minutes_step, NULL);
508 }
509
510 /**
511  * hildon_time_selector_set_time
512  * @selector: the #HildonTimeSelector
513  * @hours:  the current hour (0-23)
514  * @minutes: the current minute (0-59)
515  *
516  * Sets the current active hour on the #HildonTimeSelector widget
517  *
518  * The format of the hours accepted is always 24h format, with a range
519  * (0-23):(0-59).
520  *
521  * Since: 2.2
522  *
523  * Returns: %TRUE on success, %FALSE otherwise
524  **/
525 gboolean
526 hildon_time_selector_set_time (HildonTimeSelector * selector,
527                                guint hours, guint minutes)
528 {
529   GtkTreeIter iter;
530   gint hours_item = 0;
531
532   g_return_val_if_fail (hours <= 23, FALSE);
533   g_return_val_if_fail (minutes <= 59, FALSE);
534
535   if (selector->priv->ampm_format) {
536     _set_pm (selector, hours >= 12);
537
538     hours_item = hours - selector->priv->pm * 12;
539   } else {
540     hours_item = hours;
541   }
542
543   gtk_tree_model_iter_nth_child (selector->priv->hours_model, &iter, NULL,
544                                  hours_item);
545   hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
546                                      COLUMN_HOURS, &iter, FALSE);
547
548   g_assert (selector->priv->minutes_step>0);
549   minutes = minutes/selector->priv->minutes_step;
550   gtk_tree_model_iter_nth_child (selector->priv->minutes_model, &iter, NULL,
551                                  minutes);
552   hildon_touch_selector_select_iter (HILDON_TOUCH_SELECTOR (selector),
553                                      COLUMN_MINUTES, &iter, FALSE);
554
555   return TRUE;
556 }
557
558 /**
559  * hildon_time_selector_get_time
560  * @selector: the #HildonTimeSelector
561  * @hours:  to set the current hour (0-23)
562  * @minutes: to set the current minute (0-59)
563  *
564  * Gets the current active hour on the #HildonTimeSelector widget. Both @year
565  * and @minutes can be NULL.
566  *
567  * This method returns the date always in 24h format, with a range (0-23):(0-59)
568  *
569  * Since: 2.2
570  **/
571 void
572 hildon_time_selector_get_time (HildonTimeSelector * selector,
573                                guint * hours, guint * minutes)
574 {
575   GtkTreeIter iter;
576
577   if (hours != NULL) {
578     hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
579                                         COLUMN_HOURS, &iter);
580     gtk_tree_model_get (selector->priv->hours_model,
581                         &iter, COLUMN_INT, hours, -1);
582     if (selector->priv->ampm_format) {
583       *hours %= 12;
584       *hours += selector->priv->pm * 12;
585     }
586   }
587
588   if (minutes != NULL) {
589     hildon_touch_selector_get_selected (HILDON_TOUCH_SELECTOR (selector),
590                                         COLUMN_MINUTES, &iter);
591     gtk_tree_model_get (selector->priv->minutes_model,
592                         &iter, COLUMN_INT, minutes, -1);
593   }
594 }