114af7cb22aa7d30d3c1818da77ffd0019725bd1
[hildon] / src / hildon-time-editor.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2005, 2006 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Rodrigo Novo <rodrigo.novo@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; version 2.1 of
11  * the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24
25 /**
26  * SECTION:hildon-time-editor
27  * @short_description: A widget used to enter time or duration in hours, minutes,
28  * and optional seconds.
29  * @see_also: #HildonTimePicker
30  * 
31  * HildonTimeEditor is used to edit time or duration. Time mode is
32  * restricted to normal 24 hour cycle, but Duration mode can select any
33  * amount of time up to 99 hours.  It consists of entries for hours,
34  * minutes and seconds, and pm/am indicator as well as a button which
35  * popups a #HildonTimePicker dialog.
36  *
37  * <example>
38  * <title>HildonTimePicker example</title>
39  * <programlisting>
40  * <!-- -->
41  * editor = hildon_time_editor_new ();
42  * hildon_time_editor_set_time (editor, h, m, s);
43  * <!-- -->
44  * gtk_box_pack_start (..., editor)
45  * <!-- -->
46  * hildon_time_editor_get_time (editor, &amp;h, &amp;m, &amp;s);
47  * <!-- -->
48  * </programlisting>
49  * </example>
50  *
51  */
52
53 #undef                                          HILDON_DISABLE_DEPRECATED
54
55 #ifdef                                          HAVE_CONFIG_H
56 #include                                        <config.h>
57 #endif
58
59 #include                                        <string.h>
60 #include                                        <time.h>
61 #include                                        <stdlib.h>
62 #include                                        <langinfo.h>
63 #include                                        <libintl.h>
64 #include                                        <gdk/gdkkeysyms.h>
65
66 #include                                        "hildon-time-editor.h"
67 #include                                        "hildon-defines.h"
68 #include                                        "hildon-time-picker.h"
69 #include                                        "hildon-banner.h"
70 #include                                        "hildon-private.h"
71 #include                                        "hildon-marshalers.h"
72 #include                                        "hildon-enum-types.h"
73 #include                                        "hildon-time-editor-private.h"
74
75 #define                                         _(String) dgettext("hildon-libs", String)
76
77 #define                                         c_(String) dgettext("hildon-common-strings", String)
78
79 #define                                         TICKS(h,m,s) \
80                                                 ((h) * 3600 + (m) * 60 + (s))
81
82 #define                                         TIME_EDITOR_HEIGHT 30
83
84 #define                                         ICON_PRESSED 4
85
86 #define                                         ICON_NAME "qgn_widg_timedit"
87
88 #define                                         ICON_SIZE "timepicker-size"
89
90 #define                                         MIN_DURATION 0
91
92 #define                                         MAX_DURATION TICKS(99, 59, 59)
93
94 /* Default values for properties */
95
96 #define                                         HILDON_TIME_EDITOR_TICKS_VALUE 0
97
98 #define                                         HILDON_TIME_EDITOR_DURATION_MODE FALSE
99
100 #define                                         HILDON_TIME_EDITOR_DURATION_LOWER_VALUE 0
101
102 #define                                         HILDON_TIME_EDITOR_DURATION_UPPER_VALUE TICKS(99, 59, 59)
103
104 #define                                         HOURS_MAX_24 23
105
106 #define                                         HOURS_MAX_12 12
107
108 #define                                         HOURS_MIN_24 0
109
110 #define                                         HOURS_MIN_12 1
111
112 #define                                         MINUTES_MAX 59
113
114 #define                                         SECONDS_MAX 59
115
116 #define                                         MINUTES_MIN 0
117
118 #define                                         SECONDS_MIN 0
119
120 static GtkContainerClass*                       parent_class;
121
122 enum
123 {
124     PROP_0,
125     PROP_TICKS,
126     PROP_DURATION_MODE,
127     PROP_DURATION_MIN,
128     PROP_DURATION_MAX,
129     PROP_SHOW_SECONDS,
130     PROP_SHOW_HOURS
131 };
132
133 /* Signals */
134 enum {
135     TIME_ERROR,
136     LAST_SIGNAL
137 };
138
139 /* Error codes categories */
140 enum {
141     MAX_VALUE,
142     MIN_VALUE,
143     WITHIN_RANGE,
144     NUM_ERROR_CODES
145 };
146
147 static guint                                    time_editor_signals[LAST_SIGNAL] = { 0 };
148
149 static guint                                    hour_errors[NUM_ERROR_CODES] = {
150                                                 HILDON_DATE_TIME_ERROR_MAX_HOURS, 
151                                                 HILDON_DATE_TIME_ERROR_MIN_HOURS, 
152                                                 HILDON_DATE_TIME_ERROR_EMPTY_HOURS };
153
154 static guint                                    min_errors[NUM_ERROR_CODES] = { 
155                                                 HILDON_DATE_TIME_ERROR_MAX_MINS,  
156                                                 HILDON_DATE_TIME_ERROR_MIN_MINS,  
157                                                 HILDON_DATE_TIME_ERROR_EMPTY_MINS };
158
159 static guint                                    sec_errors[NUM_ERROR_CODES] = { 
160                                                 HILDON_DATE_TIME_ERROR_MAX_SECS, 
161                                                 HILDON_DATE_TIME_ERROR_MIN_SECS, 
162                                                 HILDON_DATE_TIME_ERROR_EMPTY_SECS };
163
164 static void 
165 hildon_time_editor_class_init                   (HildonTimeEditorClass *editor_class);
166
167 static void 
168 hildon_time_editor_init                         (HildonTimeEditor *editor);
169
170 static void 
171 hildon_time_editor_finalize                     (GObject *obj_self);
172
173 static void
174 hildon_time_editor_set_property                 (GObject *object,
175                                                  guint param_id,
176                                                  const GValue *value,
177                                                  GParamSpec *pspec);
178
179 static void 
180 hildon_time_editor_get_property                 (GObject *object,
181                                                  guint param_id,
182                                                  GValue *value,
183                                                  GParamSpec *pspec);
184
185 static void
186 hildon_time_editor_forall                       (GtkContainer *container,
187                                                  gboolean include_internals,
188                                                  GtkCallback callback,
189                                                  gpointer callback_data);
190                           
191 static void
192 hildon_time_editor_destroy                      (GtkObject *self);
193
194 static gboolean
195 hildon_time_editor_entry_focus_out              (GtkWidget *widget,
196                                                  GdkEventFocus *event,
197                                                  gpointer data);
198
199 static gboolean 
200 hildon_time_editor_entry_focus_in               (GtkWidget *widget,
201                                                  GdkEventFocus *event, 
202                                                  gpointer data);
203
204 static gboolean
205 hildon_time_editor_time_error                   (HildonTimeEditor *editor,
206                                                  HildonDateTimeError type);
207
208 static gboolean 
209 hildon_time_editor_ampm_clicked                 (GtkWidget *widget,
210                                                  gpointer data);
211
212 static gboolean 
213 hildon_time_editor_icon_clicked                 (GtkWidget *widget,
214                                                  gpointer data);
215
216 static void     
217 hildon_time_editor_size_request                 (GtkWidget *widget,
218                                                  GtkRequisition *requisition);
219
220 static void    
221 hildon_time_editor_size_allocate                (GtkWidget *widget,
222                                                  GtkAllocation *allocation);
223
224 static gboolean
225 hildon_time_editor_focus                        (GtkWidget *widget,
226                                                  GtkDirectionType direction);
227
228 static gboolean
229 hildon_time_editor_entry_keypress (GtkEntry *entry,
230                                    GdkEventKey* event,
231                                    gpointer user_data);
232
233 static gboolean
234 hildon_time_editor_check_locale                 (HildonTimeEditor *editor);
235
236 #ifdef MAEMO_GTK 
237 static void 
238 hildon_time_editor_tap_and_hold_setup           (GtkWidget *widget,
239                                                  GtkWidget *menu,
240                                                  GtkCallback func,
241                                                  GtkWidgetTapAndHoldFlags flags);
242 #endif
243
244 static void
245 hildon_time_editor_validate                     (HildonTimeEditor *editor, 
246                                                  gboolean allow_intermediate);
247
248 static void 
249 hildon_time_editor_set_to_current_time          (HildonTimeEditor *editor);
250
251 static gboolean
252 hildon_time_editor_entry_select_all             (GtkWidget *widget);
253
254 static void 
255 convert_to_12h                                  (guint *h, 
256                                                  gboolean *am);
257
258 static void 
259 convert_to_24h                                  (guint *h, 
260                                                  gboolean am);
261
262 static void 
263 ticks_to_time                                   (guint ticks,
264                                                  guint *hours,
265                                                  guint *minutes,
266                                                  guint *seconds);
267
268 static void
269 hildon_time_editor_inserted_text                (GtkEditable *editable,
270                                                  gchar *new_text,
271                                                  gint new_text_length,
272                                                  gint *position,
273                                                  gpointer user_data);
274
275 /**
276  * hildon_time_editor_get_type:
277  *
278  * Initializes and returns the type of a hildon time editor.
279  *
280  * Returns: GType of #HildonTimeEditor
281  */
282 GType G_GNUC_CONST
283 hildon_time_editor_get_type                     (void)
284 {
285     static GType editor_type = 0;
286
287     if (! editor_type) {
288         static const GTypeInfo editor_info = {
289             sizeof(HildonTimeEditorClass),
290             NULL,       /* base_init      */
291             NULL,       /* base_finalize  */
292             (GClassInitFunc) hildon_time_editor_class_init,
293             NULL,       /* class_finalize */
294             NULL,       /* class_data     */
295             sizeof(HildonTimeEditor),
296             0,          /* n_preallocs    */
297             (GInstanceInitFunc) hildon_time_editor_init,
298         };
299         editor_type = g_type_register_static (GTK_TYPE_CONTAINER,
300                 "HildonTimeEditor",
301                 &editor_info, 0);
302     }
303
304     return editor_type;
305 }
306
307 static void 
308 hildon_time_editor_forall                       (GtkContainer *container,
309                                                  gboolean include_internals,
310                                                  GtkCallback callback,
311                                                  gpointer callback_data)
312 {
313     HildonTimeEditorPrivate *priv;
314
315     g_assert (HILDON_IS_TIME_EDITOR (container));
316     g_assert (callback != NULL);
317
318     priv = HILDON_TIME_EDITOR_GET_PRIVATE (container);
319
320     g_assert (priv);
321
322     if (! include_internals)
323         return;
324
325     /* widget that are always shown */
326     (*callback) (priv->iconbutton, callback_data);
327     (*callback) (priv->frame, callback_data);
328 }
329
330 static void 
331 hildon_time_editor_destroy                      (GtkObject *self)
332 {
333     HildonTimeEditorPrivate *priv;
334
335     priv = HILDON_TIME_EDITOR_GET_PRIVATE (self);
336     g_assert (priv);
337
338     if (priv->iconbutton) {
339         gtk_widget_unparent (priv->iconbutton);
340         priv->iconbutton = NULL;
341     }
342     if (priv->frame) {
343         gtk_widget_unparent (priv->frame);
344         priv->frame = NULL;
345     }
346
347     if (GTK_OBJECT_CLASS (parent_class)->destroy)
348         GTK_OBJECT_CLASS (parent_class)->destroy (self);
349 }
350
351 static void
352 hildon_time_editor_class_init                   (HildonTimeEditorClass *editor_class)
353 {
354     GObjectClass *object_class = G_OBJECT_CLASS (editor_class);
355     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (editor_class);
356     GtkContainerClass *container_class = GTK_CONTAINER_CLASS (editor_class);
357
358     parent_class = g_type_class_peek_parent (editor_class);
359
360     g_type_class_add_private (editor_class, sizeof (HildonTimeEditorPrivate));
361
362     object_class->get_property                  = hildon_time_editor_get_property;
363     object_class->set_property                  = hildon_time_editor_set_property;
364     widget_class->size_request                  = hildon_time_editor_size_request;
365     widget_class->size_allocate                 = hildon_time_editor_size_allocate;
366     widget_class->focus                         = hildon_time_editor_focus;
367
368     container_class->forall                     = hildon_time_editor_forall;
369     GTK_OBJECT_CLASS (editor_class)->destroy    = hildon_time_editor_destroy;
370
371     object_class->finalize                      = hildon_time_editor_finalize;
372
373     editor_class->time_error                    = hildon_time_editor_time_error; 
374
375     time_editor_signals[TIME_ERROR] =
376         g_signal_new ("time-error",
377                 G_OBJECT_CLASS_TYPE (object_class),
378                 G_SIGNAL_RUN_LAST,
379                 G_STRUCT_OFFSET (HildonTimeEditorClass, time_error),
380                 g_signal_accumulator_true_handled, NULL,
381                 _hildon_marshal_BOOLEAN__ENUM,
382                 G_TYPE_BOOLEAN, 1, HILDON_TYPE_DATE_TIME_ERROR);
383
384     /**
385      * HildonTimeEditor:ticks:
386      *
387      * If editor is in duration mode, contains the duration seconds.
388      * If not, contains seconds since midnight.
389      */
390     g_object_class_install_property (object_class, PROP_TICKS,
391             g_param_spec_uint ("ticks",
392                 "Duration value",
393                 "Current value of duration",
394                 0, G_MAXUINT,
395                 HILDON_TIME_EDITOR_TICKS_VALUE,
396                 G_PARAM_READABLE | G_PARAM_WRITABLE) );
397
398     /**
399      * HildonTimeEditor:show_seconds:
400      *
401      * Controls whether seconds are shown in the editor
402      */
403     g_object_class_install_property (object_class, PROP_SHOW_SECONDS,
404             g_param_spec_boolean ("show_seconds",
405                 "Show seconds property",
406                 "Controls whether the seconds are shown in the editor",
407                 FALSE,
408                 G_PARAM_READABLE | G_PARAM_WRITABLE) );
409
410     /**
411      * HildonTimeEditor:show_hours:
412      *
413      * Controls whether hours are shown in the editor
414      */
415     g_object_class_install_property (object_class, PROP_SHOW_HOURS,
416             g_param_spec_boolean ("show_hours",
417                 "Show hours field",
418                 "Controls whether the hours field is shown in the editor",
419                 TRUE,
420                 G_PARAM_READABLE | G_PARAM_WRITABLE) );
421
422     /**
423      * HildonTimeEditor:duration_mode:
424      *
425      * Controls whether the TimeEditor is in duration mode
426      */
427     g_object_class_install_property (object_class, PROP_DURATION_MODE,
428             g_param_spec_boolean ("duration_mode",
429                 "Duration mode",
430                 "Controls whether the TimeEditor is in duration mode",
431                 HILDON_TIME_EDITOR_DURATION_MODE,
432                 G_PARAM_READABLE | G_PARAM_WRITABLE) );
433
434     /**
435      * HildonTimeEditor:duration_min:
436      *
437      * Minimum allowed duration value.
438      */
439     g_object_class_install_property (object_class, PROP_DURATION_MIN,
440             g_param_spec_uint ("duration_min",
441                 "Minumum duration value",
442                 "Smallest possible duration value",
443                 MIN_DURATION, MAX_DURATION,
444                 HILDON_TIME_EDITOR_DURATION_LOWER_VALUE,
445                 G_PARAM_READABLE | G_PARAM_WRITABLE) );
446
447     /**
448      * HildonTimeEditor:duration_max:
449      *
450      * Maximum allowed duration value.
451      */
452     g_object_class_install_property (object_class, PROP_DURATION_MAX,
453             g_param_spec_uint ("duration_max",
454                 "Maximum duration value",
455                 "Largest possible duration value",
456                 0, G_MAXUINT,
457                 HILDON_TIME_EDITOR_DURATION_UPPER_VALUE,
458                 G_PARAM_READABLE | G_PARAM_WRITABLE) );
459 }
460
461 #ifdef MAEMO_GTK 
462 static void 
463 hildon_time_editor_tap_and_hold_setup           (GtkWidget *widget,
464                                                  GtkWidget *menu,
465                                                  GtkCallback func,
466                                                  GtkWidgetTapAndHoldFlags flags)
467 {
468     HildonTimeEditorPrivate *priv = HILDON_TIME_EDITOR_GET_PRIVATE (widget);
469     gint i;
470
471     /* Forward this tap_and_hold_setup signal to all our child widgets */
472     for (i = 0; i < ENTRY_COUNT; i++)
473     {
474         gtk_widget_tap_and_hold_setup (priv->entries[i], menu, func,
475                 GTK_TAP_AND_HOLD_NO_SIGNALS);
476     }
477     gtk_widget_tap_and_hold_setup (priv->ampm_button, menu, func,
478             GTK_TAP_AND_HOLD_NO_SIGNALS);
479     gtk_widget_tap_and_hold_setup (priv->iconbutton, menu, func,
480             GTK_TAP_AND_HOLD_NONE);
481 }
482 #endif
483
484 static void 
485 hildon_time_editor_entry_changed                (GtkWidget *widget, 
486                                                  gpointer data)
487 {
488     g_assert (HILDON_IS_TIME_EDITOR (data));
489     hildon_time_editor_validate (HILDON_TIME_EDITOR (data), TRUE);
490 }
491
492 static void 
493 hildon_time_editor_init                         (HildonTimeEditor *editor)
494 {
495     HildonTimeEditorPrivate *priv;
496     GtkWidget *hbox, *icon;
497     gint i;
498
499     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
500     g_assert (priv);
501
502     gtk_widget_push_composite_child ();
503
504     /* Setup defaults and create widgets */
505     priv->ticks          = 0;
506     priv->show_seconds   = FALSE;
507     priv->show_hours     = TRUE;
508     priv->ampm_pos_after = TRUE;
509     priv->clock_24h      = TRUE;
510     priv->duration_mode  = FALSE;
511     priv->iconbutton     = gtk_button_new();
512     priv->ampm_label     = gtk_label_new(NULL);
513     priv->hm_label       = gtk_label_new(NULL);
514     priv->sec_label      = gtk_label_new(NULL);
515     priv->frame          = gtk_frame_new(NULL);
516     priv->ampm_button    = gtk_button_new();
517     priv->skipper        = FALSE;
518
519     icon = gtk_image_new_from_icon_name (ICON_NAME, HILDON_ICON_SIZE_SMALL);
520     hbox = gtk_hbox_new (FALSE, 0);
521
522     GTK_WIDGET_SET_FLAGS (editor, GTK_NO_WINDOW);
523     GTK_WIDGET_UNSET_FLAGS (priv->iconbutton, GTK_CAN_FOCUS | GTK_CAN_DEFAULT);
524
525     gtk_container_set_border_width (GTK_CONTAINER(priv->frame), 0);
526
527     gtk_container_add (GTK_CONTAINER (priv->iconbutton), icon);
528     gtk_container_add (GTK_CONTAINER (priv->ampm_button), priv->ampm_label);
529     gtk_button_set_relief(GTK_BUTTON (priv->ampm_button), GTK_RELIEF_NONE);
530     gtk_button_set_focus_on_click (GTK_BUTTON (priv->ampm_button), FALSE);
531
532     /* Create hour, minute and second entries */
533     for (i = 0; i < ENTRY_COUNT; i++)
534     {
535         priv->entries[i] = gtk_entry_new ();
536
537         /* No frames for entries, so that they all appear to be inside one long entry */
538         gtk_entry_set_has_frame (GTK_ENTRY (priv->entries[i]), FALSE);
539
540 #ifdef MAEMO_GTK 
541         /* Set the entries to accept only numeric characters */
542         g_object_set (priv->entries[i], "hildon-input-mode", HILDON_GTK_INPUT_MODE_NUMERIC, NULL);
543 #endif
544
545         /* The entry fields all take exactly two characters */
546         gtk_entry_set_max_length (GTK_ENTRY (priv->entries[i]), 2);
547         gtk_entry_set_width_chars (GTK_ENTRY (priv->entries[i]), 2);
548
549         g_signal_connect (priv->entries[i], "focus-in-event",
550                 G_CALLBACK (hildon_time_editor_entry_focus_in), editor);
551         g_signal_connect (priv->entries[i], "focus-out-event",
552                 G_CALLBACK (hildon_time_editor_entry_focus_out), editor);
553         g_signal_connect (priv->entries[i], "key-press-event",
554                 G_CALLBACK (hildon_time_editor_entry_keypress), editor);
555         g_signal_connect (priv->entries[i], "changed",
556                 G_CALLBACK (hildon_time_editor_entry_changed), editor);
557
558         /* inserted signal sets time */
559         g_signal_connect_after (G_OBJECT(priv->entries[i]), "insert_text",
560                 G_CALLBACK (hildon_time_editor_inserted_text), 
561                 editor);
562     }
563
564     /* clicked signal for am/pm label */
565     g_signal_connect (G_OBJECT (priv->ampm_button), "clicked",
566             G_CALLBACK (hildon_time_editor_ampm_clicked), editor);
567
568     /* clicked signal for icon */
569     g_signal_connect (G_OBJECT (priv->iconbutton), "clicked",
570             G_CALLBACK (hildon_time_editor_icon_clicked), editor);
571
572     /* Set ourself as the parent of all the widgets we created */
573     gtk_widget_set_parent (priv->iconbutton, GTK_WIDGET(editor));
574     gtk_box_pack_start (GTK_BOX (hbox), priv->entries[ENTRY_HOURS], FALSE, FALSE, 0);
575     gtk_box_pack_start (GTK_BOX (hbox), priv->hm_label,             FALSE, FALSE, 0);
576     gtk_box_pack_start (GTK_BOX (hbox), priv->entries[ENTRY_MINS],  FALSE, FALSE, 0);
577     gtk_box_pack_start (GTK_BOX (hbox), priv->sec_label,            FALSE, FALSE, 0);
578     gtk_box_pack_start (GTK_BOX (hbox), priv->entries[ENTRY_SECS],  FALSE, FALSE, 0);
579     gtk_box_pack_start (GTK_BOX (hbox), priv->ampm_button,          FALSE, FALSE, 0);
580     gtk_misc_set_padding (GTK_MISC (priv->ampm_label), 0, 0);
581
582     gtk_container_add (GTK_CONTAINER (priv->frame), hbox);
583
584     /* Show created widgets */
585     gtk_widget_set_parent (priv->frame, GTK_WIDGET(editor));
586     gtk_widget_show_all (priv->frame);
587     gtk_widget_show_all (priv->iconbutton);
588
589     /* Update AM/PM and time separators settings from locale */
590     if (! hildon_time_editor_check_locale (editor)) {
591         /* Using 12h clock */
592         priv->clock_24h = FALSE;
593     } else {
594         gtk_widget_hide (priv->ampm_button);
595     }
596
597     if (! priv->show_seconds) {
598         gtk_widget_hide (priv->sec_label);
599         gtk_widget_hide (priv->entries[ENTRY_SECS]);
600     }
601
602     /* set the default time to current time. */
603     hildon_time_editor_set_to_current_time (editor);
604
605     gtk_widget_pop_composite_child ();
606
607 #ifdef MAEMO_GTK 
608     g_signal_connect (editor, "tap-and-hold-setup",
609                       G_CALLBACK (hildon_time_editor_tap_and_hold_setup),
610                       NULL);
611 #endif
612
613 }
614
615 static void 
616 hildon_time_editor_set_property                 (GObject *object,
617                                                  guint param_id,
618                                                  const GValue *value,
619                                                  GParamSpec *pspec)
620 {
621     HildonTimeEditor *time_editor = HILDON_TIME_EDITOR (object);
622
623     switch (param_id)
624     {
625         case PROP_TICKS:
626             hildon_time_editor_set_ticks (time_editor, g_value_get_uint(value));
627             break;
628
629         case PROP_SHOW_SECONDS:
630             hildon_time_editor_set_show_seconds (time_editor, g_value_get_boolean(value));
631             break;
632
633         case PROP_SHOW_HOURS:
634             hildon_time_editor_set_show_hours (time_editor, g_value_get_boolean(value));
635             break;
636
637         case PROP_DURATION_MODE:
638             hildon_time_editor_set_duration_mode (time_editor, g_value_get_boolean(value));
639             break;
640
641         case PROP_DURATION_MIN:
642             hildon_time_editor_set_duration_min (time_editor, g_value_get_uint(value));
643             break;
644
645         case PROP_DURATION_MAX:
646             hildon_time_editor_set_duration_max (time_editor, g_value_get_uint(value));
647             break;
648
649         default:
650             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
651             break;
652     }
653 }
654
655 static void 
656 hildon_time_editor_get_property                 (GObject *object,
657                                                  guint param_id,
658                                                  GValue *value,
659                                                  GParamSpec *pspec)
660 {
661     HildonTimeEditor *time_editor = HILDON_TIME_EDITOR (object);
662
663     switch (param_id)
664     {
665
666         case PROP_TICKS:
667             g_value_set_uint (value, hildon_time_editor_get_ticks (time_editor));
668             break;
669
670         case PROP_SHOW_SECONDS:
671             g_value_set_boolean (value, hildon_time_editor_get_show_seconds (time_editor));
672             break;
673
674         case PROP_SHOW_HOURS:
675             g_value_set_boolean (value, hildon_time_editor_get_show_hours (time_editor));
676             break;
677
678         case PROP_DURATION_MODE:
679             g_value_set_boolean (value, hildon_time_editor_get_duration_mode (time_editor));
680             break;
681
682         case PROP_DURATION_MIN:
683             g_value_set_uint (value, hildon_time_editor_get_duration_min (time_editor));
684             break;
685
686         case PROP_DURATION_MAX:
687             g_value_set_uint (value, hildon_time_editor_get_duration_max (time_editor));
688             break;
689
690         default:
691             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
692             break;
693     }
694 }
695
696 /*
697  * hildon_time_editor_new:
698  *
699  * This function creates a new time editor. 
700  *
701  * Returns: pointer to a new #HildonTimeEditor widget
702  */
703 GtkWidget*
704 hildon_time_editor_new                          (void)
705 {
706     return GTK_WIDGET (g_object_new (HILDON_TYPE_TIME_EDITOR, NULL));
707 }
708
709 static void 
710 hildon_time_editor_finalize                     (GObject *obj_self)
711 {
712     HildonTimeEditorPrivate *priv = HILDON_TIME_EDITOR_GET_PRIVATE (obj_self);
713     g_assert (priv);
714
715     if (priv->am_symbol) 
716             g_free (priv->am_symbol);
717
718     if (priv->pm_symbol)
719             g_free (priv->pm_symbol);
720
721     if (priv->highlight_idle)
722         g_source_remove (priv->highlight_idle);
723
724     if (G_OBJECT_CLASS (parent_class)->finalize)
725         G_OBJECT_CLASS (parent_class)->finalize (obj_self);
726 }
727
728 /**
729  * hildon_time_editor_get_time_separators:
730  * @hm_sep_label: the label that will show the hour:minutes separator
731  * @ms_sep_label: the label that will show the minutes:seconds separator
732  *
733  * Gets hour-minute separator and minute-second separator from current
734  * locale and sets then to the labels we set as parameters. Both
735  * parameters can be NULL if you just want to assing one separator.
736  *
737  */
738 void 
739 hildon_time_editor_get_time_separators          (GtkLabel *hm_sep_label,
740                                                  GtkLabel *ms_sep_label)
741 {
742     gchar buffer[256];
743     gchar *separator;
744     GDate locale_test_date;
745     gchar *iter, *endp = NULL;
746
747     /* Get localized time string */
748     g_date_set_dmy (&locale_test_date, 1, 2, 1970);
749     (void) g_date_strftime (buffer, sizeof (buffer), "%X", &locale_test_date);
750
751     if (hm_sep_label != NULL)
752     {
753         /* Find h-m separator */
754         iter = buffer;
755         while (*iter && g_ascii_isdigit (*iter)) iter++;
756
757         /* Extract h-m separator*/
758         endp = iter;
759         while (*endp && ! g_ascii_isdigit (*endp)) endp++;
760         separator = g_strndup (iter, endp - iter);
761         gtk_label_set_label (hm_sep_label, separator);
762         g_free (separator);
763     }
764
765     if (ms_sep_label != NULL)
766     {      
767         /* Find m-s separator */
768         iter = endp;
769         while (*iter && g_ascii_isdigit (*iter)) iter++;
770
771         /* Extract m-s separator*/
772         endp = iter;
773         while (*endp && ! g_ascii_isdigit (*endp)) endp++;
774         separator = g_strndup (iter, endp - iter);
775         gtk_label_set_label (ms_sep_label, separator);
776         g_free (separator);
777     }
778 }
779
780 /* Convert ticks to H:M:S. Ticks = seconds since 00:00:00. */
781 static void 
782 ticks_to_time                                   (guint ticks,
783                                                  guint *hours,
784                                                  guint *minutes,
785                                                  guint *seconds)
786 {
787     guint left;
788
789     *hours = ticks / 3600;
790     left   = ticks % 3600;
791     *minutes = left / 60;
792     *seconds = left % 60;
793 }
794
795 /**
796  * hildon_time_editor_set_ticks:
797  * @editor: the #HildonTimeEditor widget
798  * @ticks: the duration to set, in seconds
799  *
800  * Sets the current duration in seconds. This means seconds from
801  * midnight, if not in duration mode. In case of any errors, it tries
802  * to fix it.
803  */
804
805 void 
806 hildon_time_editor_set_ticks                    (HildonTimeEditor *editor,
807                                                  guint ticks)
808 {
809     HildonTimeEditorPrivate *priv;
810     guint i, h, m, s;
811     gchar str[3];
812
813     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
814
815     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
816     g_assert (priv);
817
818     /* Validate ticks. If it's too low or too high, set it to
819        min/max value for the current mode. */
820     if (priv->duration_mode)
821         priv->ticks = CLAMP (ticks, priv->duration_min, priv->duration_max);
822     else {
823         /* Check that ticks value is valid. We only need to check that hours
824            don't exceed 23. */
825         ticks_to_time (ticks, &h, &m, &s);
826         if (h > HOURS_MAX_24)
827             ticks = TICKS(HOURS_MAX_24, m, s);
828
829         priv->ticks = ticks;
830     }
831
832     /* Get the time in H:M:S. */
833     ticks_to_time (priv->ticks, &h, &m, &s);
834
835     if (!priv->clock_24h && ! priv->duration_mode)
836     {
837         /* Convert 24h H:M:S values to 12h mode, and update AM/PM state */
838         convert_to_12h (&h, &priv->am);
839     }
840
841     /* Set H:M:S values to entries. We  do not want to invoke validation
842        callbacks (since they can cause new call to this function), so we 
843        block signals while setting values. */
844     for (i = 0; i < ENTRY_COUNT; i++)
845     {
846         g_signal_handlers_block_by_func(priv->entries[i],
847                 (gpointer) hildon_time_editor_entry_changed, editor);
848
849         g_signal_handlers_block_by_func(priv->entries[i],
850                 (gpointer) hildon_time_editor_inserted_text, editor);
851
852         g_signal_handlers_block_by_func(priv->entries[i],
853                 (gpointer) hildon_time_editor_entry_focus_out, editor);
854     }
855
856     g_snprintf (str, sizeof (str), "%02u", h);
857     gtk_entry_set_text (GTK_ENTRY (priv->entries[ENTRY_HOURS]), str);
858
859     g_snprintf(str, sizeof (str), "%02u", m);
860     gtk_entry_set_text (GTK_ENTRY (priv->entries[ENTRY_MINS]), str);
861
862     g_snprintf(str, sizeof (str), "%02u", s);
863     gtk_entry_set_text (GTK_ENTRY (priv->entries[ENTRY_SECS]), str);
864
865     for (i = 0; i < ENTRY_COUNT; i++)
866     {
867         g_signal_handlers_unblock_by_func (priv->entries[i],
868                 (gpointer) hildon_time_editor_entry_changed, editor);
869
870         g_signal_handlers_unblock_by_func (priv->entries[i],
871                 (gpointer) hildon_time_editor_inserted_text, editor);
872
873         g_signal_handlers_unblock_by_func (priv->entries[i],
874                 (gpointer) hildon_time_editor_entry_focus_out, editor);
875     }
876
877     /* Update AM/PM label in case we're in 12h mode */
878     gtk_label_set_label( GTK_LABEL (priv->ampm_label),
879             priv->am ? priv->am_symbol : priv->pm_symbol);
880
881     g_object_notify (G_OBJECT (editor), "ticks");
882 }
883
884 static void
885 hildon_time_editor_set_to_current_time          (HildonTimeEditor *editor)
886 {
887     time_t now;
888     const struct tm *tm;
889
890     now = time (NULL);
891     tm = localtime (&now);
892
893     if (tm != NULL)
894         hildon_time_editor_set_time (editor, tm->tm_hour, tm->tm_min, tm->tm_sec);
895 }
896
897 /**
898  * hildon_time_editor_get_ticks:
899  * @editor: the #HildonTimeEditor widget
900  *
901  * This function returns the current duration, in seconds.
902  * This means seconds from midnight, if not in duration mode.
903  * 
904  * Returns: current duration in seconds 
905  */
906 guint 
907 hildon_time_editor_get_ticks                    (HildonTimeEditor *editor)
908 {
909     HildonTimeEditorPrivate *priv;
910
911     g_return_val_if_fail (HILDON_IS_TIME_EDITOR (editor), 0);
912
913     priv = HILDON_TIME_EDITOR_GET_PRIVATE(editor);
914     g_assert (priv);
915
916     return (priv->ticks);
917 }
918
919 /**
920  * hildon_time_editor_set_show_seconds:
921  * @editor: the #HildonTimeEditor
922  * @show_seconds: enable or disable showing of seconds
923  *
924  * This function shows or hides the seconds field.
925  */
926 void 
927 hildon_time_editor_set_show_seconds             (HildonTimeEditor *editor,
928                                                  gboolean show_seconds)
929 {
930     HildonTimeEditorPrivate *priv;
931
932     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
933
934     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
935     g_assert (priv);
936
937     if (show_seconds != priv->show_seconds) {
938         priv->show_seconds = show_seconds;
939
940         /* show/hide seconds field and its ':' label if the value changed. */
941         if (show_seconds) {
942             gtk_widget_show (priv->entries[ENTRY_SECS]);
943             gtk_widget_show (priv->sec_label);        
944         } else {    
945             gtk_widget_hide (priv->entries[ENTRY_SECS]);
946             gtk_widget_hide (priv->sec_label);
947         }
948
949         g_object_notify (G_OBJECT (editor), "show_seconds");
950     }
951 }
952
953 /**
954  * hildon_time_editor_get_show_seconds:
955  * @editor: the #HildonTimeEditor widget
956  *
957  * This function returns a boolean indicating the visibility of
958  * seconds in the #HildonTimeEditor
959  *
960  * Returns: TRUE if the seconds are visible 
961  */
962 gboolean 
963 hildon_time_editor_get_show_seconds             (HildonTimeEditor *editor)
964 {
965     HildonTimeEditorPrivate *priv;
966
967     g_return_val_if_fail (HILDON_IS_TIME_EDITOR (editor), FALSE);
968     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
969     g_assert (priv);
970
971     return (priv->show_seconds);
972 }
973
974 /**
975  * hildon_time_editor_set_duration_mode:
976  * @editor: the #HildonTimeEditor
977  * @duration_mode: enable or disable duration editor mode
978  *
979  * This function sets the duration editor mode in which the maximum hours
980  * is 99.
981  */
982 void 
983 hildon_time_editor_set_duration_mode            (HildonTimeEditor *editor,
984                                                  gboolean duration_mode)
985 {
986     HildonTimeEditorPrivate *priv;
987
988     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
989
990     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
991     g_assert (priv);
992
993     if (duration_mode != priv->duration_mode) {
994         priv->duration_mode = duration_mode;
995
996         if (duration_mode) {
997             /* FIXME: Why do we reset the duration range here?
998                Would change API, so won't touch this for now. */
999             hildon_time_editor_set_duration_range (editor, MIN_DURATION, MAX_DURATION);
1000             /* There's no AM/PM label or time picker icon in duration mode.
1001                Make sure they're hidden. */
1002             gtk_widget_hide (GTK_WIDGET (priv->ampm_label));
1003             gtk_widget_hide (GTK_WIDGET (priv->ampm_button));
1004             gtk_widget_hide (GTK_WIDGET (priv->iconbutton));
1005             /* Duration mode has seconds by default. */
1006             hildon_time_editor_set_show_seconds (editor, TRUE);
1007         } else {
1008             /* Make sure AM/PM label and time picker icons are visible if needed */
1009             if (! priv->clock_24h)
1010                 gtk_widget_show (GTK_WIDGET (priv->ampm_label));
1011
1012             gtk_widget_show (GTK_WIDGET (priv->ampm_button));
1013             gtk_widget_show (GTK_WIDGET (priv->iconbutton));        
1014
1015             /* Reset the ticks to current time. Anything set in duration mode
1016              * is bound to be invalid or useless in time mode.
1017              */
1018             hildon_time_editor_set_to_current_time (editor);
1019         }
1020
1021         g_object_notify (G_OBJECT (editor), "duration_mode");
1022     }
1023 }
1024
1025 /**
1026  * hildon_time_editor_get_duration_mode:
1027  * @editor: the #HildonTimeEditor widget
1028  *
1029  * This function returns a boolean indicating whether the #HildonTimeEditor
1030  * is in the duration mode.
1031  * 
1032  * Returns: TRUE if the #HildonTimeEditor is in duration mode 
1033  */
1034 gboolean 
1035 hildon_time_editor_get_duration_mode            (HildonTimeEditor *editor)
1036 {
1037     HildonTimeEditorPrivate *priv;
1038
1039     g_return_val_if_fail (HILDON_IS_TIME_EDITOR (editor), FALSE);
1040     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1041     g_assert (priv);
1042
1043     return (priv->duration_mode);
1044 }
1045
1046 /**
1047  * hildon_time_editor_set_duration_min:
1048  * @editor: the #HildonTimeEditor widget
1049  * @duration_min: mimimum allowed duration
1050  *
1051  * Sets the minimum allowed duration for the duration mode.
1052  * Note: Has no effect in time mode
1053  */
1054 void 
1055 hildon_time_editor_set_duration_min             (HildonTimeEditor *editor,
1056                                                  guint duration_min)
1057 {
1058     HildonTimeEditorPrivate *priv;
1059
1060     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
1061     g_return_if_fail (duration_min >= MIN_DURATION);
1062
1063     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1064     g_assert (priv);
1065
1066     if (! priv->duration_mode )
1067         return;
1068
1069     priv->duration_min = duration_min;
1070
1071     /* Clamp the current value to the minimum if necessary */
1072     if (priv->ticks < duration_min)
1073     {
1074         hildon_time_editor_set_ticks (editor, duration_min);
1075     }
1076
1077     g_object_notify (G_OBJECT (editor), "duration_min");
1078 }
1079
1080 /**
1081  * hildon_time_editor_get_duration_min:
1082  * @editor: the #HildonTimeEditor widget
1083  *
1084  * This function returns the smallest duration the #HildonTimeEditor
1085  * allows in the duration mode.
1086  * 
1087  * Returns: minimum allowed duration in seconds 
1088  */
1089 guint 
1090 hildon_time_editor_get_duration_min             (HildonTimeEditor *editor)
1091 {
1092     HildonTimeEditorPrivate *priv;
1093
1094     g_return_val_if_fail (HILDON_IS_TIME_EDITOR (editor), 0);
1095
1096     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1097     g_assert (priv);
1098
1099     if(! priv->duration_mode )
1100         return (0);
1101
1102     return (priv->duration_min);
1103 }
1104
1105 /**
1106  * hildon_time_editor_set_duration_max:
1107  * @editor: the #HildonTimeEditor widget
1108  * @duration_max: maximum allowed duration in seconds
1109  *
1110  * Sets the maximum allowed duration in seconds for the duration mode.
1111  * Note: Has no effect in time mode
1112  */
1113 void 
1114 hildon_time_editor_set_duration_max             (HildonTimeEditor *editor,
1115                                                  guint duration_max)
1116 {
1117     HildonTimeEditorPrivate *priv;
1118
1119     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
1120     g_return_if_fail (duration_max <= MAX_DURATION);
1121
1122     priv = HILDON_TIME_EDITOR_GET_PRIVATE(editor);
1123     g_assert (priv);
1124
1125     if (! priv->duration_mode)
1126         return;
1127
1128     priv->duration_max = duration_max;
1129
1130     /* Clamp the current value to the maximum if necessary */
1131     if (priv->ticks > duration_max)
1132     {
1133         hildon_time_editor_set_ticks (editor, duration_max);
1134     }
1135
1136     g_object_notify (G_OBJECT (editor), "duration_max");
1137 }
1138
1139 /**
1140  * hildon_time_editor_get_duration_max:
1141  * @editor: the #HildonTimeEditor widget
1142  *
1143  * This function returns the longest duration the #HildonTimeEditor
1144  * allows in the duration mode.
1145  * 
1146  * Returns: maximum allowed duration in seconds 
1147  */
1148 guint 
1149 hildon_time_editor_get_duration_max             (HildonTimeEditor * editor)
1150 {
1151     HildonTimeEditorPrivate *priv;
1152
1153     g_return_val_if_fail (HILDON_IS_TIME_EDITOR (editor), 0);
1154
1155     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1156     g_assert (priv);
1157
1158     if (! priv->duration_mode)
1159         return (0);
1160
1161     return (priv->duration_max);
1162 }
1163
1164 /**
1165  * hildon_time_editor_set_time:
1166  * @editor: the #HildonTimeEditor widget
1167  * @hours: hours
1168  * @minutes: minutes
1169  * @seconds: seconds
1170  *
1171  * This function sets the time on an existing time editor. If the
1172  * time specified by the arguments is invalid, it's fixed.
1173  * The time is assumed to be in 24h format.
1174  */
1175 void 
1176 hildon_time_editor_set_time                     (HildonTimeEditor *editor, 
1177                                                  guint hours,
1178                                                  guint minutes, 
1179                                                  guint seconds)
1180 {
1181     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
1182
1183     hildon_time_editor_set_ticks (editor, TICKS(hours, minutes, seconds));
1184 }
1185
1186 /**
1187  * hildon_time_editor_get_time:
1188  * @editor: the #HildonTimeEditor widget
1189  * @hours: hours
1190  * @minutes: minutes
1191  * @seconds: seconds
1192  *
1193  * Gets the time of the #HildonTimeEditor widget. The time returned is
1194  * always in 24h format.
1195  */
1196 void 
1197 hildon_time_editor_get_time                     (HildonTimeEditor *editor,
1198                                                  guint *hours,
1199                                                  guint *minutes, 
1200                                                  guint *seconds)
1201 {
1202     HildonTimeEditorPrivate *priv;
1203
1204     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
1205
1206     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1207     g_assert (priv);
1208
1209     ticks_to_time (hildon_time_editor_get_ticks (editor), hours, minutes, seconds);
1210 }
1211
1212 /**
1213  * hildon_time_editor_set_duration_range:
1214  * @editor: the #HildonTimeEditor widget
1215  * @min_seconds: minimum allowed time in seconds
1216  * @max_seconds: maximum allowed time in seconds
1217  *
1218  * Sets the duration editor time range of the #HildonTimeEditor widget.
1219  */
1220 void 
1221 hildon_time_editor_set_duration_range           (HildonTimeEditor *editor,
1222                                                  guint min_seconds,
1223                                                  guint max_seconds)
1224 {
1225     HildonTimeEditorPrivate *priv;
1226     guint tmp;
1227
1228     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
1229
1230     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1231     g_assert (priv);
1232
1233     /* Swap values if reversed */
1234     if (min_seconds > max_seconds)
1235     {
1236         tmp = max_seconds;
1237         max_seconds = min_seconds;
1238         min_seconds = tmp;
1239     }
1240
1241     hildon_time_editor_set_duration_max (editor, max_seconds);
1242     hildon_time_editor_set_duration_min (editor, min_seconds);
1243
1244     if (priv->duration_mode) {
1245         /* Set minimum allowed value for duration editor.
1246            FIXME: Shouldn't it be changed only if it's not in range?
1247            Would change API, so won't touch this for now. */
1248         hildon_time_editor_set_ticks (editor, min_seconds);
1249     }
1250 }
1251
1252 /**
1253  * hildon_time_editor_get_duration_range:
1254  * @editor: the #HildonTimeEditor widget
1255  * @min_seconds: pointer to guint
1256  * @max_seconds: pointer to guint
1257  *
1258  * Gets the duration editor time range of the #HildonTimeEditor widget.
1259  */
1260 void 
1261 hildon_time_editor_get_duration_range           (HildonTimeEditor *editor,
1262                                                  guint *min_seconds,
1263                                                  guint *max_seconds)
1264 {
1265     HildonTimeEditorPrivate *priv;
1266
1267     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
1268
1269     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1270     g_assert (priv);
1271
1272     *min_seconds = priv->duration_min;
1273     *max_seconds = priv->duration_max;
1274 }
1275
1276 static gboolean 
1277 hildon_time_editor_check_locale                 (HildonTimeEditor *editor)
1278 {
1279     HildonTimeEditorPrivate *priv;
1280
1281     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1282     g_assert (priv);
1283
1284     /* Update time separator symbols */
1285     hildon_time_editor_get_time_separators (GTK_LABEL (priv->hm_label), GTK_LABEL (priv->sec_label));
1286
1287     /* Get AM/PM symbols. */
1288     priv->am_symbol = g_strdup (nl_langinfo (AM_STR));
1289     priv->pm_symbol = g_strdup (nl_langinfo (PM_STR));
1290
1291     if (priv->am_symbol[0] == '\0')
1292         return TRUE;
1293     else {
1294         /* 12h clock mode. Check if AM/PM should be before or after time.
1295            %p is the AM/PM string, so we assume that if the format string
1296            begins with %p it's in the beginning, and in any other case it's
1297            in the end (although that's not necessarily the case). */
1298         if (strncmp (nl_langinfo (T_FMT_AMPM), "%p", 2) == 0)
1299             priv->ampm_pos_after = FALSE;
1300         return FALSE;
1301     }
1302 }
1303
1304 static gboolean
1305 hildon_time_editor_entry_focus_in               (GtkWidget *widget,
1306                                                  GdkEventFocus *event, 
1307                                                  gpointer data)
1308 {
1309     g_idle_add ((GSourceFunc) hildon_time_editor_entry_select_all,
1310             GTK_ENTRY (widget));
1311
1312     return FALSE;
1313 }
1314
1315 static gboolean 
1316 hildon_time_editor_time_error                   (HildonTimeEditor *editor,
1317                                                  HildonDateTimeError type)
1318 {
1319     return TRUE;
1320 }
1321
1322 /* Returns negative if we didn't get value,
1323  * and should stop further validation 
1324  */
1325 static gint 
1326 validated_conversion                            (HildonTimeEditorPrivate *priv,
1327                                                  GtkWidget *field,
1328                                                  gint min,
1329                                                  gint max,
1330                                                  gint def_value,
1331                                                  gboolean allow_intermediate,
1332                                                  guint *error_code,
1333                                                  GString *error_string)
1334 {
1335     const gchar *text;
1336     gchar *tail;
1337     long value;
1338
1339     text = gtk_entry_get_text (GTK_ENTRY (field));
1340
1341     if (text && text[0])
1342     {
1343         /* Try to convert entry text to number */
1344         value = strtol (text, &tail, 10);
1345
1346         /* Check if conversion succeeded */
1347         if ((tail[0] == 0) && !(text[0] == '-'))
1348         {    
1349             if (value > max) {
1350                 g_string_printf (error_string, _("ckct_ib_maximum_value"), max);
1351                 priv->error_widget = field;
1352                 *error_code = MAX_VALUE;
1353                 return max;
1354             }
1355
1356             if (value < min && !allow_intermediate) {
1357                 g_string_printf (error_string, _("ckct_ib_minimum_value"), min);
1358                 priv->error_widget = field;
1359                 *error_code = MIN_VALUE;
1360                 return min;
1361             }
1362
1363             return value;
1364         }
1365
1366         /* We'll handle failed conversions soon */
1367         else
1368         {
1369             if ((tail[0] == '-') || (text[0] == '-'))
1370             {
1371                 g_string_printf (error_string, _("ckct_ib_minimum_value"), min);
1372                 priv->error_widget = field;
1373                 *error_code = MIN_VALUE;
1374                 return min;
1375             }
1376         }
1377     }
1378     else if (allow_intermediate) 
1379         return -1;  /* Empty field while user is still editing. No error, but
1380                        cannot validate either... */
1381     else /* Empty field: show error and set value to minimum allowed */
1382     {
1383         g_string_printf (error_string, _("ckct_ib_set_a_value_within_range"), min, max);
1384         priv->error_widget = field;
1385         *error_code = WITHIN_RANGE;
1386         return def_value;
1387     }
1388
1389     /* Empty field and not allowed intermediated OR failed conversion */
1390     g_string_printf (error_string, _("ckct_ib_set_a_value_within_range"), min, max);
1391     priv->error_widget = field;
1392     *error_code = WITHIN_RANGE;
1393     return -1;
1394 }
1395
1396 static void
1397 hildon_time_editor_real_validate                (HildonTimeEditor *editor, 
1398                                                  gboolean allow_intermediate, 
1399                                                  GString *error_string)
1400 {
1401     HildonTimeEditorPrivate *priv;
1402     guint h, m, s, ticks;
1403     guint error_code;
1404     guint max_hours, min_hours, def_hours;
1405     guint max_minutes, min_minutes, def_minutes;
1406     guint max_seconds, min_seconds, def_seconds;
1407     gboolean r;
1408
1409     g_assert (HILDON_IS_TIME_EDITOR (editor));
1410
1411     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1412     g_assert (priv);
1413
1414     /* Find limits for field based validation. */
1415     if (priv->duration_mode)
1416     {
1417         ticks_to_time (priv->duration_min, &min_hours, &min_minutes, &min_seconds);
1418         ticks_to_time (priv->duration_max, &max_hours, &max_minutes, &max_seconds);
1419     } else {
1420         if (priv->clock_24h) {
1421             max_hours = HOURS_MAX_24;
1422             min_hours = HOURS_MIN_24;
1423         } else {
1424             max_hours = HOURS_MAX_12;
1425             min_hours = HOURS_MIN_12;
1426         }
1427     }
1428
1429     hildon_time_editor_get_time (editor, &def_hours, &def_minutes, &def_seconds);
1430
1431     /* Get time components from fields and validate them... */
1432     if (priv->show_hours) {
1433         h = validated_conversion (priv, priv->entries[ENTRY_HOURS], min_hours, max_hours, def_hours,
1434                 allow_intermediate, &error_code, error_string);
1435         if (priv->error_widget == priv->entries[ENTRY_HOURS])
1436             g_signal_emit (editor, time_editor_signals [TIME_ERROR], 0, hour_errors[error_code], &r);
1437         if ((gint) h < 0) return;
1438     }
1439     else h = 0;
1440     m = validated_conversion (priv, priv->entries[ENTRY_MINS], MINUTES_MIN, MINUTES_MAX, def_minutes,
1441             allow_intermediate, &error_code, error_string);
1442     if (priv->error_widget == priv->entries[ENTRY_MINS])
1443         g_signal_emit (editor, time_editor_signals [TIME_ERROR], 0, min_errors[error_code], &r);
1444     if ((gint) m < 0) return;
1445     if (priv->show_seconds) {
1446         s = validated_conversion (priv, priv->entries[ENTRY_SECS], SECONDS_MIN, SECONDS_MAX, def_seconds,
1447                 allow_intermediate, &error_code, error_string);
1448         if (priv->error_widget == priv->entries[ENTRY_SECS])
1449             g_signal_emit (editor, time_editor_signals [TIME_ERROR], 0, sec_errors[error_code], &r);
1450         if ((gint) s < 0) return;
1451     } 
1452     else s = 0;
1453
1454     /* Ok, we now do separate check that tick count is valid for duration mode */      
1455     if (priv->duration_mode)
1456     {          
1457         ticks = TICKS(h, m, s);
1458
1459         if (ticks < priv->duration_min && !allow_intermediate)
1460         {
1461             g_string_printf (error_string,
1462                     _("ckct_ib_min_allowed_duration_hts"), 
1463                     min_hours, min_minutes, min_seconds);
1464             hildon_time_editor_set_ticks (editor, priv->duration_min);
1465             priv->error_widget = priv->show_hours ? priv->entries[ENTRY_HOURS] : priv->entries[ENTRY_MINS];
1466             g_signal_emit (editor, time_editor_signals[TIME_ERROR], 0, HILDON_DATE_TIME_ERROR_MIN_DURATION, &r);
1467             return;
1468         }
1469         else if (ticks > priv->duration_max)
1470         {
1471             g_string_printf (error_string,
1472                     _("ckct_ib_max_allowed_duration_hts"), 
1473                     max_hours, max_minutes, max_seconds);
1474             hildon_time_editor_set_ticks (editor, priv->duration_max);
1475             priv->error_widget = priv->show_hours ? priv->entries[ENTRY_HOURS] : priv->entries[ENTRY_MINS];
1476             g_signal_emit (editor, time_editor_signals[TIME_ERROR], 0, HILDON_DATE_TIME_ERROR_MAX_DURATION, &r);
1477             return;
1478         }
1479     }
1480     else if (! priv->clock_24h)
1481         convert_to_24h (&h, priv->am);
1482
1483     /* The only case when we do not want to refresh the
1484        time display, is when the user is editing a value 
1485        (unless the value was out of bounds and we have to fix it) */
1486     if (! allow_intermediate || priv->error_widget)
1487         hildon_time_editor_set_time (editor, h, m, s);
1488 }
1489
1490 /* Setting text to entries causes entry to recompute itself
1491    in idle callback, which remove selection. Because of this
1492    we need to do selection in idle as well. */
1493 static gboolean 
1494 highlight_callback                              (gpointer data)
1495 {
1496     HildonTimeEditorPrivate *priv;
1497     GtkWidget *widget;
1498     gint i;
1499
1500     g_assert (HILDON_IS_TIME_EDITOR (data));
1501     priv = HILDON_TIME_EDITOR_GET_PRIVATE (data);
1502     g_assert (priv);
1503
1504     GDK_THREADS_ENTER ();
1505
1506     widget = priv->error_widget;
1507     priv->error_widget = NULL;
1508
1509     if (GTK_IS_WIDGET (widget) == FALSE)
1510         goto Done;
1511
1512     /* Avoid revalidation because it will issue the date_error signal
1513        twice when there is an empty field. We must block the signal
1514        for all the entries because we do not know where the focus
1515        comes from */
1516     for (i = 0; i < ENTRY_COUNT; i++)
1517         g_signal_handlers_block_by_func (priv->entries[i],
1518                 (gpointer) hildon_time_editor_entry_focus_out, data);
1519     gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
1520     gtk_widget_grab_focus (widget);
1521     for (i = 0; i < ENTRY_COUNT; i++)
1522         g_signal_handlers_unblock_by_func (priv->entries[i],
1523                 (gpointer) hildon_time_editor_entry_focus_out, data);
1524
1525 Done:
1526     priv->highlight_idle = 0;
1527     GDK_THREADS_LEAVE ();
1528
1529     return FALSE;
1530 }
1531
1532 /* Update ticks from current H:M:S entries. If they're invalid, show an
1533    infoprint and update the fields unless they're empty. */
1534 static void
1535 hildon_time_editor_validate                     (HildonTimeEditor *editor, 
1536                                                  gboolean allow_intermediate)
1537 {
1538     HildonTimeEditorPrivate *priv;
1539     GString *error_message;
1540
1541     g_assert (HILDON_IS_TIME_EDITOR(editor));
1542     priv = HILDON_TIME_EDITOR_GET_PRIVATE(editor);
1543     g_assert (priv);
1544
1545     /* if there is already an error we do nothing until it will be managed by the idle */
1546     if (priv->highlight_idle == 0 && priv->skipper == FALSE)
1547     {
1548         priv->skipper = TRUE;
1549         error_message = g_string_new (NULL);
1550         hildon_time_editor_real_validate (editor, 
1551                 allow_intermediate, error_message);
1552
1553         if (priv->error_widget) 
1554         {
1555             hildon_banner_show_information (priv->error_widget, NULL,
1556                     error_message->str);
1557
1558             priv->highlight_idle = g_idle_add (highlight_callback, editor);
1559         }
1560
1561         priv->skipper = FALSE;
1562         g_string_free (error_message, TRUE);
1563     }
1564 }
1565
1566 /* on inserted text, if entry has two digits, jumps to the next field. */
1567 static void
1568 hildon_time_editor_inserted_text                (GtkEditable *editable,
1569                                                  gchar *new_text,
1570                                                  gint new_text_length,
1571                                                  gint *position,
1572                                                  gpointer user_data) 
1573 {
1574     HildonTimeEditor *editor;
1575     GtkEntry *entry;
1576     gchar *value;
1577     HildonTimeEditorPrivate *priv;
1578
1579     entry = GTK_ENTRY (editable);
1580     editor = HILDON_TIME_EDITOR (user_data);
1581
1582     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1583     g_assert (priv);
1584
1585     /* if there is already an error we don't have to do anything */ 
1586     if (! priv->error_widget)
1587     {
1588         value = (gchar *) gtk_entry_get_text (entry);
1589
1590         if (strlen (value) == 2)
1591         {
1592             if (GTK_WIDGET (editable) == priv->entries[ENTRY_HOURS]) 
1593             {
1594                 /* We already checked the input in changed signal, but 
1595                  * now we will re-check it again in focus-out we 
1596                  * intermediate flag set to FALSE */
1597                 gtk_widget_grab_focus (priv->entries[ENTRY_MINS]);
1598                 *position = -1;
1599             }
1600             else if (GTK_WIDGET (editable) == priv->entries[ENTRY_MINS] &&
1601                     GTK_WIDGET_VISIBLE (priv->entries[ENTRY_SECS])) 
1602             {
1603                 /* See above */
1604                 gtk_widget_grab_focus (priv->entries[ENTRY_SECS]);
1605                 *position = -1;
1606             }
1607         }
1608     }   
1609 }
1610
1611 static gboolean 
1612 hildon_time_editor_entry_focus_out              (GtkWidget *widget,
1613                                                  GdkEventFocus *event,
1614                                                  gpointer data)
1615 {
1616     g_assert (HILDON_IS_TIME_EDITOR (data));
1617
1618     /* Validate the given time and update ticks. */
1619     hildon_time_editor_validate (HILDON_TIME_EDITOR (data), FALSE);
1620
1621     return FALSE;
1622 }
1623
1624 static gboolean
1625 hildon_time_editor_ampm_clicked                 (GtkWidget *widget,
1626                                                  gpointer data)
1627 {
1628     HildonTimeEditor *editor;
1629     HildonTimeEditorPrivate *priv;
1630
1631     g_assert (GTK_IS_WIDGET (widget));
1632     g_assert (HILDON_IS_TIME_EDITOR (data));
1633
1634     editor = HILDON_TIME_EDITOR (data);
1635     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1636     g_assert (priv);
1637
1638     /* First validate the given time and update ticks. */
1639     hildon_time_editor_validate (editor, FALSE);
1640
1641     /* Apply the AM/PM change by moving the current time by 12 hours */
1642     if (priv->am) {
1643         /* 00:00 .. 11:59 -> 12:00 .. 23:59 */
1644         hildon_time_editor_set_ticks (editor, priv->ticks + 12 * 3600);
1645     } else {
1646         /* 12:00 .. 23:59 -> 00:00 .. 11:59 */
1647         hildon_time_editor_set_ticks (editor, priv->ticks - 12 * 3600);
1648     }
1649
1650     return FALSE;
1651 }
1652
1653 static gboolean
1654 hildon_time_editor_icon_clicked                 (GtkWidget *widget, 
1655                                                  gpointer data)
1656 {
1657     HildonTimeEditor *editor;
1658     GtkWidget *picker;
1659     GtkWidget *parent;
1660     guint h, m, s, result;
1661     HildonTimeEditorPrivate *priv;
1662
1663     g_assert (HILDON_IS_TIME_EDITOR (data));
1664
1665     editor = HILDON_TIME_EDITOR (data);
1666     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1667     g_assert (priv);
1668
1669     /* icon is passive in duration editor mode */
1670     if (hildon_time_editor_get_duration_mode (editor))
1671         return FALSE;
1672
1673     /* Validate and do not launch if broken */
1674     hildon_time_editor_validate (HILDON_TIME_EDITOR (data), FALSE);
1675     if (priv->error_widget != NULL)
1676         return FALSE;
1677
1678     /* Launch HildonTimePicker dialog */
1679     parent = gtk_widget_get_ancestor (GTK_WIDGET (editor), GTK_TYPE_WINDOW);
1680     picker = hildon_time_picker_new (GTK_WINDOW (parent));
1681
1682     hildon_time_editor_get_time (editor, &h, &m, &s);
1683     hildon_time_picker_set_time (HILDON_TIME_PICKER (picker), h, m);
1684
1685     result = gtk_dialog_run (GTK_DIALOG (picker));
1686     switch (result) {
1687
1688         case GTK_RESPONSE_OK:
1689         case GTK_RESPONSE_ACCEPT:
1690             /* Use the selected time */
1691             hildon_time_picker_get_time (HILDON_TIME_PICKER (picker), &h, &m);
1692             hildon_time_editor_set_time (editor, h, m, 0);
1693             break;
1694
1695         default:
1696             break;
1697     }
1698
1699     gtk_widget_destroy (picker);
1700     return FALSE;
1701 }
1702
1703 static void 
1704 hildon_time_editor_size_request                 (GtkWidget *widget,
1705                                                  GtkRequisition *requisition)
1706 {
1707     HildonTimeEditor *editor;
1708     HildonTimeEditorPrivate *priv;
1709     GtkRequisition req;
1710
1711     editor = HILDON_TIME_EDITOR (widget);
1712     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1713
1714     /* Get frame's size */
1715     gtk_widget_size_request (priv->frame, requisition);
1716
1717     if (GTK_WIDGET_VISIBLE (priv->iconbutton))
1718     {
1719         gtk_widget_size_request (priv->iconbutton, &req);
1720         /* Reserve space for icon */
1721         requisition->width += req.width + ICON_PRESSED +
1722             HILDON_MARGIN_DEFAULT;
1723     }
1724
1725     /* FIXME: It's evil to use hardcoded TIME_EDITOR_HEIGHT. For now we'll
1726        want to force this since themes might have varying thickness values
1727        which cause the height to change. */
1728     requisition->height = TIME_EDITOR_HEIGHT;
1729 }
1730
1731 static void 
1732 hildon_time_editor_size_allocate                (GtkWidget *widget,
1733                                                  GtkAllocation *allocation)
1734 {
1735     HildonTimeEditorPrivate *priv = HILDON_TIME_EDITOR_GET_PRIVATE (widget);
1736     GtkAllocation alloc;
1737     GtkRequisition req, max_req;
1738     gboolean rtl;
1739
1740     g_assert (priv);
1741
1742     rtl = (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL);
1743     widget->allocation = *allocation;
1744     gtk_widget_get_child_requisition (widget, &max_req);
1745
1746     /* Center horizontally */
1747     alloc.x = allocation->x + MAX (allocation->width - max_req.width, 0) / 2;
1748     /* Center vertically */
1749     alloc.y = allocation->y + MAX (allocation->height - max_req.height, 0) / 2;
1750
1751     /* allocate frame */
1752     if (rtl)
1753         gtk_widget_get_child_requisition (priv->iconbutton, &req);
1754     else
1755         gtk_widget_get_child_requisition (priv->frame, &req);
1756
1757     alloc.width = req.width;
1758     alloc.height = max_req.height;
1759     if (rtl)
1760         gtk_widget_size_allocate (priv->iconbutton, &alloc);
1761     else
1762         gtk_widget_size_allocate (priv->frame, &alloc);
1763
1764     /* allocate icon */
1765     if (GTK_WIDGET_VISIBLE (priv->iconbutton)) {
1766         if (rtl)
1767             gtk_widget_get_child_requisition (priv->frame, &req);
1768         else
1769             gtk_widget_get_child_requisition (priv->iconbutton, &req);
1770
1771         alloc.x += alloc.width + HILDON_MARGIN_DEFAULT;
1772         alloc.width = req.width;
1773
1774         if (rtl)
1775             gtk_widget_size_allocate (priv->frame, &alloc);
1776         else
1777             gtk_widget_size_allocate (priv->iconbutton, &alloc);        
1778     }
1779
1780     /* FIXME: ugly way to move labels up. They just don't seem move up
1781        otherwise. This is likely because we force the editor to be
1782        smaller than it otherwise would be. */
1783     alloc = priv->ampm_label->allocation;
1784     alloc.y = allocation->y - 2;
1785     alloc.height = max_req.height + 2;
1786     gtk_widget_size_allocate (priv->ampm_label, &alloc);
1787
1788     alloc = priv->hm_label->allocation;
1789     alloc.y = allocation->y - 2;
1790     alloc.height = max_req.height + 2;
1791     gtk_widget_size_allocate (priv->hm_label, &alloc);
1792
1793     alloc = priv->sec_label->allocation;
1794     alloc.y = allocation->y - 2;
1795     alloc.height = max_req.height + 2;
1796     gtk_widget_size_allocate (priv->sec_label, &alloc);
1797 }
1798
1799 static gboolean
1800 hildon_time_editor_focus                      (GtkWidget *widget,
1801                                                GtkDirectionType direction)
1802 {
1803   gboolean retval;
1804   GtkDirectionType effective_direction;
1805
1806   g_assert (HILDON_IS_TIME_EDITOR (widget));
1807
1808   retval = hildon_private_composite_focus (widget, direction, &effective_direction);
1809
1810   if (retval == TRUE)
1811     return GTK_WIDGET_CLASS (parent_class)->focus (widget, effective_direction);
1812   else
1813     return FALSE;
1814 }
1815
1816 static gboolean
1817 hildon_time_editor_entry_keypress (GtkEntry *entry,
1818                                    GdkEventKey *event,
1819                                    gpointer data)
1820 {
1821   switch (event->keyval)
1822     {
1823     case GDK_Return:
1824     case GDK_ISO_Enter:
1825       hildon_time_editor_icon_clicked (GTK_WIDGET (entry), data);
1826       return TRUE;
1827     default:
1828       return FALSE;
1829     }
1830
1831   g_assert_not_reached ();
1832 }
1833
1834 static void
1835 convert_to_12h                                  (guint *h, 
1836                                                  gboolean *am)
1837 {
1838     g_assert (0 <= *h && *h < 24);
1839
1840     /* 00:00 to 00:59  add 12 hours      */
1841     /* 01:00 to 11:59  straight to am    */
1842     /* 12:00 to 12:59  straight to pm    */
1843     /* 13:00 to 23:59  subtract 12 hours */
1844
1845     if      (       *h == 0       ) { *am = TRUE;  *h += 12;}
1846     else if (  1 <= *h && *h < 12 ) { *am = TRUE;           }
1847     else if ( 12 <= *h && *h < 13 ) { *am = FALSE;          }
1848     else                            { *am = FALSE; *h -= 12;}
1849 }
1850
1851 static void
1852 convert_to_24h                                  (guint *h, 
1853                                                  gboolean am)
1854 {
1855     if (*h == 12 && am) /* 12 midnight - 12:59 AM  subtract 12 hours  */
1856     {
1857         *h -= 12;
1858     }
1859
1860     else if (! am && 1 <= *h && *h < 12)    /* 1:00 PM - 11:59 AM   add 12 hours */
1861     {
1862         *h += 12;
1863     }
1864 }
1865
1866 /**
1867  * hildon_time_editor_set_show_hours:
1868  * @editor: The #HildonTimeEditor.
1869  * @show_hours: Enable or disable showing of hours.
1870  *
1871  * This function shows or hides the hours field.
1872  *
1873  **/
1874 void 
1875 hildon_time_editor_set_show_hours               (HildonTimeEditor *editor,
1876                                                  gboolean show_hours)
1877 {
1878     HildonTimeEditorPrivate *priv;
1879
1880     g_return_if_fail (HILDON_IS_TIME_EDITOR (editor));
1881
1882     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1883     g_assert (priv);
1884
1885     if (show_hours != priv->show_hours) {
1886         priv->show_hours = show_hours;
1887
1888         /* show/hide hours field and its ':' label if the value changed. */
1889         if (show_hours) {
1890             gtk_widget_show (priv->entries[ENTRY_HOURS]);
1891             gtk_widget_show (priv->hm_label);        
1892         } else {    
1893             gtk_widget_hide (priv->entries[ENTRY_HOURS]);
1894             gtk_widget_hide (priv->hm_label);
1895         }
1896
1897         g_object_notify (G_OBJECT (editor), "show_hours");
1898     }
1899 }
1900
1901 /**
1902  * hildon_time_editor_get_show_hours:
1903  * @editor: the @HildonTimeEditor widget.
1904  *
1905  * This function returns a boolean indicating the visibility of
1906  * hours in the @HildonTimeEditor
1907  *
1908  * Return value: TRUE if hours are visible. 
1909  *
1910  **/
1911 gboolean 
1912 hildon_time_editor_get_show_hours               (HildonTimeEditor *editor)
1913 {
1914     HildonTimeEditorPrivate *priv;
1915
1916     g_return_val_if_fail (HILDON_IS_TIME_EDITOR (editor), FALSE);
1917     priv = HILDON_TIME_EDITOR_GET_PRIVATE (editor);
1918     g_assert (priv);
1919
1920     return priv->show_hours;
1921 }
1922
1923 /* Idle callback */
1924 static gboolean
1925 hildon_time_editor_entry_select_all             (GtkWidget *widget)
1926 {
1927     GDK_THREADS_ENTER ();
1928     gtk_editable_select_region (GTK_EDITABLE (widget), 0, -1);
1929     GDK_THREADS_LEAVE ();
1930
1931     return FALSE;
1932 }