Adding an example test program to check icon-lookup functionality. Doc updates.
[hildon] / src / hildon-time-picker.c
1 /*
2  * This file is a part of hildon
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-picker
27  * @short_description: A dialog popup widget which lets the user set the time.
28  * @see_also: #HildonTimeEditor
29  * 
30  * #HildonTimePicker is a dialog popup widget which lets the user set the time,
31  * using up/down arrows on hours and minutes. There are two arrows for minutes,
32  * so that minutes can be added also in 10 min increments.This widget is mainly 
33  * used as a part of #HildonTimeEditor implementation.
34  * 
35  * <example>
36  * <title>HildonTimePicker example</title>
37  * <programlisting>
38  * <!-- -->
39  * parent = gtk_widget_get_ancestor (GTK_WIDGET (editor), GTK_TYPE_WINDOW);
40  * picker = hildon_time_picker_new (GTK_WINDOW (parent));
41  * <!-- -->
42  * hildon_time_editor_get_time (editor, &amp;h, &amp;m, &amp;s);
43  * hildon_time_picker_set_time( HILDON_TIME_PICKER( picker ), h, m );
44  * <!-- -->
45  * result = gtk_dialog_run (GTK_DIALOG (picker));
46  * switch (result)
47  * {
48  * case GTK_RESPONSE_OK:
49  * case GTK_RESPONSE_ACCEPT:
50  *      hildon_time_picker_get_time(HILDON_TIME_PICKER (picker), &amp;h, &amp;m );
51  *      foo_set_time(h,m);
52  *      break;
53  * default:
54  *      break;
55  * }
56  * <!-- -->
57  * gtk_widget_destroy( picker );
58  * <!-- -->
59  * </programlisting>
60  * </example>
61  */
62
63 #ifdef                                          HAVE_CONFIG_H
64 #include                                        <config.h>
65 #endif
66
67 #include                                        "hildon-time-picker.h"
68 #include                                        "hildon-defines.h"
69 #include                                        <gtk/gtk.h>
70 #include                                        <gdk/gdkkeysyms.h>
71 #include                                        <gdk/gdk.h>
72 #include                                        <time.h>
73 #include                                        <stdio.h>
74 #include                                        <stdlib.h>
75 #include                                        <string.h>
76 #include                                        <langinfo.h>
77 #include                                        <libintl.h>
78 #include                                        "hildon-time-picker-private.h"
79 #include                                        "hildon-time-editor.h"
80
81 #define                                         _(String) \
82                                                 dgettext("hildon-libs", String)
83
84 #define                                         DEFAULT_HOURS 1
85
86 #define                                         DEFAULT_MINUTES 1
87
88 #define                                         DEFAULT_ARROW_WIDTH 26
89
90 #define                                         DEFAULT_ARROW_HEIGHT 26
91
92 #define                                         MINS_IN_1H  (60)
93
94 #define                                         MINS_IN_24H (MINS_IN_1H * 24)
95
96 #define                                         MINS_IN_12H (MINS_IN_1H * 12)
97
98 #define                                         HILDON_TIME_PICKER_LABEL_X_PADDING 0
99
100 #define                                         HILDON_TIME_PICKER_LABEL_Y_PADDING 1
101
102 static void
103 hildon_time_picker_class_init                   (HildonTimePickerClass *klass);
104
105 static void
106 hildon_time_picker_init                         (HildonTimePicker *picker);
107
108 static gboolean
109 hildon_time_picker_key_repeat_timeout           (gpointer tpicker);
110
111 static void
112 hildon_time_picker_change_time                  (HildonTimePicker *picker, 
113                                                  guint minutes);
114
115 static gboolean
116 hildon_time_picker_ampm_release                 (GtkWidget *widget, 
117                                                  GdkEvent *event,
118                                                  HildonTimePicker *picker);
119
120 static gboolean
121 hildon_time_picker_arrow_press                  (GtkWidget *widget, 
122                                                  GdkEvent *event,
123                                                  HildonTimePicker *picker);
124
125 static gboolean
126 hildon_time_picker_arrow_release                (GtkWidget *widget, 
127                                                  GdkEvent *event,
128                                                  HildonTimePicker *picker);
129
130 static void
131 hildon_time_picker_finalize                     (GObject *object);
132
133 static void
134 hildon_time_picker_get_property                 (GObject *object, 
135                                                  guint param_id,
136                                                  GValue *value, 
137                                                  GParamSpec *pspec);
138
139 static void
140 hildon_time_picker_set_property                 (GObject *object, 
141                                                  guint param_id,
142                                                  const GValue *value, 
143                                                  GParamSpec *pspec);
144
145 static gboolean
146 hildon_time_picker_event_box_focus_in           (GtkWidget *widget, 
147                                                  GdkEvent *event,
148                                                  gpointer unused);
149
150 static gboolean
151 hildon_time_picker_event_box_focus_out          (GtkWidget *widget, 
152                                                  GdkEvent *event,
153                                                  gpointer unused);
154
155 static gboolean
156 hildon_time_picker_event_box_key_press          (GtkWidget *widget,  
157                                                  GdkEventKey *event,
158                                                  HildonTimePicker *picker);
159
160 static gboolean
161 hildon_time_picker_event_box_key_release        (GtkWidget *widget, 
162                                                  GdkEventKey *event,
163                                                  HildonTimePicker *picker);
164
165 static gboolean
166 hildon_time_picker_event_box_button_press       (GtkWidget *widget, 
167                                                  GdkEventKey *event,
168                                                  gpointer unused);
169
170 static void
171 hildon_time_picker_map                          (GtkWidget *widget);
172
173 static void
174 frame_size_request                              (GtkWidget *widget, 
175                                                  GtkRequisition *requistion);
176
177 static GtkDialogClass*                          parent_class;
178
179 enum
180 {
181     PROP_0,
182     PROP_MINUTES
183 };
184
185 static const gint                               button_multipliers[WIDGET_GROUP_COUNT][2] =
186 {
187     { MINS_IN_1H, -MINS_IN_1H },
188     { 10, -10 },
189     { 1, -1 },
190     { 0, 0 }
191 };
192
193 /**
194  * hildon_time_picker_get_type:
195  *
196  * Returns the type of HildonTimePicker.
197  *
198  * Returns: HildonTimePicker type
199  */
200 GType G_GNUC_CONST
201 hildon_time_picker_get_type                     (void)
202 {
203     static GType picker_type = 0;
204
205     if( !picker_type )
206     {
207         static const GTypeInfo picker_info =
208         {
209             sizeof (HildonTimePickerClass),
210             NULL,       /* base_init */
211             NULL,       /* base_finalize */
212             (GClassInitFunc)hildon_time_picker_class_init,
213             NULL,       /* class_finalize */
214             NULL,       /* class_data */
215             sizeof (HildonTimePicker),
216             0,          /* n_preallocs */
217             (GInstanceInitFunc)hildon_time_picker_init,
218         };
219         picker_type = g_type_register_static( GTK_TYPE_DIALOG, "HildonTimePicker",
220                 &picker_info, 0 );
221     }
222     return picker_type;
223 }
224
225
226 static void
227 hildon_time_picker_class_init                   (HildonTimePickerClass *klass)
228 {
229     GObjectClass *gobject_class     = G_OBJECT_CLASS (klass);
230     GtkWidgetClass *widget_class    = GTK_WIDGET_CLASS (klass);
231     parent_class = g_type_class_peek_parent (klass);
232
233     gobject_class->finalize = hildon_time_picker_finalize;
234     gobject_class->get_property = hildon_time_picker_get_property;
235     gobject_class->set_property = hildon_time_picker_set_property;
236     widget_class->map = hildon_time_picker_map;
237
238     /**
239      * HildonTimePicker:minutes:
240      *
241      * Currently selected time in minutes since midnight.
242      */
243     g_object_class_install_property (gobject_class, PROP_MINUTES,
244             g_param_spec_uint ("minutes",
245                 "Current minutes",
246                 "The selected time in minutes "
247                 "since midnight",
248                 0, MINS_IN_24H, 0,
249                 G_PARAM_READABLE | G_PARAM_WRITABLE) );
250
251     gtk_widget_class_install_style_property (widget_class,
252             g_param_spec_uint ("arrow-width",
253                 "Arrow width",
254                 "Increase/decrease arrows width.",
255                 0, G_MAXUINT,
256                 DEFAULT_ARROW_WIDTH,
257                 G_PARAM_READABLE) );
258
259     gtk_widget_class_install_style_property (widget_class,
260             g_param_spec_uint ("arrow-height",
261                 "Arrow height",
262                 "Increase/decrease arrows height.",
263                 0, G_MAXUINT,
264                 DEFAULT_ARROW_HEIGHT,
265                 G_PARAM_READABLE) );
266
267     g_type_class_add_private (klass, sizeof (HildonTimePickerPrivate));
268 }
269
270 /* Okay, this is really bad. We make the requisition of the frames a bit larger 
271  * so that it doesn't "change" when digits are changed (see #37489). It's a 
272  * really bad solution to a problem, but the whole layout of the time picker is 
273  * on crack anyways */
274 static void 
275 frame_size_request                              (GtkWidget *widget, 
276                                                  GtkRequisition *requistion)
277 {
278     int framed = requistion->width / 10;
279     requistion->width = (framed + 1) * 10;
280 }
281
282 static void
283 hildon_time_picker_init                         (HildonTimePicker *picker)
284 {
285     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
286     gint widget_group_table_column_pos[WIDGET_GROUP_COUNT];
287     GtkSettings *settings = NULL;
288     GtkDialog *dialog = GTK_DIALOG (picker);
289     GtkTable *table = NULL;
290     GtkWidget *maintocenter, *colon_label;
291     const struct tm *local = NULL;
292     time_t stamp;
293     gint i = 0;
294
295     g_assert (priv);
296
297     widget_group_table_column_pos[WIDGET_GROUP_HOURS] = 1;
298     widget_group_table_column_pos[WIDGET_GROUP_10_MINUTES] = 3;
299     widget_group_table_column_pos[WIDGET_GROUP_1_MINUTES] = 4;
300     widget_group_table_column_pos[WIDGET_GROUP_AMPM] = 5;
301
302     /* Get AM/PM strings from locale. If they're set, the time is wanted
303        in 12 hour mode. */
304     priv->am_symbol = g_strdup (nl_langinfo (AM_STR));
305     priv->pm_symbol = g_strdup (nl_langinfo (PM_STR));
306
307     priv->show_ampm = priv->am_symbol[0] != '\0';
308     if (priv->show_ampm)
309     {
310         /* Check if AM/PM should be before or after time.
311            %p is the AM/PM string, so we assume that if the format string
312            begins with %p it's in the beginning, and in any other case it's
313            in the end (although that's not necessarily the case). */
314         if (strncmp (nl_langinfo (T_FMT_AMPM), "%p", 2) == 0)
315         {
316             /* Before time. Update column position. */
317             priv->ampm_left = TRUE;
318             widget_group_table_column_pos[WIDGET_GROUP_AMPM] = 0;
319         }
320     }
321
322     gtk_widget_push_composite_child ();
323
324     /* Pack all our internal widgets into a table */
325     table = GTK_TABLE (gtk_table_new (3, 6, FALSE));
326
327     /* Put everything centered into window */
328     maintocenter = gtk_alignment_new (0.5, 0, 0, 0);
329
330     /* Create our internal widgets */
331     for (i = 0; i < WIDGET_GROUP_COUNT; i++)
332     {
333         HildonTimePickerWidgetGroup *group = &priv->widgets[i];
334         gint table_column = widget_group_table_column_pos[i];
335
336         /* Create frame and attach to table. With AM/PM label we're attaching
337            it later. */
338         group->frame = gtk_frame_new (NULL);
339         if (i != WIDGET_GROUP_AMPM)
340         {
341             gtk_table_attach (table, group->frame, table_column, table_column + 1,
342                     1, 2, GTK_EXPAND, GTK_EXPAND, 0, 0);
343
344
345         }
346         /* FIXME: is it needed to force it to 0 here? */
347         gtk_container_set_border_width (GTK_CONTAINER(group->frame), 0);
348
349         /* Create eventbox inside frame */
350         group->eventbox = gtk_event_box_new ();
351         gtk_container_add (GTK_CONTAINER (group->frame), group->eventbox);
352
353         g_object_set (group->eventbox, "can-focus", TRUE, NULL);
354         gtk_widget_set_events (group->eventbox,
355                 GDK_FOCUS_CHANGE_MASK | GDK_BUTTON_PRESS_MASK);
356
357         /* Connect signals to eventbox */
358         g_signal_connect (group->eventbox, "key-release-event",
359                 G_CALLBACK (hildon_time_picker_event_box_key_release),
360                 picker);
361         g_signal_connect (group->eventbox, "key-press-event",
362                 G_CALLBACK (hildon_time_picker_event_box_key_press),
363                 picker);
364         g_signal_connect (group->eventbox, "focus-in-event",
365                 G_CALLBACK (hildon_time_picker_event_box_focus_in),
366                 picker);
367         g_signal_connect (group->eventbox, "focus-out-event",
368                 G_CALLBACK (hildon_time_picker_event_box_focus_out),
369                 picker);
370         g_signal_connect (group->eventbox, "button-press-event",
371                 G_CALLBACK (hildon_time_picker_event_box_button_press),
372                 picker);
373
374         /* Create label inside eventbox */
375         group->label = GTK_LABEL (gtk_label_new (NULL));
376         g_signal_connect (group->frame, "size-request",
377                 G_CALLBACK (frame_size_request),
378                 NULL);
379         gtk_misc_set_alignment (GTK_MISC (group->label), 0.5, 0.5);
380         gtk_container_add (GTK_CONTAINER (group->eventbox), GTK_WIDGET (group->label));
381
382         if (i != WIDGET_GROUP_AMPM)
383         {
384             gint button;
385
386             /* Add some padding to hour and minute labels, and make them bigger */
387             gtk_misc_set_padding(GTK_MISC (group->label),
388                     HILDON_TIME_PICKER_LABEL_X_PADDING,
389                     HILDON_TIME_PICKER_LABEL_Y_PADDING);
390
391             gtk_widget_set_name (GTK_WIDGET(group->label), "osso-LargeFont");
392
393             /* Create up and down buttons for hours and mins */
394             for (button = 0; button < BUTTON_COUNT; button++)
395             {
396                 gint table_row = button == BUTTON_UP ? 0 : 2;
397
398                 group->buttons[button] = gtk_button_new ();
399                 gtk_table_attach (table, group->buttons[button],
400                         table_column, table_column + 1,
401                         table_row, table_row + 1,
402                         GTK_SHRINK, GTK_SHRINK, 0, 0);
403                 g_object_set (group->buttons[button], "can-focus", FALSE, NULL);
404
405                 /* Connect signals */
406                 g_signal_connect(group->buttons[button], "button-press-event",
407                         G_CALLBACK (hildon_time_picker_arrow_press), picker);
408                 g_signal_connect(group->buttons[button], "button-release-event",
409                         G_CALLBACK (hildon_time_picker_arrow_release), picker);
410             }
411
412             gtk_widget_set_name (group->buttons[BUTTON_UP],
413                     "hildon-time-picker-up");
414             gtk_widget_set_name (group->buttons[BUTTON_DOWN],
415                     "hildon-time-picker-down");
416         }
417     }
418
419     /* Label between hour and minutes */
420     colon_label = gtk_label_new (NULL);
421     hildon_time_editor_get_time_separators (GTK_LABEL(colon_label), NULL);
422
423     gtk_table_attach (table, colon_label, 2, 3, 1, 2,
424             GTK_SHRINK, GTK_SHRINK, 6, 0); /* FIXME: magic */
425     gtk_widget_set_name (colon_label, "osso-LargeFont" );
426
427     priv->minutes = 0;
428     priv->mul = 0;
429     priv->key_repeat = 0;
430     priv->start_key_repeat = FALSE;
431     priv->timer_id = 0;
432     priv->button_press = FALSE;
433
434     gtk_table_set_row_spacing (table, 0, 6);
435     gtk_table_set_row_spacing (table, 1, 6);
436
437     if (priv->show_ampm)
438     {
439         gint table_column = widget_group_table_column_pos[WIDGET_GROUP_AMPM];
440         GtkWidget *ampmtotop = NULL;
441
442         /* Show the AM/PM label centered vertically */
443         ampmtotop = gtk_alignment_new (0, 0.5, 0, 0);
444         gtk_table_attach (table, ampmtotop, table_column, table_column + 1,
445                 1, 2, GTK_SHRINK, GTK_SHRINK, 0, 0);
446         gtk_container_add (GTK_CONTAINER (ampmtotop),
447                 priv->widgets[WIDGET_GROUP_AMPM].frame);
448
449         if (table_column != 0)
450             gtk_table_set_col_spacing (table, table_column - 1, 9);
451
452         /* Connect AM/PM signal handlers */
453         g_signal_connect (priv->widgets[WIDGET_GROUP_AMPM].eventbox,
454                 "button-release-event",
455                 G_CALLBACK(hildon_time_picker_ampm_release), picker);
456     }
457
458     gtk_widget_pop_composite_child ();
459
460     /* Get button press repeater timeout from settings (in milliseconds) */
461     settings = gtk_settings_get_default ();
462     g_object_get (settings, "gtk-timeout-update", &priv->key_repeat, NULL);
463
464     /* This dialog isn't modal */
465     gtk_window_set_modal (GTK_WINDOW(dialog), FALSE);
466     /* And final dialog packing */
467     gtk_dialog_set_has_separator (dialog, FALSE);
468     gtk_dialog_add_button (dialog, _("ecdg_bd_time_picker_close"),
469             GTK_RESPONSE_OK);
470
471     gtk_container_add (GTK_CONTAINER (maintocenter), GTK_WIDGET(table));
472     gtk_box_pack_start (GTK_BOX (dialog->vbox), maintocenter, TRUE, FALSE, 0);
473
474     /* Set default time to current time */
475     stamp = time (NULL);
476     local = localtime (&stamp);
477     hildon_time_picker_set_time (picker, local->tm_hour, local->tm_min);
478
479     gtk_widget_show_all (maintocenter);
480 }
481
482 static void
483 hildon_time_picker_set_property                 (GObject *object, 
484                                                  guint param_id,
485                                                  const GValue *value, 
486                                                  GParamSpec *pspec)
487 {
488     HildonTimePicker *picker = HILDON_TIME_PICKER (object);
489
490     switch (param_id)
491     {
492
493         case PROP_MINUTES:
494             hildon_time_picker_change_time (picker, g_value_get_uint(value));
495             break;
496
497         default:
498             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
499             break;
500     }
501 }
502
503 static void
504 hildon_time_picker_finalize                     (GObject *object)
505 {
506     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (object);
507
508     g_assert (priv);
509
510     /* Make sure the timer is stopped */
511     if (priv->timer_id)
512         g_source_remove(priv->timer_id);
513
514     g_free(priv->am_symbol);
515     g_free(priv->pm_symbol);
516
517     if (G_OBJECT_CLASS(parent_class)->finalize)
518         G_OBJECT_CLASS(parent_class)->finalize(object);
519 }
520
521 static void
522 hildon_time_picker_get_property                 (GObject *object, 
523                                                  guint param_id,
524                                                  GValue *value, 
525                                                  GParamSpec *pspec)
526 {
527     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (object);
528     g_assert (priv);
529
530     switch( param_id )
531     {
532         case PROP_MINUTES:
533             g_value_set_uint (value, priv->minutes);
534             break;
535
536         default:
537             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
538             break;
539     }
540 }
541
542 static void
543 hildon_time_picker_map                          (GtkWidget *widget)
544 {
545     guint width, height;
546     gint i, button;
547     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (widget);
548     g_assert (priv);
549
550     /* Widget is now mapped. Set border for the dialog. */
551     gdk_window_set_decorations (widget->window, GDK_DECOR_BORDER);
552
553     /* Update hour/minute up/down buttons sizes from style properties */
554     gtk_widget_style_get (widget,
555             "arrow-width", &width,
556             "arrow-height", &height, NULL);
557
558     for (i = 0; i < WIDGET_GROUP_COUNT; i++)
559     {
560         if (priv->widgets[i].buttons[0] != NULL)
561         {
562             for (button = 0; button < BUTTON_COUNT; button++)
563             {
564                 gtk_widget_set_size_request (priv->widgets[i].buttons[button], width, height);
565             }
566         }
567     }
568
569     GTK_WIDGET_CLASS (parent_class)->map(widget);
570 }
571
572 /* 
573  * Clicked hour/minute field. Move focus to it. 
574  */
575 static gboolean
576 hildon_time_picker_event_box_button_press       (GtkWidget *widget,
577                                                  GdkEventKey *event, 
578                                                  gpointer unused)
579 {
580     gtk_widget_grab_focus (widget);
581     return FALSE;
582 }
583
584 /* 
585  * Clicked AM/PM label. Move focus to it and move the time by 12 hours. 
586  */
587 static gboolean
588 hildon_time_picker_ampm_release                 (GtkWidget *widget, 
589                                                  GdkEvent *event,
590                                                  HildonTimePicker *picker)
591 {
592     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
593     g_assert (priv);
594
595     gtk_widget_grab_focus (widget);
596     hildon_time_picker_change_time (picker, priv->minutes > MINS_IN_12H ?
597             priv->minutes - MINS_IN_12H :
598             priv->minutes + MINS_IN_12H);
599
600     return FALSE;
601 }
602
603 static gboolean
604 hildon_time_picker_arrow_press                  (GtkWidget *widget, 
605                                                  GdkEvent *event,
606                                                  HildonTimePicker *picker)
607 {
608     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
609     gint i, button;
610     gint newval = 0;
611
612     /* Make sure we don't add repeat timer twice. Normally it shouldn't
613        happen but WM can cause button release to be lost. */
614     if (priv->button_press )
615         return FALSE;
616
617     priv->start_key_repeat = priv->button_press = TRUE;
618
619     /* Find the widget which was clicked */
620     priv->mul = 0;
621     for (i = 0; i < WIDGET_GROUP_COUNT; i++)
622     {
623         for (button = 0; button < BUTTON_COUNT; button++)
624         {
625             if (priv->widgets[i].buttons[button] == widget)
626             {
627                 /* Update multiplier and move the focus to the clicked field */
628                 priv->mul = button_multipliers[i][button];
629                 gtk_widget_grab_focus (priv->widgets[i].eventbox);
630                 break;
631             }
632         }
633     }
634     g_assert (priv->mul != 0);
635
636     /* Change the time now, wrapping if needed. */
637     newval = priv->minutes + priv->mul;
638     if( newval < 0 )
639         newval += MINS_IN_24H;
640
641     hildon_time_picker_change_time (picker, newval);
642
643     /* Keep changing the time as long as button is being pressed.
644        The first repeat takes 3 times longer to start than the rest. */
645     priv->timer_id = g_timeout_add (priv->key_repeat * 3,
646             hildon_time_picker_key_repeat_timeout,
647             picker);
648
649     return FALSE;
650 }
651
652 static gboolean
653 hildon_time_picker_arrow_release                (GtkWidget *widget, 
654                                                  GdkEvent *event,
655                                                  HildonTimePicker *picker)
656 {
657     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
658     g_assert (priv);
659
660     if (priv->timer_id)
661     {
662         /* Stop repeat timer */
663         g_source_remove (priv->timer_id);
664         priv->timer_id = 0;
665     }
666     
667     priv->button_press = FALSE;
668     return FALSE;
669 }
670
671 static gboolean
672 hildon_time_picker_event_box_focus_in           (GtkWidget *widget, 
673                                                  GdkEvent *event,
674                                                  gpointer unused)
675 {
676     /* Draw the widget in selected state so focus shows clearly. */
677     gtk_widget_set_state (widget, GTK_STATE_SELECTED);
678     return FALSE;
679 }
680
681 static gboolean
682 hildon_time_picker_event_box_focus_out          (GtkWidget *widget, 
683                                                  GdkEvent *event,
684                                                  gpointer unused)
685 {
686     /* Draw the widget in normal state */
687     gtk_widget_set_state( widget, GTK_STATE_NORMAL );
688     return FALSE;
689 }
690
691 static gint
692 hildon_time_picker_lookup_eventbox_group        (HildonTimePicker *picker,
693                                                  GtkWidget *widget)
694 {
695     gint i;
696     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
697
698     g_assert (priv);
699
700     for (i = 0; i < WIDGET_GROUP_COUNT; i++)
701     {
702         if (priv->widgets[i].eventbox == widget)
703             return i;
704     }
705     return -1;
706 }
707
708 static gboolean
709 hildon_time_picker_event_box_key_press          (GtkWidget *widget, 
710                                                  GdkEventKey *event,
711                                                  HildonTimePicker *picker)
712 {
713     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
714     HildonTimePickerWidgetGroup *group;
715     gint group_idx;
716
717     g_assert (priv);
718
719     /* If mouse button is already being pressed, ignore this keypress */
720     if (priv->timer_id )
721         return TRUE;
722
723     group_idx = hildon_time_picker_lookup_eventbox_group (picker, widget);
724     group = group_idx < 0 ? NULL : &priv->widgets[group_idx];
725
726     /* Handle keypresses in hour/minute/AMPM fields */
727     switch (event->keyval)
728     {
729         case GDK_Up:
730         case GDK_Down:
731             if (group != NULL)
732             {
733                 gint button = event->keyval == GDK_Up ? BUTTON_UP : BUTTON_DOWN;
734
735                 if (group->buttons[button] != NULL)
736                 {
737                     /* Fake a button up/down press */
738                     hildon_time_picker_arrow_press (group->buttons[button], NULL, picker);
739                     gtk_widget_set_state (group->buttons[button], GTK_STATE_SELECTED);
740                 }
741                 else
742                 {
743                     /* Fake a AM/PM button release */
744                     g_assert (group_idx == WIDGET_GROUP_AMPM);
745                     hildon_time_picker_ampm_release (group->eventbox, NULL, picker);
746                 }
747             }
748             return TRUE;
749
750         case GDK_Left:
751             /* If we're in leftmost field, stop this keypress signal.
752                Otherwise let the default key handler move focus to field in left. */
753             if (priv->show_ampm && priv->ampm_left)
754             {
755                 /* AM/PM is the leftmost field */
756                 if (group_idx == WIDGET_GROUP_AMPM)
757                     return TRUE;
758             }
759             else
760             {
761                 /* Hours is the leftmost field */
762                 if (group_idx == WIDGET_GROUP_HOURS)
763                     return TRUE;
764             }
765             break;
766
767         case GDK_Right:
768             /* If we're in rightmost field, stop this keypress signal.
769                Otherwise let the default key handler move focus to field in right. */
770             if (priv->show_ampm && !priv->ampm_left)
771             {
772                 /* AM/PM is the rightmost field */
773                 if (group_idx == WIDGET_GROUP_AMPM)
774                     return TRUE;
775             }
776             else
777             {
778                 /* 1-minutes is the leftmost field */
779                 if (group_idx == WIDGET_GROUP_1_MINUTES)
780                     return TRUE;
781             }
782             break;
783
784         case GDK_Escape:
785             gtk_dialog_response (GTK_DIALOG (picker), GTK_RESPONSE_CANCEL);
786             return TRUE;
787
788         case GDK_Return:
789             gtk_dialog_response (GTK_DIALOG (picker), GTK_RESPONSE_OK);
790             return TRUE;
791     }
792
793     return FALSE;
794 }
795
796 static gboolean
797 hildon_time_picker_event_box_key_release        (GtkWidget *widget, 
798                                                  GdkEventKey *event,
799                                                  HildonTimePicker *picker)
800 {
801     HildonTimePickerWidgetGroup *group;
802     gint group_idx;
803     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
804
805     g_assert (priv);
806
807     /* Fake a button release if in key-press handler we faked a button press. */
808     switch( event->keyval )
809     {
810         case GDK_Up:
811         case GDK_Down:
812             group_idx = hildon_time_picker_lookup_eventbox_group (picker, widget);
813             if (group_idx >= 0)
814             {
815                 gint button = event->keyval == GDK_Up ? BUTTON_UP : BUTTON_DOWN;
816
817                 group = &priv->widgets[group_idx];
818                 if (group->buttons[button] != NULL)
819                 {
820                     /* Fake a button up/down press */
821                     gtk_widget_set_state (group->buttons[button], GTK_STATE_NORMAL);
822                     hildon_time_picker_arrow_release (group->buttons[button], NULL, picker);
823                 }
824             }
825             break;
826     }
827     return FALSE;
828 }
829
830 /* Button up/down is being pressed. Update the time. */
831 static gboolean
832 hildon_time_picker_key_repeat_timeout           (gpointer tpicker)
833 {
834     HildonTimePicker *picker;
835     HildonTimePickerPrivate *priv = NULL;
836     gint newval = 0;
837
838     picker = HILDON_TIME_PICKER(tpicker);
839     g_assert(picker != NULL);
840
841     priv = HILDON_TIME_PICKER_GET_PRIVATE (tpicker);
842     g_assert (priv);
843
844     GDK_THREADS_ENTER ();
845
846     /* Change the time, wrapping if needed */
847     newval = priv->minutes + priv->mul;
848     if (newval < 0)
849         newval += MINS_IN_24H;
850
851     hildon_time_picker_change_time (picker, newval);
852
853     if (priv->start_key_repeat)
854     {
855         /* This is the first repeat. Shorten the timeout to key_repeat
856            (instead of the first time's 3*key_repeat) */
857         priv->timer_id = g_timeout_add (priv->key_repeat,
858                 hildon_time_picker_key_repeat_timeout,
859                 picker);
860         priv->start_key_repeat = FALSE;
861
862         GDK_THREADS_LEAVE ();
863         return FALSE;
864     }
865
866     GDK_THREADS_LEAVE ();
867     return TRUE;
868 }
869
870 static void
871 hildon_time_picker_change_time                  (HildonTimePicker *picker, 
872                                                  guint minutes)
873 {
874     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
875
876     gchar str[3] = "00";
877     guint hours = 0;
878     gboolean ampm = TRUE;
879
880     g_assert (priv);
881
882     /* If the minutes isn't in valid range, wrap them. */
883     minutes %= MINS_IN_24H;
884
885     if (priv->minutes == minutes)
886         return;
887
888     /* Minutes changed. Update widgets to show the new time. */
889     priv->minutes = minutes;
890
891     if (priv->show_ampm)
892     {
893         /* am < 12:00 <= pm */
894         ampm = !((guint)(minutes / MINS_IN_12H));
895         /* 12:00 - 23:59 -> 00:00 - 11:59 */
896         minutes %= MINS_IN_12H;
897         if (minutes < MINS_IN_1H )
898             /* 00:mm is always shown as 12:mm */
899             minutes += MINS_IN_12H;
900
901         /* Update the AM/PM label */
902         gtk_label_set_text (priv->widgets[WIDGET_GROUP_AMPM].label,
903                 ampm ? priv->am_symbol : priv->pm_symbol);
904     }
905
906     /* Update hour and minute fields */
907     hours = minutes / MINS_IN_1H;
908     minutes %= MINS_IN_1H;
909
910     snprintf(str, sizeof (str), "%02d", hours);
911     gtk_label_set_text (priv->widgets[WIDGET_GROUP_HOURS].label, str);
912
913     snprintf(str, sizeof (str), "%d", minutes / 10);
914     gtk_label_set_text (priv->widgets[WIDGET_GROUP_10_MINUTES].label, str);
915
916     snprintf(str, sizeof (str), "%d", minutes % 10);
917     gtk_label_set_text(priv->widgets[WIDGET_GROUP_1_MINUTES].label, str);
918
919     g_object_notify (G_OBJECT(picker), "minutes");
920 }
921
922 /**
923  * hildon_time_picker_new:
924  * @parent: parent window
925  *
926  * #HildonTimePicker shows time picker dialog. The close button is placed
927  * in the dialog's action area and time picker is placed in dialogs vbox.
928  * The actual time picker consists of two #GtkLabel fields - one for hours 
929  * and one for minutes - and an AM/PM button. A colon (:) is placed
930  * between hour and minute fields.
931  *
932  * Returns: pointer to a new #HildonTimePicker widget.
933  */
934 GtkWidget*
935 hildon_time_picker_new                          (GtkWindow *parent)
936 {
937     GtkWidget *widget = g_object_new (HILDON_TYPE_TIME_PICKER,
938             "minutes", 360, NULL );
939
940     if (parent)
941         gtk_window_set_transient_for (GTK_WINDOW(widget), parent);
942
943     return GTK_WIDGET (widget);
944 }
945
946 /**
947  * hildon_time_picker_set_time:
948  * @picker: the #HildonTimePicker widget
949  * @hours: hours
950  * @minutes: minutes
951  *
952  * Sets the time of the #HildonTimePicker widget.
953  */
954 void
955 hildon_time_picker_set_time                     (HildonTimePicker *picker,
956                                                  guint hours, 
957                                                  guint minutes )
958 {
959     g_return_if_fail (HILDON_IS_TIME_PICKER(picker));
960     hildon_time_picker_change_time (picker, hours * MINS_IN_1H + minutes);
961 }
962
963 /**
964  * hildon_time_picker_get_time:
965  * @picker: the #HildonTimePicker widget
966  * @hours: hours
967  * @minutes: minutes
968  *
969  * Gets the time of the #HildonTimePicker widget.
970  */
971 void 
972 hildon_time_picker_get_time                     (HildonTimePicker *picker,
973                                                  guint *hours, 
974                                                  guint *minutes )
975 {
976     guint current;
977     g_return_if_fail (HILDON_IS_TIME_PICKER (picker));
978
979     HildonTimePickerPrivate *priv = HILDON_TIME_PICKER_GET_PRIVATE (picker);
980     g_assert (priv);
981
982     current = priv->minutes;
983     *hours = current / MINS_IN_1H;
984     *minutes = current % MINS_IN_1H;
985 }
986
987