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