2009-02-27 Alberto Garcia <agarcia@igalia.com>
[hildon] / src / hildon-picker-dialog.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2005, 2008 Nokia Corporation.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version. or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free
18  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20
21 /**
22  * SECTION:hildon-picker-dialog
23  * @short_description: A utility widget that shows a #HildonTouchSelector widget
24  *
25  * #HildonPickerDialog is a dialog that is used to show a
26  * #HildonTouchSelector widget and a 'Done' button to allow users to
27  * finish their selections.
28  *
29  * The #HildonPickerDialog will show a 'Done' button in case the
30  * #HildonTouchSelector allows multiple selection. The label of the
31  * button can be set using hildon_picker_dialog_set_done_label() and
32  * retrieved using hildon_picker_dialog_get_done_label()
33  *
34  * Note that in most cases developers don't need to deal directly with
35  * this widget. #HildonPickerButton is designed to pop up a
36  * #HildonPickerDialog and manage the interaction with it.
37  */
38
39 #ifdef HAVE_CONFIG_H
40 #include <config.h>
41 #endif
42
43 #include <string.h>
44 #include <stdlib.h>
45 #include <libintl.h>
46
47 #include "hildon-touch-selector.h"
48 #include "hildon-picker-dialog.h"
49
50 #define _(String)  dgettext("hildon-libs", String)
51
52 #define HILDON_PICKER_DIALOG_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_PICKER_DIALOG, HildonPickerDialogPrivate))
53
54 G_DEFINE_TYPE (HildonPickerDialog, hildon_picker_dialog, HILDON_TYPE_DIALOG)
55
56 #define HILDON_TOUCH_SELECTOR_HEIGHT            320
57
58 struct _HildonPickerDialogPrivate
59 {
60   GtkWidget *selector;
61   GtkWidget *button;
62
63   gulong signal_changed_id;
64   gulong signal_columns_changed_id;
65
66   gboolean center_on_show;
67   GSList *current_selection;
68 };
69
70 /* properties */
71 enum
72 {
73   PROP_0,
74   PROP_DONE_BUTTON_TEXT,
75   PROP_CENTER_ON_SHOW,
76   PROP_LAST
77 };
78
79 enum
80 {
81   RESPONSE,
82   LAST_SIGNAL
83 };
84
85 #define DEFAULT_DONE_BUTTON_TEXT        _("wdgt_bd_done")
86
87 static void
88 hildon_picker_dialog_set_property               (GObject * object,
89                                                  guint prop_id,
90                                                  const GValue * value,
91                                                  GParamSpec * pspec);
92
93 static void
94 hildon_picker_dialog_get_property               (GObject * object,
95                                                  guint prop_id,
96                                                  GValue * value, GParamSpec * pspec);
97
98 static void
99 hildon_picker_dialog_finalize                   (GObject *object);
100
101 /* gtkwidget */
102 static void
103 hildon_picker_dialog_show                       (GtkWidget *widget);
104
105 static void
106 hildon_picker_dialog_realize                    (GtkWidget *widget);
107
108 /* private functions */
109 static gboolean
110 requires_done_button                            (HildonPickerDialog * dialog);
111
112 static void
113 prepare_action_area                             (HildonPickerDialog *dialog);
114
115 static void
116 setup_interaction_mode                          (HildonPickerDialog * dialog);
117
118 static void
119 _select_on_selector_changed_cb                  (HildonTouchSelector * dialog,
120                                                  gint column,
121                                                  gpointer data);
122
123 static gboolean
124 _hildon_picker_dialog_set_selector              (HildonPickerDialog * dialog,
125                                                  HildonTouchSelector * selector);
126
127 static void
128 _on_dialog_response                             (GtkDialog *dialog,
129                                                  gint response_id,
130                                                  gpointer data);
131
132 static void
133 _save_current_selection                         (HildonPickerDialog *dialog);
134
135 static void
136 _restore_current_selection                      (HildonPickerDialog *dialog);
137
138 static void
139 _clean_current_selection                        (HildonPickerDialog *dialog);
140
141 /**********************************************************************************/
142
143 static void
144 hildon_picker_dialog_class_init (HildonPickerDialogClass * class)
145 {
146   GObjectClass *gobject_class;
147   GtkObjectClass *object_class;
148   GtkWidgetClass *widget_class;
149   GtkContainerClass *container_class;
150
151   gobject_class = (GObjectClass *) class;
152   object_class = (GtkObjectClass *) class;
153   widget_class = (GtkWidgetClass *) class;
154   container_class = (GtkContainerClass *) class;
155
156   /* GObject */
157   gobject_class->set_property = hildon_picker_dialog_set_property;
158   gobject_class->get_property = hildon_picker_dialog_get_property;
159   gobject_class->finalize = hildon_picker_dialog_finalize;
160
161   /* GtkWidget */
162   widget_class->show = hildon_picker_dialog_show;
163   widget_class->realize = hildon_picker_dialog_realize;
164
165   /* HildonPickerDialog */
166   class->set_selector = _hildon_picker_dialog_set_selector;
167
168   /* signals */
169
170   /* properties */
171   /**
172    * HildonPickerDialog
173    *
174    * Button label
175    *
176    * Since: 2.2
177    */
178   g_object_class_install_property (gobject_class,
179                                    PROP_DONE_BUTTON_TEXT,
180                                    g_param_spec_string ("done-button-text",
181                                                         "Done Button Label",
182                                                         "Done Button Label",
183                                                         DEFAULT_DONE_BUTTON_TEXT,
184                                                         G_PARAM_READABLE |
185                                                         G_PARAM_WRITABLE |
186                                                         G_PARAM_CONSTRUCT));
187
188   g_object_class_install_property (gobject_class,
189                                    PROP_CENTER_ON_SHOW,
190                                    g_param_spec_boolean ("center-on-show",
191                                                          "Center on show",
192                                                          "If the dialog should center"
193                                                          " on the current selection"
194                                                          " when it is showed",
195                                                          TRUE,
196                                                          G_PARAM_READWRITE |
197                                                          G_PARAM_CONSTRUCT));
198
199   g_type_class_add_private (object_class, sizeof (HildonPickerDialogPrivate));
200 }
201
202
203 static void
204 hildon_picker_dialog_init (HildonPickerDialog * dialog)
205 {
206   dialog->priv = HILDON_PICKER_DIALOG_GET_PRIVATE (dialog);
207
208   dialog->priv->selector = NULL;
209   dialog->priv->button =
210     gtk_dialog_add_button (GTK_DIALOG (dialog), "", GTK_RESPONSE_OK);
211   gtk_widget_grab_default (dialog->priv->button);
212
213   dialog->priv->signal_changed_id = 0;
214   dialog->priv->signal_columns_changed_id = 0;
215   dialog->priv->center_on_show = TRUE;
216   dialog->priv->current_selection = NULL;
217
218   g_signal_connect (G_OBJECT (dialog),
219                     "response", G_CALLBACK (_on_dialog_response),
220                     NULL);
221 }
222
223
224 static void
225 hildon_picker_dialog_set_property (GObject * object,
226                                    guint param_id,
227                                    const GValue * value, GParamSpec * pspec)
228 {
229   HildonPickerDialog *dialog;
230
231   dialog = HILDON_PICKER_DIALOG (object);
232
233   switch (param_id) {
234   case PROP_DONE_BUTTON_TEXT:
235     hildon_picker_dialog_set_done_label (HILDON_PICKER_DIALOG (object),
236                                          g_value_get_string (value));
237     break;
238   case PROP_CENTER_ON_SHOW:
239     dialog->priv->center_on_show = g_value_get_boolean (value);
240     break;
241   default:
242     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
243     break;
244   }
245 }
246
247 static void
248 hildon_picker_dialog_get_property (GObject * object,
249                                    guint param_id,
250                                    GValue * value, GParamSpec * pspec)
251 {
252   HildonPickerDialog *dialog;
253
254   dialog = HILDON_PICKER_DIALOG (object);
255
256   switch (param_id) {
257   case PROP_DONE_BUTTON_TEXT:
258     g_value_set_string (value, hildon_picker_dialog_get_done_label (dialog));
259     break;
260   case PROP_CENTER_ON_SHOW:
261     g_value_set_boolean (value, dialog->priv->center_on_show);
262     break;
263   default:
264     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
265     break;
266   }
267 }
268
269 static void
270 hildon_picker_dialog_finalize (GObject *object)
271 {
272   _clean_current_selection (HILDON_PICKER_DIALOG (object));
273
274   G_OBJECT_CLASS (hildon_picker_dialog_parent_class)->finalize (object);
275 }
276
277 static void
278 hildon_picker_dialog_show                       (GtkWidget *widget)
279 {
280   HildonPickerDialog *dialog = HILDON_PICKER_DIALOG (widget);
281   HildonTouchSelector *selector;
282
283   if (dialog->priv->center_on_show) {
284     selector = hildon_picker_dialog_get_selector (dialog);
285     hildon_touch_selector_center_on_selected (selector);
286   }
287
288   _save_current_selection (dialog);
289   prepare_action_area (dialog);
290
291   GTK_WIDGET_CLASS (hildon_picker_dialog_parent_class)->show (widget);
292 }
293
294 static void
295 hildon_picker_dialog_realize (GtkWidget *widget)
296 {
297   setup_interaction_mode (HILDON_PICKER_DIALOG (widget));
298
299   GTK_WIDGET_CLASS (hildon_picker_dialog_parent_class)->realize (widget);
300 }
301
302 /* ------------------------------ PRIVATE METHODS ---------------------------- */
303
304 static void
305 _select_on_selector_changed_cb (HildonTouchSelector * selector,
306                                 gint column, gpointer data)
307 {
308   g_return_if_fail (HILDON_IS_PICKER_DIALOG (data));
309
310   gtk_dialog_response (GTK_DIALOG (data), GTK_RESPONSE_OK);
311 }
312
313 static gboolean
314 selection_completed (HildonPickerDialog *dialog)
315 {
316   HildonPickerDialogPrivate *priv;
317   GList *list;
318   gint i, n_cols;
319   gboolean all_selected = TRUE;
320
321   priv = HILDON_PICKER_DIALOG_GET_PRIVATE (dialog);
322
323   n_cols = hildon_touch_selector_get_num_columns (HILDON_TOUCH_SELECTOR (priv->selector));
324   for (i = 0; i < n_cols; i++) {
325     list = hildon_touch_selector_get_selected_rows (HILDON_TOUCH_SELECTOR (priv->selector), i);
326     if (list == NULL) {
327       all_selected = FALSE;
328       break;
329     }
330     g_list_foreach (list, (GFunc)gtk_tree_path_free, NULL);
331     g_list_free (list);
332   }
333
334   return all_selected;
335 }
336
337 static void
338 _on_dialog_response                             (GtkDialog *dialog,
339                                                  gint response_id,
340                                                  gpointer data)
341 {
342   if (response_id == GTK_RESPONSE_OK) {
343     if (selection_completed (HILDON_PICKER_DIALOG (dialog)) == FALSE) {
344       g_signal_stop_emission_by_name (dialog, "response");
345     }
346   } else if (response_id == GTK_RESPONSE_DELETE_EVENT) {
347     _restore_current_selection (HILDON_PICKER_DIALOG (dialog));
348   }
349 }
350
351 static void
352 on_selector_columns_changed (HildonTouchSelector * selector, gpointer userdata)
353 {
354   HildonPickerDialog * dialog;
355
356   dialog = HILDON_PICKER_DIALOG (userdata);
357
358   prepare_action_area (dialog);
359   if (GTK_WIDGET_REALIZED (dialog)) {
360     setup_interaction_mode (dialog);
361   }
362 }
363
364 /**
365  * hildon_picker_dialog_set_done_label:
366  * @dialog: a #HildonPickerDialog
367  * @label: a string
368  *
369  * Sets a custom string to be used as the 'Done' button label in @dialog.
370  *
371  * Since: 2.2
372  **/
373 void
374 hildon_picker_dialog_set_done_label (HildonPickerDialog * dialog,
375                                      const gchar * label)
376 {
377   HildonPickerDialogPrivate *priv;
378
379   g_return_if_fail (HILDON_IS_PICKER_DIALOG (dialog));
380   g_return_if_fail (label != NULL);
381
382   priv = HILDON_PICKER_DIALOG_GET_PRIVATE (dialog);
383
384   gtk_button_set_label (GTK_BUTTON (priv->button), label);
385 }
386
387 /**
388  * hildon_picker_dialog_get_done_label:
389  * @dialog: a #HildonPickerDialog
390  *
391  * Retrieves current 'Done' button label.
392  *
393  * Returns: the custom string to be used.
394  *
395  * Since: 2.2
396  **/
397 const gchar *
398 hildon_picker_dialog_get_done_label (HildonPickerDialog * dialog)
399 {
400   HildonPickerDialogPrivate *priv;
401
402   g_return_val_if_fail (HILDON_IS_PICKER_DIALOG (dialog), NULL);
403
404   priv = HILDON_PICKER_DIALOG_GET_PRIVATE (dialog);
405
406   return gtk_button_get_label (GTK_BUTTON (priv->button));
407 }
408
409 static void
410 free_path_list (GList *list)
411 {
412   g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
413   g_list_free (list);
414 }
415
416 static void
417 _clean_current_selection (HildonPickerDialog *dialog)
418 {
419   if (dialog->priv->current_selection) {
420     g_slist_foreach (dialog->priv->current_selection, (GFunc) free_path_list, NULL);
421     g_slist_free (dialog->priv->current_selection);
422     dialog->priv->current_selection = NULL;
423   }
424 }
425
426 static void
427 _save_current_selection (HildonPickerDialog *dialog)
428 {
429   HildonTouchSelector *selector;
430   gint i, columns;
431
432   selector = HILDON_TOUCH_SELECTOR (dialog->priv->selector);
433
434   _clean_current_selection (dialog);
435
436   columns = hildon_touch_selector_get_num_columns (selector);
437   for (i = 0; i  < columns; i++) {
438     dialog->priv->current_selection
439       = g_slist_append (dialog->priv->current_selection,
440                         hildon_touch_selector_get_selected_rows (selector, i));
441   }
442 }
443
444 static void
445 _restore_current_selection (HildonPickerDialog *dialog)
446 {
447   GSList *current_selection, *iter;
448   GList *selected, *selected_iter;
449   GtkTreePath *current_path;
450   HildonTouchSelector *selector;
451   GtkTreeModel *model;
452   GtkTreeIter tree_iter;
453   gint i;
454
455   if (dialog->priv->current_selection == NULL)
456     return;
457
458   current_selection = dialog->priv->current_selection;
459   selector = HILDON_TOUCH_SELECTOR (dialog->priv->selector);
460
461   if (hildon_touch_selector_get_num_columns (selector) !=
462       g_slist_length (current_selection)) {
463     /* We conclude that if the current selection has the same
464        numbers of columns that the selector, all this ok
465        Anyway this shouldn't happen. */
466     g_critical ("Trying to restore the selection on a selector after change"
467                 " the number of columns. Are you removing columns while the"
468                 " dialog is open?");
469     return;
470   }
471
472   if (dialog->priv->signal_changed_id)
473     g_signal_handler_block (selector, dialog->priv->signal_changed_id);
474   for (iter = current_selection, i = 0; iter; iter = g_slist_next (iter), i++) {
475     selected = (GList *) (iter->data);
476     model = hildon_touch_selector_get_model (selector, i);
477     hildon_touch_selector_unselect_all (selector, i);
478     for (selected_iter = selected; selected_iter; selected_iter = g_list_next (selected_iter)) {
479       current_path = (GtkTreePath *) selected_iter->data;
480       gtk_tree_model_get_iter (model, &tree_iter, current_path);
481       hildon_touch_selector_select_iter (selector, i, &tree_iter, FALSE);
482     }
483   }
484   if (dialog->priv->signal_changed_id)
485     g_signal_handler_unblock (selector, dialog->priv->signal_changed_id);
486 }
487
488 static gboolean
489 requires_done_button (HildonPickerDialog * dialog)
490 {
491   return hildon_touch_selector_has_multiple_selection
492     (HILDON_TOUCH_SELECTOR (dialog->priv->selector));
493 }
494
495 static void
496 prepare_action_area (HildonPickerDialog *dialog)
497 {
498   if (requires_done_button (dialog)) {
499     gtk_dialog_set_has_separator (GTK_DIALOG (dialog), TRUE);
500     gtk_widget_show (GTK_DIALOG (dialog)->action_area);
501   } else {
502     gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
503     gtk_widget_hide (GTK_DIALOG (dialog)->action_area);
504   }
505 }
506
507 static void
508 setup_interaction_mode (HildonPickerDialog * dialog)
509 {
510   if (dialog->priv->signal_changed_id) {
511     g_signal_handler_disconnect (dialog->priv->selector,
512                                  dialog->priv->signal_changed_id);
513   }
514
515   if (requires_done_button (dialog) == FALSE) {
516     dialog->priv->signal_changed_id =
517       g_signal_connect (G_OBJECT (dialog->priv->selector), "changed",
518                         G_CALLBACK (_select_on_selector_changed_cb), dialog);
519   }
520 }
521
522 /*------------------------- PUBLIC METHODS ---------------------------- */
523
524 /**
525  * hildon_picker_dialog_new:
526  * @parent: the parent window
527  *
528  * Creates a new #HildonPickerDialog
529  *
530  * Returns: a new #HildonPickerDialog
531  *
532  * Since: 2.2
533  **/
534 GtkWidget *
535 hildon_picker_dialog_new (GtkWindow * parent)
536 {
537   GtkDialog *dialog = NULL;
538
539   dialog = g_object_new (HILDON_TYPE_PICKER_DIALOG, NULL);
540
541   if (parent) {
542     gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
543   }
544
545   return GTK_WIDGET (dialog);
546 }
547
548
549 static gboolean
550 _hildon_picker_dialog_set_selector (HildonPickerDialog * dialog,
551                                     HildonTouchSelector * selector)
552 {
553   g_object_ref (selector);
554
555   /* Remove the old selector, if any */
556   if (dialog->priv->selector != NULL) {
557     gtk_container_remove (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
558                           dialog->priv->selector);
559     if (dialog->priv->signal_columns_changed_id) {
560             g_signal_handler_disconnect (dialog->priv->selector,
561                                          dialog->priv->signal_columns_changed_id);
562     }
563   }
564
565   dialog->priv->selector = GTK_WIDGET (selector);
566
567   /* Pack the new selector */
568   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
569                       dialog->priv->selector, TRUE, TRUE, 0);
570
571   g_object_unref (selector);
572
573   /* Ensure that the dialog's height is correct */
574   gtk_widget_set_size_request (GTK_WIDGET (dialog->priv->selector), -1,
575                                HILDON_TOUCH_SELECTOR_HEIGHT);
576
577   gtk_widget_show (dialog->priv->selector);
578
579   prepare_action_area (dialog);
580   if (GTK_WIDGET_REALIZED (dialog)) {
581     setup_interaction_mode (dialog);
582   }
583
584   dialog->priv->signal_columns_changed_id = g_signal_connect (G_OBJECT (dialog->priv->selector),
585                                                               "columns-changed",
586                                                               G_CALLBACK (on_selector_columns_changed), dialog);
587   return TRUE;
588 }
589
590 /**
591  * hildon_picker_dialog_set_selector:
592  * @dialog: a #HildonPickerDialog
593  * @selector: a #HildonTouchSelector
594  *
595  * Sets @selector as the #HildonTouchSelector to be shown in @dialog
596  *
597  * Returns: %TRUE if @selector was set, %FALSE otherwise
598  *
599  * Since: 2.2
600  **/
601 gboolean
602 hildon_picker_dialog_set_selector (HildonPickerDialog * dialog,
603                                    HildonTouchSelector * selector)
604 {
605   g_return_val_if_fail (HILDON_IS_PICKER_DIALOG (dialog), FALSE);
606   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), FALSE);
607
608   return HILDON_PICKER_DIALOG_GET_CLASS (dialog)->set_selector (dialog, selector);
609 }
610
611 /**
612  * hildon_picker_dialog_get_selector:
613  * @dialog: a #HildonPickerDialog
614  *
615  * Retrieves the #HildonTouchSelector associated to @dialog.
616  *
617  * Returns: a #HildonTouchSelector
618  *
619  * Since: 2.2
620  **/
621 HildonTouchSelector *
622 hildon_picker_dialog_get_selector (HildonPickerDialog * dialog)
623 {
624   g_return_val_if_fail (HILDON_IS_PICKER_DIALOG (dialog), NULL);
625
626   return HILDON_TOUCH_SELECTOR (dialog->priv->selector);
627 }