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