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