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