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