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