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