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