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