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