2008-08-14 Alberto Garcia <agarcia@igalia.com>
[hildon] / src / hildon-touch-selector.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-touch-selector
23  * @short_description: A selector widget with several columns.
24  *
25  * #HildonTouchSelector is a selector widget, that allows users to
26  * select items from one to many predefined lists. It is very similar
27  * to #GtkComboBox, but with several individual pannable columns.
28  *
29  * Normally, you would use #HildonTouchSelector together with a
30  * #HildonPickerDialog activated from a button. For the most common
31  * cases, you should use #HildonPickerButton.
32  *
33  * The composition of each column in the selector is represented by a
34  * #GtkTreeModel. To add a new column to a #HildonTouchSelector, use
35  * hildon_touch_selector_append_column(). If you want to add a
36  * text-only column, without special attributes, use
37  * hildon_touch_selector_append_text_column().
38  *
39  * It is highly recommended that you use only one column
40  * #HildonTouchSelector<!-- -->s.
41  * If you only need a text only, one column selector, you can create it with
42  * hildon_touch_selector_new_text() and populate with
43  * hildon_touch_selector_append_text(), hildon_touch_selector_prepend_text(),
44  * and hildon_touch_selector_insert_text().
45  *
46  * If you need a selector widget that also accepts user inputs, you
47  * can use #HildonTouchSelectorEntry.
48  */
49
50 #ifdef HAVE_CONFIG_H
51 #include <config.h>
52 #endif
53
54 #include <string.h>
55 #include <stdlib.h>
56
57 #include "hildon-pannable-area.h"
58 #include "hildon-touch-selector.h"
59
60 #define HILDON_TOUCH_SELECTOR_GET_PRIVATE(obj)                          \
61   (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HILDON_TYPE_TOUCH_SELECTOR, HildonTouchSelectorPrivate))
62
63 G_DEFINE_TYPE (HildonTouchSelector, hildon_touch_selector, GTK_TYPE_VBOX)
64
65 #define CENTER_ON_SELECTED_ITEM_DELAY 50
66
67 /*
68  * Struct to maintain the data of each column. The columns are the elements
69  * of the widget that belongs properly to the selection behaviour. As
70  * the selector contents are arranged in a #GtkHBox, you can add more widgets, like buttons etc.
71  * between the columns, but this doesn't belongs to the selection
72  * logic
73  */
74 typedef struct _SelectorColumn SelectorColumn;
75 struct _SelectorColumn
76 {
77   HildonTouchSelector *parent;    /* the selector that contains this column */
78   GtkTreeModel *model;
79   GtkTreeView *tree_view;
80
81   GtkWidget *panarea;           /* the pannable widget */
82 };
83
84 struct _HildonTouchSelectorPrivate
85 {
86   GSList *columns;              /* the selection columns */
87   GtkWidget *hbox;              /* the container for the selector's columns */
88
89   HildonTouchSelectorPrintFunc print_func;
90 };
91
92 enum
93 {
94   PROP_HAS_MULTIPLE_SELECTION = 1
95 };
96
97 enum
98 {
99   CHANGED,
100   LAST_SIGNAL
101 };
102
103 static gint hildon_touch_selector_signals[LAST_SIGNAL] = { 0 };
104
105 static void hildon_touch_selector_get_property (GObject * object,
106                                                 guint prop_id,
107                                                 GValue * value, GParamSpec * pspec);
108
109 /* gtkwidget */
110 static void hildon_touch_selector_map (GtkWidget * widget);
111
112 /* gtkcontainer */
113 static void hildon_touch_selector_remove (GtkContainer * container,
114                                           GtkWidget * widget);
115 /* private functions */
116 static void _selection_changed_cb (GtkTreeSelection * selection,
117                                    gpointer user_data);
118 static gchar *_default_print_func (HildonTouchSelector * selector);
119
120 static SelectorColumn *_create_new_column (HildonTouchSelector * selector,
121                                            GtkTreeModel * model,
122                                            GtkCellRenderer * renderer,
123                                            va_list args);
124 static gboolean _hildon_touch_selector_center_on_selected_items (gpointer data);
125
126 static void
127 _hildon_touch_selector_set_model (HildonTouchSelector * selector,
128                                   gint num_column, GtkTreeModel * model);
129 static gboolean
130 _hildon_touch_selector_has_multiple_selection (HildonTouchSelector * selector);
131
132 static void
133 hildon_touch_selector_class_init (HildonTouchSelectorClass * class)
134 {
135   GObjectClass *gobject_class;
136   GtkObjectClass *object_class;
137   GtkWidgetClass *widget_class;
138   GtkContainerClass *container_class;
139
140   gobject_class = (GObjectClass *) class;
141   object_class = (GtkObjectClass *) class;
142   widget_class = (GtkWidgetClass *) class;
143   container_class = (GtkContainerClass *) class;
144
145   /* GObject */
146
147   gobject_class->get_property = hildon_touch_selector_get_property;
148
149   /* GtkWidget */
150   widget_class->map = hildon_touch_selector_map;
151
152   /* GtkContainer */
153   container_class->remove = hildon_touch_selector_remove;
154
155   /* HildonTouchSelector */
156   class->set_model = _hildon_touch_selector_set_model;
157
158   class->has_multiple_selection = _hildon_touch_selector_has_multiple_selection;
159
160   /* signals */
161   /**
162    * HildonTouchSelector::changed:
163    * @widget: the object which received the signal
164    *
165    * The changed signal is emitted when the active
166    * item is changed. This can be due to the user selecting
167    * a different item from the list, or due to a
168    * call to hildon_touch_selector_set_active_iter() on
169    * one of the columns.
170    *
171    */
172   hildon_touch_selector_signals[CHANGED] =
173     g_signal_new ("changed",
174                   G_OBJECT_CLASS_TYPE (class),
175                   G_SIGNAL_RUN_LAST,
176                   G_STRUCT_OFFSET (HildonTouchSelectorClass, changed),
177                   NULL, NULL,
178                   gtk_marshal_NONE__INT, G_TYPE_NONE, 1, G_TYPE_INT);
179   /* properties */
180
181   g_object_class_install_property (gobject_class, PROP_HAS_MULTIPLE_SELECTION,
182                                    g_param_spec_boolean ("has-multiple-selection",
183                                                          "has multiple selection",
184                                                          "Whether the widget has multiple "
185                                                          "selection (like multiple columns, "
186                                                          "multiselection mode, or multiple "
187                                                          "internal widgets) and therefore "
188                                                          "it may need a confirmation button, "
189                                                          "for instance.",
190                                                          FALSE,
191                                                          G_PARAM_READABLE));
192
193   /* style properties */
194   g_type_class_add_private (object_class, sizeof (HildonTouchSelectorPrivate));
195 }
196
197 static void
198 hildon_touch_selector_get_property (GObject * object,
199                                     guint prop_id,
200                                     GValue * value, GParamSpec * pspec)
201 {
202   switch (prop_id) {
203   case PROP_HAS_MULTIPLE_SELECTION:
204     g_value_set_boolean (value,
205                          hildon_touch_selector_has_multiple_selection (HILDON_TOUCH_SELECTOR (object)));
206     break;
207   default:
208     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
209     break;
210   }
211 }
212
213 static void
214 hildon_touch_selector_init (HildonTouchSelector * selector)
215 {
216   selector->priv = HILDON_TOUCH_SELECTOR_GET_PRIVATE (selector);
217
218   GTK_WIDGET_SET_FLAGS (GTK_WIDGET (selector), GTK_NO_WINDOW);
219   gtk_widget_set_redraw_on_allocate (GTK_WIDGET (selector), FALSE);
220
221   selector->priv->columns = NULL;
222
223   selector->priv->print_func = NULL;
224   selector->priv->hbox = gtk_hbox_new (FALSE, 0);
225
226   gtk_box_pack_end (GTK_BOX (selector), selector->priv->hbox,
227                     TRUE, TRUE, 0);
228   gtk_widget_show (selector->priv->hbox);
229
230   /* FIXME: this is the correct height? A fixed height is the correct 
231      implementation */
232   gtk_widget_set_size_request (GTK_WIDGET (selector), -1, 320);
233 }
234
235 static void
236 hildon_touch_selector_map (GtkWidget * widget)
237 {
238   GTK_WIDGET_CLASS (hildon_touch_selector_parent_class)->map (widget);
239
240   g_timeout_add (CENTER_ON_SELECTED_ITEM_DELAY,
241                  _hildon_touch_selector_center_on_selected_items, widget);
242 }
243
244 /*------------------------------ GtkContainer ------------------------------ */
245
246 /*
247  * Required in order to free the column at the columns list
248  */
249 static void
250 hildon_touch_selector_remove (GtkContainer * container, GtkWidget * widget)
251 {
252   HildonTouchSelector *selector = NULL;
253   GSList *iter = NULL;
254   gint position = 0;
255   SelectorColumn *current_column = NULL;
256   gint num_columns = 0;
257
258   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (container));
259
260   selector = HILDON_TOUCH_SELECTOR (container);
261   num_columns = hildon_touch_selector_get_num_columns (selector);
262
263   /* Check if the widget is inside a column and remove
264      it from the list */
265   iter = selector->priv->columns;
266   position = 0;
267   while (iter) {
268     current_column = (SelectorColumn *) iter->data;
269     if (widget == current_column->panarea) {
270       current_column = g_slist_nth_data (selector->priv->columns, position);
271
272       selector->priv->columns = g_slist_remove (selector->priv->columns,
273                                                 current_column);
274       g_free (current_column);
275
276       break;
277     }
278
279     position++;
280     iter = g_slist_next (iter);
281   }
282   if (position >= num_columns) {
283     g_debug ("This widget was not inside the selector column");
284   }
285
286   GTK_CONTAINER_CLASS (hildon_touch_selector_parent_class)->remove (container, widget);
287 }
288
289 /* ------------------------------ PRIVATE METHODS ---------------------------- */
290 /**
291  * default_print_func:
292  * @selector: a #HildonTouchSelector
293  *
294  * Default print function
295  *
296  * Returns: a new string that represent the selected items
297  **/
298 static gchar *
299 _default_print_func (HildonTouchSelector * selector)
300 {
301   gchar *result = NULL;
302   gchar *aux = NULL;
303   gint num_columns = 0;
304   GtkTreeIter iter;
305   GtkTreeModel *model = NULL;
306   gchar *current_string = NULL;
307   gint i;
308   HildonTouchSelectorSelectionMode mode;
309   GList *item = NULL;
310   GtkTreePath *current_path = NULL;
311   GList *selected_rows = NULL;
312   gint initial_value = 0;
313
314   num_columns = hildon_touch_selector_get_num_columns (selector);
315
316   mode = hildon_touch_selector_get_column_selection_mode (selector);
317
318   if ((mode == HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE)
319       && (num_columns > 0)) {
320     /* In this case we get the first column first */
321     selected_rows = hildon_touch_selector_get_selected_rows (selector, 0);
322     model = hildon_touch_selector_get_model (selector, 0);
323
324     result = g_strdup_printf ("(");
325     i = 0;
326     for (item = selected_rows; item; item = g_list_next (item)) {
327       current_path = item->data;
328       gtk_tree_model_get_iter (model, &iter, current_path);
329
330       gtk_tree_model_get (model, &iter, 0, &current_string, -1);
331
332       if (i < g_list_length (selected_rows) - 1) {
333         aux = g_strconcat (result, current_string, ",", NULL);
334         g_free (result);
335         result = aux;
336       } else {
337         aux = g_strconcat (result, current_string, NULL);
338         g_free (result);
339         result = aux;
340       }
341       i++;
342     }
343
344     aux = g_strconcat (result, ")", NULL);
345     g_free (result);
346     result = aux;
347
348     g_list_foreach (selected_rows, (GFunc) (gtk_tree_path_free), NULL);
349     g_list_free (selected_rows);
350     initial_value = 1;
351   } else {
352     initial_value = 0;
353   }
354
355   for (i = initial_value; i < num_columns; i++) {
356     model = hildon_touch_selector_get_model (selector, i);
357     if (hildon_touch_selector_get_active_iter (selector, i, &iter)) {
358
359       gtk_tree_model_get (model, &iter, 0, &current_string, -1);
360       if (i != 0) {
361         aux = g_strconcat (result, ":", current_string, NULL);
362         g_free (result);
363         result = aux;
364       } else {
365         result = g_strdup_printf ("%s", current_string);
366       }
367     }
368   }
369
370   return result;
371 }
372
373 static void
374 _selection_changed_cb (GtkTreeSelection * selection, gpointer user_data)
375 {
376   HildonTouchSelector *selector = NULL;
377   SelectorColumn *column = NULL;
378   gint num_column = -1;
379
380   column = (SelectorColumn *) user_data;
381   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (column->parent));
382
383   selector = column->parent;
384
385   num_column = g_slist_index (selector->priv->columns, column);
386
387   g_signal_emit (selector, hildon_touch_selector_signals[CHANGED], 0, num_column);
388 }
389
390
391 static SelectorColumn *
392 _create_new_column (HildonTouchSelector * selector,
393                     GtkTreeModel * model,
394                     GtkCellRenderer * renderer, va_list args)
395 {
396   SelectorColumn *new_column = NULL;
397   GtkTreeViewColumn *tree_column = NULL;
398   GValue val = { 0, };
399   GtkTreeView *tv = NULL;
400   GtkWidget *panarea = NULL;
401   GtkTreeSelection *selection = NULL;
402   GtkTreeIter iter;
403   gchar *attribute;
404   gint value;
405
406   tree_column = gtk_tree_view_column_new ();
407   gtk_tree_view_column_pack_start (tree_column, renderer, TRUE);
408
409   attribute = va_arg (args, gchar *);
410   while (attribute != NULL) {
411     value = va_arg (args, gint);
412     gtk_tree_view_column_add_attribute (tree_column, renderer, attribute,
413                                         value);
414     attribute = va_arg (args, gchar *);
415   }
416
417   tv = g_object_new (GTK_TYPE_TREE_VIEW, "model", model, "name", "fremantle-widget",    /* FIXME: is this always this name? */
418                      "hildon-ui-mode", HILDON_UI_MODE_EDIT,
419                      "rules-hint", TRUE, NULL);
420
421   gtk_tree_view_append_column (GTK_TREE_VIEW (tv), tree_column);
422
423   new_column = (SelectorColumn *) g_malloc0 (sizeof (SelectorColumn));
424   new_column->parent = selector;
425
426   panarea = hildon_pannable_area_new ();
427
428   g_value_init (&val, G_TYPE_INT);
429   g_value_set_int (&val, HILDON_PANNABLE_AREA_INDICATOR_MODE_HIDE);
430   g_object_set_property (G_OBJECT (panarea), "vindicator-mode", &val);
431
432   g_value_unset (&val);
433   g_value_init (&val, G_TYPE_BOOLEAN);
434   g_value_set_boolean (&val, FALSE);
435   g_object_set_property (G_OBJECT (panarea), "initial-hint", &val);
436
437   gtk_container_add (GTK_CONTAINER (panarea), GTK_WIDGET (tv));
438
439   new_column->model = model;
440   new_column->tree_view = tv;
441   new_column->panarea = panarea;
442
443   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv));
444   gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
445
446   /* select the first item */
447   if (gtk_tree_model_get_iter_first (model, &iter)) {
448     gtk_tree_selection_select_iter (selection, &iter);
449   }
450
451   gtk_widget_grab_focus (GTK_WIDGET (tv));
452
453   /* connect to the changed signal connection */
454   g_signal_connect (G_OBJECT (selection), "changed",
455                     G_CALLBACK (_selection_changed_cb), new_column);
456
457   return new_column;
458 }
459
460 /* ------------------------------ PUBLIC METHODS ---------------------------- */
461
462 /**
463  * hildon_touch_selector_new:
464  *
465  * Creates a new empty #HildonTouchSelector.
466  *
467  * Returns: a new #HildonTouchSelector.
468  **/
469 GtkWidget *
470 hildon_touch_selector_new (void)
471 {
472   return g_object_new (HILDON_TYPE_TOUCH_SELECTOR, NULL);
473 }
474
475 /**
476  * hildon_touch_selector_new_text:
477  *
478  * Creates a #HildonTouchSelector with a single text column that
479  * can be populated conveniently through hildon_touch_selector_append_text(),
480  * hildon_touch_selector_prepend_text(), hildon_touch_selector_insert_text().
481  *
482  * Returns: A new #HildonTouchSelector
483  **/
484 GtkWidget *
485 hildon_touch_selector_new_text (void)
486 {
487   GtkWidget *selector;
488   GtkListStore *store;
489
490   selector = hildon_touch_selector_new ();
491   store = gtk_list_store_new (1, G_TYPE_STRING);
492
493   hildon_touch_selector_append_text_column (HILDON_TOUCH_SELECTOR (selector),
494                                             GTK_TREE_MODEL (store), TRUE);
495
496   return selector;
497 }
498
499 /**
500  * hildon_touch_selector_append_text:
501  * @selector: A #HildonTouchSelector.
502  * @text: a non %NULL text string.
503  *
504  * Appends a new entry in a #HildonTouchSelector created with
505  * hildon_touch_selector_new_text().
506  **/
507 void
508 hildon_touch_selector_append_text (HildonTouchSelector * selector,
509                                    const gchar * text)
510 {
511   GtkTreeIter iter;
512   GtkTreeModel *model;
513
514   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (selector));
515   g_return_if_fail (text != NULL);
516
517   model = hildon_touch_selector_get_model (HILDON_TOUCH_SELECTOR (selector), 0);
518
519   g_return_if_fail (GTK_IS_LIST_STORE (model));
520
521   gtk_list_store_append (GTK_LIST_STORE (model), &iter);
522   gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, text, -1);
523 }
524
525 /**
526  * hildon_touch_selector_prepend_text:
527  * @selector: A #HildonTouchSelector.
528  * @text: a non %NULL text string.
529  *
530  * Prepends a new entry in a #HildonTouchSelector created with
531  * hildon_touch_selector_new_text().
532  **/
533 void
534 hildon_touch_selector_prepend_text (HildonTouchSelector * selector,
535                                     const gchar * text)
536 {
537   GtkTreeIter iter;
538   GtkTreeModel *model;
539
540   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (selector));
541   g_return_if_fail (text != NULL);
542
543   model = hildon_touch_selector_get_model (HILDON_TOUCH_SELECTOR (selector), 0);
544
545   g_return_if_fail (GTK_IS_LIST_STORE (model));
546
547   gtk_list_store_prepend (GTK_LIST_STORE (model), &iter);
548   gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, text, -1);
549 }
550
551 /**
552  * hildon_touch_selector_insert_text:
553  * @selector: a #HildonTouchSelector.
554  * @position: the position to insert @text.
555  * @text: A non %NULL text string.
556  *
557  * Inserts a new entry in particular position of a #HildoTouchSelector created
558  * with hildon_touch_selector_new_text().
559  *
560  **/
561 void
562 hildon_touch_selector_insert_text (HildonTouchSelector * selector,
563                                    gint position, const gchar * text)
564 {
565   GtkTreeIter iter;
566   GtkTreeModel *model;
567
568   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (selector));
569   g_return_if_fail (text != NULL);
570   g_return_if_fail (position >= 0);
571
572   model = hildon_touch_selector_get_model (HILDON_TOUCH_SELECTOR (selector), 0);
573
574   g_return_if_fail (GTK_IS_LIST_STORE (model));
575
576   gtk_list_store_insert (GTK_LIST_STORE (model), &iter, position);
577   gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, text, -1);
578 }
579
580 /**
581  * hildon_touch_selector_append_column
582  * @selector: a #HildonTouchSelector
583  * @model: the #GtkTreeModel with the data of the column
584  * @cell_renderer: The #GtkCellRenderer where to draw each row contents.
585  * @Varargs: a %NULL-terminated pair of attributes and column numbers.
586  *
587  * This functions adds a new column to the widget, whose data will
588  * be obtained from the model. Only widgets added this way should used on
589  * the selection logic, i.e., the print function, the #HildonTouchPicker::changed
590  * signal, etc.
591  *
592  * Contents will be represented in @cell_renderer. You can pass a %NULL-terminated
593  * list of pairs property/value, in the same way you would use
594  * gtk_tree_view_column_set_attributes().
595  *
596  * There is a prerequisite to be considered on models used: text data must
597  * be in the first column.
598  *
599  * This method basically adds a #GtkTreeView to the widget, using the model and
600  * the data received.
601  *
602  * Returns: %TRUE if a new column was added, %FALSE otherwise
603  **/
604
605 gboolean
606 hildon_touch_selector_append_column (HildonTouchSelector * selector,
607                                      GtkTreeModel * model,
608                                      GtkCellRenderer * cell_renderer, ...)
609 {
610   va_list args;
611   SelectorColumn *new_column = NULL;
612
613   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), FALSE);
614   g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
615
616   if (model != NULL) {
617
618     va_start (args, cell_renderer);
619     new_column = _create_new_column (selector, model, cell_renderer, args);
620     va_end (args);
621
622     selector->priv->columns = g_slist_append (selector->priv->columns,
623                                               new_column);
624     gtk_box_pack_start (GTK_BOX (selector->priv->hbox), new_column->panarea, TRUE, TRUE, 6);
625
626     gtk_widget_show_all (new_column->panarea);
627   } else {
628     return FALSE;
629   }
630
631   return TRUE;
632 }
633
634 /**
635  * hildon_touch_selector_append_text_column
636  * @selector: a #HildonTouchSelector
637  * @model: a #GtkTreeModel with data for the column
638  * @center: whether to center the text on the column
639  *
640  * Equivalent to hildon_touch_selector_append_column(), but using a
641  * default text cell renderer. This is the most common use case of the
642  * widget.
643  *
644  * Returns: %TRUE if a new column was added, %FALSE otherwise.
645  **/
646 gboolean
647 hildon_touch_selector_append_text_column (HildonTouchSelector * selector,
648                                           GtkTreeModel * model, gboolean center)
649 {
650   GtkCellRenderer *renderer = NULL;
651   GValue val = { 0, };
652
653   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), FALSE);
654   g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
655
656   if (model != NULL) {
657     renderer = gtk_cell_renderer_text_new ();
658
659     if (center) {
660       g_value_init (&val, G_TYPE_FLOAT);
661       g_value_set_float (&val, 0.5);
662       /* FIXME: center the text, this should be configurable */
663       g_object_set_property (G_OBJECT (renderer), "xalign", &val);
664     }
665
666     return hildon_touch_selector_append_column (selector, model, renderer,
667                                                 "text", 0, NULL);
668   } else {
669     return FALSE;
670   }
671 }
672
673 /**
674  * hildon_touch_selector_remove_column:
675  * @selector: a #HildonTouchSelector
676  * @column: the position of the column to be removed
677  *
678  * Removes a column from @selector.
679  *
680  * Returns: %TRUE if the column was removed, %FALSE otherwise
681  **/
682 gboolean
683 hildon_touch_selector_remove_column (HildonTouchSelector * selector, gint column)
684 {
685   SelectorColumn *current_column = NULL;
686
687   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), FALSE);
688   g_return_val_if_fail (column <
689                         hildon_touch_selector_get_num_columns (selector), FALSE);
690
691   current_column = g_slist_nth_data (selector->priv->columns, column);
692
693   gtk_container_remove (GTK_CONTAINER (selector), current_column->panarea);
694
695   return TRUE;
696 }
697
698 /**
699  * hildon_touch_selector_set_column_attributes:
700  * @selector: a #HildonTouchSelector
701  * @num_column: the number of the column whose attributes we're setting
702  * @cell_renderer: the #GtkCellRendere we're setting the attributes of
703  * @Varargs: A %NULL-terminated list of attributes.
704  *
705  * Sets the attributes for the given column. The attributes must be given
706  * in attribute/column pairs, just like in gtk_tree_view_column_set_attributes().
707  * All existing attributes are removed and replaced with the new ones.
708  *
709  **/
710 void
711 hildon_touch_selector_set_column_attributes (HildonTouchSelector * selector,
712                                              gint num_column,
713                                              GtkCellRenderer * cell_renderer,
714                                              ...)
715 {
716   va_list args;
717   GtkTreeViewColumn *tree_column = NULL;
718   SelectorColumn *current_column = NULL;
719   gchar *attribute = NULL;
720   gint value = 0;
721
722   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (selector));
723   g_return_if_fail (num_column <
724                     hildon_touch_selector_get_num_columns (selector));
725
726   current_column = g_slist_nth_data (selector->priv->columns, num_column);
727
728   tree_column = gtk_tree_view_get_column (current_column->tree_view, 0);
729   gtk_tree_view_remove_column (current_column->tree_view, tree_column);
730
731   tree_column = gtk_tree_view_column_new ();
732   gtk_tree_view_column_pack_start (tree_column, cell_renderer, TRUE);
733
734   va_start (args, cell_renderer);
735   attribute = va_arg (args, gchar *);
736
737   gtk_tree_view_column_clear_attributes (tree_column, cell_renderer);
738
739   while (attribute != NULL) {
740     value = va_arg (args, gint);
741     gtk_tree_view_column_add_attribute (tree_column, cell_renderer,
742                                         attribute, value);
743     attribute = va_arg (args, gchar *);
744   }
745
746   va_end (args);
747
748   gtk_tree_view_append_column (current_column->tree_view, tree_column);
749 }
750
751 #if 0
752 gboolean
753 hildon_touch_selector_insert_column (HildonTouchSelector * selector, gint position)
754 {
755   g_warning ("Un-implemented!");
756
757   return TRUE;
758 }
759 #endif
760
761 /**
762  * hildon_touch_selector_get_num_columns:
763  * @selector: a #HildonTouchSelector
764  *
765  * Gets the number of columns in the #HildonTouchSelector.
766  *
767  * Returns: the number of columns in @selector.
768  **/
769 gint
770 hildon_touch_selector_get_num_columns (HildonTouchSelector * selector)
771 {
772   return g_slist_length (selector->priv->columns);
773 }
774
775 /**
776  * hildon_touch_selector_get_column_selection_mode:
777  * @selector: a #HildonTouchSelector
778  *
779  * Gets the selection mode of @selector.
780  *
781  * Returns: one of #HildonTouchSelectorSelectionMode
782  **/
783 HildonTouchSelectorSelectionMode
784 hildon_touch_selector_get_column_selection_mode (HildonTouchSelector * selector)
785 {
786   HildonTouchSelectorSelectionMode result =
787     HILDON_TOUCH_SELECTOR_SELECTION_MODE_SINGLE;
788   GtkSelectionMode treeview_mode = GTK_SELECTION_SINGLE;
789   SelectorColumn *column = NULL;
790   GtkTreeSelection *selection = NULL;
791
792   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), result);
793   g_return_val_if_fail (hildon_touch_selector_get_num_columns (selector) > 0,
794                         result);
795
796   column = (SelectorColumn *) selector->priv->columns->data;
797
798   selection = gtk_tree_view_get_selection (column->tree_view);
799   treeview_mode = gtk_tree_selection_get_mode (selection);
800
801
802   if (treeview_mode == GTK_SELECTION_MULTIPLE) {
803     result = HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE;
804   } else {
805     result = HILDON_TOUCH_SELECTOR_SELECTION_MODE_SINGLE;
806   }
807
808   return result;
809 }
810
811 /**
812  * hildon_touch_selector_set_column_selection_mode:
813  * @selector: a #HildonTouchSelector
814  * @mode: the #HildonTouchSelectorMode for @selector
815  *
816  * Sets the selection mode for @selector. See #HildonTouchSelectorSelectionMode.
817  **/
818 void
819 hildon_touch_selector_set_column_selection_mode (HildonTouchSelector * selector,
820                                                  HildonTouchSelectorSelectionMode mode)
821 {
822   GtkTreeView *tv = NULL;
823   SelectorColumn *column = NULL;
824   GtkTreeSelection *selection = NULL;
825   GtkSelectionMode treeview_mode = GTK_SELECTION_MULTIPLE;
826   GtkTreeIter iter;
827
828   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (selector));
829   g_return_if_fail (hildon_touch_selector_get_num_columns (selector) > 0);
830
831   column = (SelectorColumn *) (g_slist_nth (selector->priv->columns, 0))->data;
832   tv = column->tree_view;
833
834   if (tv) {
835     switch (mode) {
836     case HILDON_TOUCH_SELECTOR_SELECTION_MODE_SINGLE:
837       treeview_mode = GTK_SELECTION_SINGLE;
838       break;
839     case HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE:
840       treeview_mode = GTK_SELECTION_MULTIPLE;
841       break;
842     }
843
844     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv));
845     gtk_tree_selection_set_mode (selection, treeview_mode);
846
847     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv));
848     gtk_tree_model_get_iter_first (column->model, &iter);
849     gtk_tree_selection_unselect_all (selection);
850     gtk_tree_selection_select_iter (selection, &iter);
851   }
852
853 }
854
855 /**
856  * hildon_touch_selector_set_print_func:
857  * @selector: a #HildonTouchSelector
858  * @func: a #HildonTouchSelectorPrintFunc function
859  *
860  * Sets the function to be used by hildon_touch_selector_get_current_text()
861  * to produce a text representation of the currently selected items in @selector.
862  * The default function will return a concatenation of comma separated items
863  * selected in each column in @selector. Use this to override this method if you
864  * need a particular representation for your application.
865  *
866  **/
867 void
868 hildon_touch_selector_set_print_func (HildonTouchSelector * selector,
869                                       HildonTouchSelectorPrintFunc func)
870 {
871   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (selector));
872
873   selector->priv->print_func = func;
874 }
875
876 /**
877  * hildon_touch_selector_get_print_func:
878  * @selector: a #HildonTouchSelector
879  *
880  * Gets the #HildonTouchSelectorPrintFunc currently used. See
881  * hildon_touch_selector_set_print_func().
882  *
883  * Returns: a #HildonTouchSelectorPrintFunc or %NULL if the default
884  * one is currently used.
885  **/
886 HildonTouchSelectorPrintFunc
887 hildon_touch_selector_get_print_func (HildonTouchSelector * selector)
888 {
889   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), NULL);
890
891   return selector->priv->print_func;
892 }
893
894 /**
895  * hildon_touch_selector_get_active_iter:
896  * @selector: a #HildonTouchSelector
897  * @column: the column number we want to get the element
898  * @iter: #GtkTreeIter currently selected
899  *
900  * Sets @iter to the currently selected node on the nth-column, if selection is set to
901  * %HILDON_TOUCH_SELECTOR_SINGLE. @iter may be %NULL if you just want to test if selection
902  * has any selected items.
903  *
904  * This function will not work if selection is in
905  * %HILDON_TOUCH_SELECTOR_MULTIPLE mode.
906  *
907  * See gtk_tree_selection_get_selected() for more information.
908  *
909  * Returns: %TRUE if @iter was correctly set, %FALSE otherwise
910  **/
911 gboolean
912 hildon_touch_selector_get_active_iter (HildonTouchSelector * selector,
913                                        gint column, GtkTreeIter * iter)
914 {
915   GtkTreeSelection *selection = NULL;
916   SelectorColumn *current_column = NULL;
917
918   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), FALSE);
919   g_return_val_if_fail (hildon_touch_selector_get_column_selection_mode (selector)
920                         == HILDON_TOUCH_SELECTOR_SELECTION_MODE_SINGLE, FALSE);
921   g_return_val_if_fail (column < hildon_touch_selector_get_num_columns (selector),
922                         FALSE);
923
924   current_column = g_slist_nth_data (selector->priv->columns, column);
925
926   selection =
927     gtk_tree_view_get_selection (GTK_TREE_VIEW (current_column->tree_view));
928
929   return gtk_tree_selection_get_selected (selection, NULL, iter);
930 }
931
932 /**
933  * hildon_touch_selector_set_active_iter
934  * @selector: a #HildonTouchSelector
935  * @column:   the column to selects
936  * @iter:     the #GtkTreeIter to be selected
937  * @scroll_to: whether to smoothly scroll to the item
938  *
939  * Sets the currently selected item in the column @column to the one pointed by @iter,
940  * optionally smoothly scrolling to it.
941  *
942  **/
943 void
944 hildon_touch_selector_set_active_iter (HildonTouchSelector * selector,
945                                        gint column, GtkTreeIter * iter,
946                                        gboolean scroll_to)
947 {
948   GtkTreePath *path;
949   GtkTreeModel *model;
950   GdkRectangle rect;
951   SelectorColumn *current_column = NULL;
952   GtkTreeSelection *selection = NULL;
953   gint y;
954
955   g_return_if_fail (HILDON_IS_TOUCH_SELECTOR (selector));
956   g_return_if_fail (column < hildon_touch_selector_get_num_columns (selector));
957
958   current_column = g_slist_nth_data (selector->priv->columns, column);
959
960   selection = gtk_tree_view_get_selection (current_column->tree_view);
961   model = gtk_tree_view_get_model (current_column->tree_view);
962   path = gtk_tree_model_get_path (model, iter);
963
964   gtk_tree_selection_select_iter (selection, iter);
965   gtk_tree_view_set_cursor (GTK_TREE_VIEW (current_column->tree_view), path, NULL, FALSE);
966
967   if (scroll_to) {
968     gtk_tree_view_get_background_area (current_column->tree_view,
969                                        path, NULL, &rect);
970     gtk_tree_view_convert_bin_window_to_tree_coords (current_column->tree_view,
971                                                      0, rect.y, NULL, &y);
972     hildon_pannable_area_scroll_to (HILDON_PANNABLE_AREA (current_column->panarea),
973                                     -1, y);
974   }
975   gtk_tree_path_free (path);
976 }
977
978 /**
979  * hildon_touch_selector_get_selected_rows:
980  * @selector: a #HildonTouchSelector
981  * @column: the position of the column to get the selected rows from
982  *
983  * Creates a list of #GtkTreePath<!-- -->s of all selected rows in a column. Additionally,
984  * if you to plan to modify the model after calling this function, you may
985  * want to convert the returned list into a list of GtkTreeRowReferences. To do this,
986  * you can use gtk_tree_row_reference_new().
987  *
988  * See gtk_tree_selection_get_selected_rows() for more information.
989  *
990  * Returns: A new #GList containing a #GtkTreePath for each selected row in the column @column.
991  *
992  **/
993 GList *
994 hildon_touch_selector_get_selected_rows (HildonTouchSelector * selector,
995                                          gint column)
996 {
997   GList *result = NULL;
998   SelectorColumn *current_column = NULL;
999   GtkTreeSelection *selection = NULL;
1000
1001   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), NULL);
1002   g_return_val_if_fail (column < hildon_touch_selector_get_num_columns (selector),
1003                         NULL);
1004
1005   current_column = g_slist_nth_data (selector->priv->columns, column);
1006   selection = gtk_tree_view_get_selection (current_column->tree_view);
1007
1008   result = gtk_tree_selection_get_selected_rows (selection, NULL);
1009
1010
1011   return result;
1012 }
1013
1014 /**
1015  * hildon_touch_selector_get_model:
1016  * @selector: a #HildonTouchSelector
1017  * @column: the position of the column in @selector
1018  *
1019  * Gets the model of a column of @selector.
1020  *
1021  * Returns: the #GtkTreeModel for the column @column of @selector.
1022  **/
1023 GtkTreeModel *
1024 hildon_touch_selector_get_model (HildonTouchSelector * selector, gint column)
1025 {
1026   SelectorColumn *current_column = NULL;
1027
1028   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), NULL);
1029   g_return_val_if_fail (column < hildon_touch_selector_get_num_columns (selector),
1030                         NULL);
1031
1032   current_column = g_slist_nth_data (selector->priv->columns, column);
1033
1034   return current_column->model;
1035 }
1036
1037 static void
1038 _hildon_touch_selector_set_model (HildonTouchSelector * selector,
1039                                  gint column, GtkTreeModel * model)
1040 {
1041   SelectorColumn *current_column = NULL;
1042   g_print ("this was actually called\n");
1043
1044   current_column =
1045     (SelectorColumn *) g_slist_nth_data (selector->priv->columns, column);
1046
1047   current_column->model = model;
1048   gtk_tree_view_set_model (current_column->tree_view, current_column->model);
1049 }
1050
1051 /**
1052  * hildon_touch_selector_set_model:
1053  * @selector: a #HildonTouchSelector
1054  * @column: the position of the column to set the model to
1055  * @model: a #GtkTreeModel
1056  *
1057  * Sets the #GtkTreeModel for a particular column in @model.
1058  **/
1059 void
1060 hildon_touch_selector_set_model (HildonTouchSelector * selector,
1061                                  gint column, GtkTreeModel * model)
1062 {
1063   g_return_if_fail (HILDON_TOUCH_SELECTOR (selector));
1064   g_return_if_fail (column < hildon_touch_selector_get_num_columns (selector));
1065
1066   HILDON_TOUCH_SELECTOR_GET_CLASS (selector)->set_model (selector, column, model);
1067 }
1068
1069 /**
1070  * hildon_touch_selector_get_current_text:
1071  * @selector: a #HildonTouchSelector
1072  *
1073  * Returns a string representing the currently selected items for
1074  * each column of @selector. See hildon_touch_selector_set_print_func().
1075  *
1076  * Returns: a newly allocated string.
1077  **/
1078 gchar *
1079 hildon_touch_selector_get_current_text (HildonTouchSelector * selector)
1080 {
1081   gchar *result = NULL;
1082   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), NULL);
1083
1084   if (selector->priv->print_func) {
1085     result = (*selector->priv->print_func) (selector);
1086   } else {
1087     result = _default_print_func (selector);
1088   }
1089
1090   return result;
1091 }
1092
1093 static gboolean
1094 _hildon_touch_selector_center_on_selected_items (gpointer data)
1095 {
1096   HildonTouchSelector *selector = NULL;
1097   SelectorColumn *column = NULL;
1098   GSList *iter_column = NULL;
1099   GtkTreeIter iter;
1100   GtkTreePath *path;
1101   GdkRectangle rect;
1102   gint y;
1103   gint i;
1104   HildonTouchSelectorSelectionMode selection_mode;
1105
1106   /* ensure to center on the initial values */
1107   selector = HILDON_TOUCH_SELECTOR (data);
1108
1109   selection_mode = hildon_touch_selector_get_column_selection_mode (selector);
1110
1111   iter_column = selector->priv->columns;
1112   i = 0;
1113   while (iter_column) {
1114     column = (SelectorColumn *) iter_column->data;
1115
1116     if ((i == 0)
1117         && (selection_mode == HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE)) {
1118       break;
1119     }
1120     if (hildon_touch_selector_get_active_iter (selector, i, &iter)) {
1121       path = gtk_tree_model_get_path (column->model, &iter);
1122       gtk_tree_view_get_background_area (GTK_TREE_VIEW
1123                                          (column->tree_view), path, NULL,
1124                                          &rect);
1125
1126       gtk_tree_view_convert_bin_window_to_tree_coords (GTK_TREE_VIEW
1127                                                        (column->tree_view), 0,
1128                                                        rect.y, NULL, &y);
1129
1130       hildon_pannable_area_scroll_to (HILDON_PANNABLE_AREA
1131                                       (column->panarea), -1, y);
1132
1133       gtk_tree_path_free (path);
1134     }
1135     iter_column = iter_column->next;
1136     i++;
1137   }
1138
1139   return FALSE;
1140 }
1141
1142 static gboolean
1143 _hildon_touch_selector_has_multiple_selection (HildonTouchSelector * selector)
1144 {
1145   HildonTouchSelectorSelectionMode mode = HILDON_TOUCH_SELECTOR_SELECTION_MODE_SINGLE;
1146   gint n_columns = 0;
1147
1148   n_columns = hildon_touch_selector_get_num_columns (selector);
1149   mode = hildon_touch_selector_get_column_selection_mode (selector);
1150
1151   return ((n_columns > 1) || (mode == HILDON_TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE));
1152 }
1153
1154 /**
1155  * hildon_touch_selector_has_multiple_selection:
1156  * @selector: A #HildonTouchSelector
1157  *
1158  * Determines whether @selector is complex enough to actually require an
1159  * extra selection step than only picking an item. This is normally %TRUE
1160  * if @selector has multiple columns, multiple selection, or when it is a
1161  * more complex widget, like %HildonTouchSelectorEntry.
1162  *
1163  * This information is useful for widgets containing a %HildonTouchSelector,
1164  * like #HildonPickerDialog, that could need a "Done" button, in case that
1165  * its internal #HildonTouchSelector has multiple columns, for instance.
1166  *
1167  * Returns: %TRUE if @selector requires multiple selection steps.
1168  **/
1169 gboolean
1170 hildon_touch_selector_has_multiple_selection (HildonTouchSelector * selector)
1171 {
1172   g_return_val_if_fail (HILDON_IS_TOUCH_SELECTOR (selector), FALSE);
1173
1174   return HILDON_TOUCH_SELECTOR_GET_CLASS (selector)->has_multiple_selection (selector);
1175 }