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