2009-01-19 Claudio Saavedra <csaavedra@igalia.com>
[hildon] / src / hildon-picker-button.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2008 Nokia Corporation, all rights reserved.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser Public License as published by
8  * the Free Software Foundation; version 2 of the license.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser Public License for more details.
14  *
15  */
16
17 /**
18  * SECTION:hildon-picker-button
19  * @short_description: A button that launches a #HildonPickerDialog and displays the
20  * selected item
21  * @see_also: #HildonTouchSelector, #HildonPickerDialog
22  *
23  * #HildonPickerButton is a widget that lets the user select a particular item from
24  * a list. Visually, it's a button with title and value labels that brings up a
25  * #HildonPickerDialog. The user can then use this dialog to choose an item, which
26  * will be displayed in the value label of the button.
27  *
28  * You should create your own #HildonTouchSelector at convenience and set it
29  * to the #HildonPickerButton with hildon_picker_button_set_selector(). For
30  * the common use cases of buttons to select date and time, you can use #HildonDateButton
31  * and #HildonTimeButton.
32  *
33  * <example>
34  * <programlisting>
35  * GtkWidget *
36  * create_selector (void)
37  * {
38  *   GtkWidget *selector;
39  * <!-- -->
40  *   selector = hildon_touch_selector_new_text ();
41  * <!-- -->
42  *   hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), "America");
43  *   hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), "Europe");
44  *   hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), "Asia");
45  *   hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), "Africa");
46  *   hildon_touch_selector_append_text (HILDON_TOUCH_SELECTOR (selector), "Australia");
47  * <!-- -->
48  *   hildon_touch_selector_set_active (HILDON_TOUCH_SELECTOR (selector), 0, 2);
49  * <!-- -->
50  *   return selector;
51  * }
52  * <!-- -->
53  * GtkWidget *
54  * create_button (HildonTouchSelector *selector)
55  * {
56  *   GtkWidget *button;
57  * <!-- -->
58  *   button = hildon_picker_button_new (HILDON_SIZE_AUTO, HILDON_BUTTON_ARRANGEMENT_VERTICAL);
59  *   hildon_button_set_title (HILDON_BUTTON (button), "Continent");
60  * <!-- -->
61  *   hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (button),
62  *                                      HILDON_TOUCH_SELECTOR (selector));
63  * <!-- -->
64  *   return button;
65  * }
66  * </programlisting>
67  * </example>
68  */
69
70 #include "hildon-picker-button.h"
71 #include "hildon-picker-dialog.h"
72
73 G_DEFINE_TYPE (HildonPickerButton, hildon_picker_button, HILDON_TYPE_BUTTON)
74
75 #define GET_PRIVATE(o)                                                  \
76   (G_TYPE_INSTANCE_GET_PRIVATE ((o), HILDON_TYPE_PICKER_BUTTON, HildonPickerButtonPrivate))
77
78 typedef struct _HildonPickerButtonPrivate HildonPickerButtonPrivate;
79
80 struct _HildonPickerButtonPrivate
81 {
82   GtkWidget *selector;
83   GtkWidget *dialog;
84   gchar *done_button_text;
85 };
86
87 /* Signals */
88 enum
89 {
90   VALUE_CHANGED,
91   LAST_SIGNAL
92 };
93
94 enum
95 {
96   PROP_SELECTOR = 1,
97   PROP_DONE_BUTTON_TEXT
98 };
99
100 static guint picker_button_signals[LAST_SIGNAL] = { 0 };
101
102 static gboolean
103 _current_selector_empty                         (HildonPickerButton *button);
104
105 static void
106 hildon_picker_button_selector_selection_changed (HildonTouchSelector * selector,
107                                                  gint column,
108                                                  gpointer user_data);
109
110 static void
111 hildon_picker_button_selector_columns_changed   (HildonTouchSelector * selector,
112                                                  gpointer user_data);
113
114
115 static void
116 hildon_picker_button_get_property (GObject * object, guint property_id,
117                                    GValue * value, GParamSpec * pspec)
118 {
119   switch (property_id) {
120   case PROP_SELECTOR:
121     g_value_set_object (value,
122                         hildon_picker_button_get_selector (HILDON_PICKER_BUTTON (object)));
123     break;
124   case PROP_DONE_BUTTON_TEXT:
125     g_value_set_string (value,
126                         hildon_picker_button_get_done_button_text (HILDON_PICKER_BUTTON (object)));
127     break;
128   default:
129     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
130   }
131 }
132
133 static void
134 hildon_picker_button_set_property (GObject * object, guint property_id,
135                                    const GValue * value, GParamSpec * pspec)
136 {
137   switch (property_id) {
138   case PROP_SELECTOR:
139     hildon_picker_button_set_selector (HILDON_PICKER_BUTTON (object),
140                                        g_value_get_object (value));
141     break;
142   case PROP_DONE_BUTTON_TEXT:
143     hildon_picker_button_set_done_button_text (HILDON_PICKER_BUTTON (object),
144                                                g_value_get_string (value));
145     break;
146   default:
147     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
148   }
149 }
150
151 static void
152 hildon_picker_button_finalize (GObject * object)
153 {
154   HildonPickerButtonPrivate *priv;
155
156   priv = GET_PRIVATE (object);
157
158   if (priv->selector) {
159     g_signal_handlers_disconnect_by_func (priv->selector,
160                                           hildon_picker_button_selector_selection_changed,
161                                           object);
162     g_signal_handlers_disconnect_by_func (priv->selector,
163                                           hildon_picker_button_selector_columns_changed,
164                                           object);
165     g_object_unref (priv->selector);
166     priv->selector = NULL;
167   }
168   if (priv->dialog) {
169     gtk_widget_destroy (priv->dialog);
170     priv->dialog = NULL;
171   }
172
173   if (priv->done_button_text) {
174     g_free (priv->done_button_text);
175     priv->done_button_text = NULL;
176   }
177
178   G_OBJECT_CLASS (hildon_picker_button_parent_class)->finalize (object);
179 }
180
181 static void
182 _selection_changed (HildonPickerButton *button)
183 {
184   HildonPickerButtonPrivate *priv = GET_PRIVATE (button);
185
186   if (!GTK_IS_WINDOW (priv->dialog) ||
187       !GTK_WIDGET_VISIBLE (GTK_WINDOW (priv->dialog))) {
188     gchar *value = hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (priv->selector));
189     if (value) {
190       hildon_button_set_value (HILDON_BUTTON (button), value);
191       g_free (value);
192       g_signal_emit (button, picker_button_signals[VALUE_CHANGED], 0);
193     }
194   }
195 }
196
197 static void
198 hildon_picker_button_on_dialog_response (GtkDialog *dialog,
199                                          gint       response,
200                                          gpointer   user_data)
201 {
202   gtk_widget_hide (GTK_WIDGET (dialog));
203
204   if (response == GTK_RESPONSE_OK) {
205     _selection_changed (HILDON_PICKER_BUTTON (user_data));
206   }
207 }
208
209 static void
210 hildon_picker_button_clicked (GtkButton * button)
211 {
212   GtkWidget *parent;
213   HildonPickerButtonPrivate *priv;
214
215   priv = GET_PRIVATE (HILDON_PICKER_BUTTON (button));
216
217   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (priv->selector));
218
219   /* Create the dialog if it doesn't exist already.  */
220   if (!priv->dialog) {
221     parent = gtk_widget_get_toplevel (GTK_WIDGET (button));
222     if (GTK_WIDGET_TOPLEVEL (parent)) {
223       priv->dialog = hildon_picker_dialog_new (GTK_WINDOW (parent));
224     } else {
225       priv->dialog = hildon_picker_dialog_new (NULL);
226     }
227
228     hildon_picker_dialog_set_selector (HILDON_PICKER_DIALOG (priv->dialog),
229                                        HILDON_TOUCH_SELECTOR (priv->selector));
230     if (priv->done_button_text) {
231       hildon_picker_dialog_set_done_label (HILDON_PICKER_DIALOG (priv->dialog),
232                                            priv->done_button_text);
233     }
234
235     gtk_window_set_modal (GTK_WINDOW (priv->dialog),
236                           gtk_window_get_modal (GTK_WINDOW (parent)));
237     gtk_window_set_title (GTK_WINDOW (priv->dialog),
238                           hildon_button_get_title (HILDON_BUTTON (button)));
239     g_signal_connect (priv->dialog, "response",
240                       G_CALLBACK (hildon_picker_button_on_dialog_response),
241                       button);
242     g_signal_connect (priv->dialog, "delete-event",
243                       G_CALLBACK (gtk_widget_hide_on_delete),
244                       NULL);
245   }
246
247   if (_current_selector_empty (HILDON_PICKER_BUTTON (button))) {
248     g_warning ("There are no elements in the selector. Nothing to show.");
249   } {
250     gtk_window_present (GTK_WINDOW (priv->dialog));
251   }
252 }
253
254 static void
255 hildon_picker_button_selector_selection_changed (HildonTouchSelector * selector,
256                                                  gint column,
257                                                  gpointer user_data)
258
259 {
260   _selection_changed (HILDON_PICKER_BUTTON (user_data));
261 }
262
263 static void
264 hildon_picker_button_selector_columns_changed (HildonTouchSelector * selector,
265                                                gpointer user_data)
266
267 {
268   _selection_changed (HILDON_PICKER_BUTTON (user_data));
269 }
270
271 static void
272 hildon_picker_button_class_init (HildonPickerButtonClass * klass)
273 {
274   GObjectClass *object_class = G_OBJECT_CLASS (klass);
275   GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
276
277   g_type_class_add_private (klass, sizeof (HildonPickerButtonPrivate));
278
279   object_class->get_property = hildon_picker_button_get_property;
280   object_class->set_property = hildon_picker_button_set_property;
281   object_class->finalize = hildon_picker_button_finalize;
282
283   button_class->clicked = hildon_picker_button_clicked;
284
285   g_object_class_install_property (object_class,
286                                    PROP_SELECTOR,
287                                    g_param_spec_object ("touch-selector",
288                                                         "HildonTouchSelector widget",
289                                                         "HildonTouchSelector widget to be launched on button clicked",
290                                                         HILDON_TYPE_TOUCH_SELECTOR,
291                                                         G_PARAM_READWRITE));
292   g_object_class_install_property (object_class,
293                                    PROP_DONE_BUTTON_TEXT,
294                                    g_param_spec_string ("done-button-text",
295                                                         "HildonPickerDialog \"done\" button text",
296                                                         "The text for the \"done\" button in the dialog launched",
297                                                         NULL,
298                                                         G_PARAM_READWRITE));
299
300   /**
301    * HildonPickerButton::value-changed:
302    * @widget: the widget that received the signal
303    *
304    * The ::value-changed signal is emitted each time the user chooses a different
305    * item from the #HildonTouchSelector related, and the value label gets updated.
306    *
307    * Since: 2.2
308    */
309   picker_button_signals[VALUE_CHANGED] =
310     g_signal_new ("value-changed",
311                   G_TYPE_FROM_CLASS (klass),
312                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
313                   0,
314                   NULL, NULL,
315                   g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
316 }
317
318 static void
319 hildon_picker_button_init (HildonPickerButton * self)
320 {
321   HildonPickerButtonPrivate *priv;
322
323   priv = GET_PRIVATE (self);
324
325   priv->dialog = NULL;
326   priv->selector = NULL;
327   priv->done_button_text = NULL;
328
329   hildon_button_set_style (HILDON_BUTTON (self),
330                            HILDON_BUTTON_STYLE_PICKER);
331 }
332
333 static gboolean
334 _current_selector_empty (HildonPickerButton *button)
335 {
336   HildonPickerButtonPrivate *priv;
337   HildonTouchSelector *selector = NULL;
338   GtkTreeModel *model = NULL;
339   GtkTreeIter iter;
340   gint i = 0;
341
342   priv = GET_PRIVATE (button);
343   selector = HILDON_TOUCH_SELECTOR (priv->selector);
344
345   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), TRUE);
346
347   if (hildon_touch_selector_has_multiple_selection (selector)) {
348     return FALSE;
349   } else {
350     for (i=0; i < hildon_touch_selector_get_num_columns (selector); i++) {
351       model = hildon_touch_selector_get_model (selector, i);
352
353       if (gtk_tree_model_get_iter_first (model, &iter)) {
354         return FALSE;
355       }
356     }
357     return TRUE;
358   }
359 }
360
361 /**
362  * hildon_picker_button_new:
363  * @size: One of #HildonSizeType, specifying the size of the new button.
364  * @arrangement: one of #HildonButtonArrangement, specifying the placement of the
365  * labels.
366  *
367  * Creates a new #HildonPickerButton. See hildon_button_new() for details on the
368  * parameters.
369  *
370  * Returns: a newly created #HildonPickerButton
371  *
372  * Since: 2.2
373  **/
374 GtkWidget *
375 hildon_picker_button_new (HildonSizeType          size,
376                           HildonButtonArrangement arrangement)
377 {
378   GtkWidget *button;
379
380   button = g_object_new (HILDON_TYPE_PICKER_BUTTON,
381                          "arrangement", arrangement, "size", size,
382                          NULL);
383
384   return button;
385 }
386
387 /**
388  * hildon_picker_button_set_selector:
389  * @button: a #HildonPickerButton
390  * @selector: a #HildonTouchSelector
391  *
392  * Sets @selector as the #HildonTouchSelector to be shown in the
393  * #HildonPickerDialog that @button brings up.
394  *
395  * Since: 2.2
396  **/
397 void
398 hildon_picker_button_set_selector (HildonPickerButton * button,
399                                    HildonTouchSelector * selector)
400 {
401   HildonPickerButtonPrivate *priv;
402   gchar *value;
403
404   g_return_if_fail (HILDON_IS_PICKER_BUTTON (button));
405   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (selector));
406
407   priv = GET_PRIVATE (button);
408
409   if (priv->selector) {
410     g_signal_handlers_disconnect_by_func (priv->selector,
411                                           hildon_picker_button_selector_selection_changed,
412                                           button);
413     g_signal_handlers_disconnect_by_func (priv->selector,
414                                           hildon_picker_button_selector_columns_changed,
415                                           button);
416     g_object_unref (priv->selector);
417   }
418
419   priv->selector = g_object_ref (selector);
420
421   g_signal_connect (G_OBJECT (selector), "changed",
422                     G_CALLBACK (hildon_picker_button_selector_selection_changed),
423                     button);
424
425   g_signal_connect (G_OBJECT (selector), "columns-changed",
426                     G_CALLBACK (hildon_picker_button_selector_columns_changed),
427                     button);
428
429   value = hildon_touch_selector_get_current_text (HILDON_TOUCH_SELECTOR (priv->selector));
430   if (value) {
431     hildon_button_set_value (HILDON_BUTTON (button), value);
432     g_free (value);
433     g_signal_emit (HILDON_PICKER_BUTTON (button),
434                    picker_button_signals[VALUE_CHANGED], 0);
435   }
436 }
437
438 /**
439  * hildon_picker_button_get_selector:
440  * @button: a #HildonPickerButton
441  *
442  * Retrieves the #HildonTouchSelector associated to @button.
443  *
444  * Returns: a #HildonTouchSelector
445  *
446  * Since: 2.2
447  **/
448 HildonTouchSelector *
449 hildon_picker_button_get_selector (HildonPickerButton * button)
450 {
451   HildonPickerButtonPrivate *priv;
452
453   g_return_val_if_fail (HILDON_IS_PICKER_BUTTON (button), NULL);
454
455   priv = GET_PRIVATE (button);
456
457   return HILDON_TOUCH_SELECTOR (priv->selector);
458 }
459
460 /**
461  * hildon_picker_button_get_active:
462  * @button: a #HildonPickerButton
463  *
464  * Returns the index of the currently active item, or -1 if there's no
465  * active item. If the selector has several columns, only the first
466  * one is used.
467  *
468  * Returns: an integer which is the index of the currently active item, or -1 if there's no active item.
469  *
470  * Since: 2.2
471  **/
472 gint
473 hildon_picker_button_get_active                 (HildonPickerButton * button)
474 {
475   HildonTouchSelector *sel;
476   g_return_val_if_fail (HILDON_IS_PICKER_BUTTON (button), -1);
477
478   sel = hildon_picker_button_get_selector (button);
479
480   return hildon_touch_selector_get_active (sel, 0);
481 }
482
483 /**
484  * hildon_picker_button_set_active:
485  * @button: a #HildonPickerButton
486  * @index: the index of the item to select, or -1 to have no active item
487  *
488  * Sets the active item of the #HildonTouchSelector associated to
489  * @button to @index. If the selector has several columns, only the
490  * first one is used.
491  *
492  * Since: 2.2
493  **/
494 void
495 hildon_picker_button_set_active                 (HildonPickerButton * button,
496                                                  gint index)
497 {
498   HildonTouchSelector *sel;
499   gchar *text;
500   g_return_if_fail (HILDON_IS_PICKER_BUTTON (button));
501
502   sel = hildon_picker_button_get_selector (button);
503   hildon_touch_selector_set_active (sel, 0, index);
504
505   text = hildon_touch_selector_get_current_text (sel);
506   hildon_button_set_value (HILDON_BUTTON (button), text);
507   g_free (text);
508 }
509
510 /**
511  * hildon_picker_button_get_done_button_text:
512  * @button: a #HildonPickerButton
513  *
514  * Gets the text used in the #HildonPickerDialog that is launched by
515  * @button. If no custom text is set, then %NULL is returned.
516  *
517  * Returns: the custom string to be used, or %NULL if the default
518  * #HildonPickerDialog::done-button-text is to be used.
519  *
520  * Since: 2.2
521  **/
522 const gchar *
523 hildon_picker_button_get_done_button_text (HildonPickerButton *button)
524 {
525   HildonPickerButtonPrivate *priv;
526
527   g_return_val_if_fail (HILDON_IS_PICKER_BUTTON (button), NULL);
528
529   priv = GET_PRIVATE (button);
530
531   return priv->done_button_text;
532 }
533
534 /**
535  * hildon_picker_button_set_done_button_text:
536  * @button: a #HildonPickerButton
537  * @done_button_text: a string
538  *
539  * Sets a custom string to be used in the "done" button in #HildonPickerDialog.
540  * If unset, the default HildonPickerButton::done-button-text property
541  * value will be used.
542  *
543  * Since: 2.2
544  **/
545 void
546 hildon_picker_button_set_done_button_text (HildonPickerButton *button,
547                                            const gchar *done_button_text)
548 {
549   HildonPickerButtonPrivate *priv;
550
551   g_return_if_fail (HILDON_IS_PICKER_BUTTON (button));
552   g_return_if_fail (done_button_text != NULL);
553
554   priv = GET_PRIVATE (button);
555
556   g_free (priv->done_button_text);
557   priv->done_button_text = g_strdup (done_button_text);
558
559   if (priv->dialog) {
560     hildon_picker_dialog_set_done_label (HILDON_PICKER_DIALOG (priv->dialog),
561                                          priv->done_button_text);
562   }
563 }