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