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