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