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