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