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