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