Bug 332497 - Add Edit -> Available Categories
[platform/upstream/evolution-data-server.git] / libedataserverui / e-categories-selector.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include <config.h>
21 #include <glib/gi18n-lib.h>
22 #include "libedataserver/e-categories.h"
23 #include "e-categories-selector.h"
24 #include "e-data-server-ui-marshal.h"
25
26 G_DEFINE_TYPE (
27         ECategoriesSelector,
28         e_categories_selector,
29         GTK_TYPE_TREE_VIEW)
30
31 struct _ECategoriesSelectorPrivate {
32         gboolean checkable;
33         GHashTable *selected_categories;
34
35         gboolean ignore_category_changes;
36 };
37
38 enum {
39         PROP_0,
40         PROP_ITEMS_CHECKABLE
41 };
42
43 enum {
44         CATEGORY_CHECKED,
45         SELECTION_CHANGED,
46         LAST_SIGNAL
47 };
48
49 enum {
50         COLUMN_ACTIVE,
51         COLUMN_ICON,
52         COLUMN_CATEGORY,
53         N_COLUMNS
54 };
55
56 static gint signals[LAST_SIGNAL] = {0};
57
58 static void
59 categories_selector_build_model (ECategoriesSelector *selector)
60 {
61         GtkListStore *store;
62         GList *list, *iter;
63
64         store = gtk_list_store_new (
65                 N_COLUMNS, G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, G_TYPE_STRING);
66
67         gtk_tree_sortable_set_sort_column_id (
68                 GTK_TREE_SORTABLE (store),
69                 COLUMN_CATEGORY, GTK_SORT_ASCENDING);
70
71         list = e_categories_get_list ();
72         for (iter = list; iter != NULL; iter = iter->next) {
73                 const gchar *category_name = iter->data;
74                 const gchar *filename;
75                 GdkPixbuf *pixbuf = NULL;
76                 GtkTreeIter iter;
77                 gboolean active;
78
79                 /* Only add user-visible categories. */
80                 if (!e_categories_is_searchable (category_name))
81                         continue;
82
83                 active = (g_hash_table_lookup (
84                                 selector->priv->selected_categories,
85                                 category_name) != NULL);
86
87                 filename = e_categories_get_icon_file_for (category_name);
88                 if (filename != NULL)
89                         pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
90
91                 gtk_list_store_append (store, &iter);
92
93                 gtk_list_store_set (
94                         store, &iter,
95                         COLUMN_ACTIVE, active,
96                         COLUMN_ICON, pixbuf,
97                         COLUMN_CATEGORY, category_name,
98                         -1);
99
100                 if (pixbuf != NULL)
101                         g_object_unref (pixbuf);
102         }
103
104         gtk_tree_view_set_model (
105                 GTK_TREE_VIEW (selector), GTK_TREE_MODEL (store));
106
107         /* This has to be reset everytime we install a new model */
108         gtk_tree_view_set_search_column (
109                 GTK_TREE_VIEW (selector), COLUMN_CATEGORY);
110
111         g_list_free (list);
112         g_object_unref (store);
113 }
114
115 static void
116 category_toggled_cb (GtkCellRenderer *renderer,
117                      const gchar *path,
118                      ECategoriesSelector *selector)
119 {
120         GtkTreeModel *model;
121         GtkTreePath *tree_path;
122         GtkTreeIter iter;
123
124         model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
125         g_return_if_fail (model);
126
127         tree_path = gtk_tree_path_new_from_string (path);
128         g_return_if_fail (tree_path);
129
130         if (gtk_tree_model_get_iter (model, &iter, tree_path)) {
131                 gchar *category;
132                 gboolean active;
133
134                 gtk_tree_model_get (model, &iter,
135                         COLUMN_ACTIVE, &active,
136                         COLUMN_CATEGORY, &category, -1);
137
138                 gtk_list_store_set (
139                         GTK_LIST_STORE (model), &iter,
140                         COLUMN_ACTIVE, !active, -1);
141
142                 if (active)
143                         g_hash_table_remove (
144                                 selector->priv->selected_categories, category);
145                 else
146                         g_hash_table_insert (
147                                 selector->priv->selected_categories,
148                                 g_strdup (category), g_strdup (category));
149
150                 g_signal_emit (
151                         selector, signals[CATEGORY_CHECKED], 0,
152                         category, !active);
153
154                 g_free (category);
155         }
156
157         gtk_tree_path_free (tree_path);
158 }
159
160 static void
161 categories_selector_listener_cb (gpointer useless_pointer,
162                                  ECategoriesSelector *selector)
163 {
164         if (!selector->priv->ignore_category_changes)
165                 categories_selector_build_model (selector);
166 }
167
168 static gboolean
169 categories_selector_key_press_event (ECategoriesSelector *selector,
170                                      GdkEventKey *event)
171 {
172         if (event->keyval == GDK_KEY_Delete) {
173                 e_categories_selector_delete_selection (selector);
174                 return TRUE;
175         }
176
177         return FALSE;
178 }
179
180 static void
181 categories_selector_selection_changed (GtkTreeSelection *selection,
182                                        ECategoriesSelector *selector)
183 {
184         g_signal_emit (selector, signals[SELECTION_CHANGED], 0, selection);
185 }
186
187 static void
188 categories_selector_get_property (GObject *object,
189                                   guint property_id,
190                                   GValue *value,
191                                   GParamSpec *pspec)
192 {
193         switch (property_id) {
194                 case PROP_ITEMS_CHECKABLE:
195                         g_value_set_boolean (
196                                 value,
197                                 e_categories_selector_get_items_checkable (
198                                 E_CATEGORIES_SELECTOR (object)));
199                         return;
200         }
201
202         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
203 }
204
205 static void
206 categories_selector_set_property (GObject *object,
207                                   guint property_id,
208                                   const GValue *value,
209                                   GParamSpec *pspec)
210 {
211         switch (property_id) {
212                 case PROP_ITEMS_CHECKABLE:
213                         e_categories_selector_set_items_checkable (
214                                 E_CATEGORIES_SELECTOR (object),
215                                 g_value_get_boolean (value));
216                         return;
217         }
218
219         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
220 }
221
222 static void
223 categories_selector_dispose (GObject *object)
224 {
225         ECategoriesSelectorPrivate *priv;
226
227         priv = E_CATEGORIES_SELECTOR (object)->priv;
228
229         if (priv->selected_categories != NULL) {
230                 g_hash_table_destroy (priv->selected_categories);
231                 priv->selected_categories = NULL;
232         }
233
234         /* Chain up to parent's dispose() method.*/
235         G_OBJECT_CLASS (e_categories_selector_parent_class)->dispose (object);
236 }
237
238 static void
239 categories_selector_finalize (GObject *object)
240 {
241         e_categories_unregister_change_listener (
242                 G_CALLBACK (categories_selector_listener_cb), object);
243 }
244
245 static void
246 e_categories_selector_class_init (ECategoriesSelectorClass *class)
247 {
248         GObjectClass *object_class;
249
250         g_type_class_add_private (class, sizeof (ECategoriesSelectorPrivate));
251
252         object_class = G_OBJECT_CLASS (class);
253         object_class->set_property = categories_selector_set_property;
254         object_class->get_property = categories_selector_get_property;
255         object_class->dispose = categories_selector_dispose;
256         object_class->finalize = categories_selector_finalize;
257
258         g_object_class_install_property (
259                 object_class,
260                 PROP_ITEMS_CHECKABLE,
261                 g_param_spec_boolean (
262                         "items-checkable",
263                         NULL,
264                         NULL,
265                         TRUE,
266                         G_PARAM_READWRITE));
267
268         signals[CATEGORY_CHECKED] = g_signal_new (
269                 "category-checked",
270                 G_TYPE_FROM_CLASS (class),
271                 G_SIGNAL_RUN_FIRST,
272                 G_STRUCT_OFFSET (ECategoriesSelectorClass, category_checked),
273                 NULL, NULL,
274                 e_data_server_ui_marshal_VOID__STRING_BOOLEAN,
275                 G_TYPE_NONE, 2,
276                 G_TYPE_STRING,
277                 G_TYPE_BOOLEAN);
278
279         signals[SELECTION_CHANGED] = g_signal_new (
280                 "selection-changed",
281                 G_TYPE_FROM_CLASS (class),
282                 G_SIGNAL_RUN_FIRST,
283                 G_STRUCT_OFFSET (ECategoriesSelectorClass, selection_changed),
284                 NULL, NULL,
285                 g_cclosure_marshal_VOID__OBJECT,
286                 G_TYPE_NONE, 1,
287                 GTK_TYPE_TREE_SELECTION);
288 }
289
290 static void
291 e_categories_selector_init (ECategoriesSelector *selector)
292 {
293         GtkCellRenderer *renderer;
294         GtkTreeViewColumn *column;
295         GtkTreeSelection *selection;
296
297         selector->priv = G_TYPE_INSTANCE_GET_PRIVATE (
298                 selector, E_TYPE_CATEGORIES_SELECTOR,
299                 ECategoriesSelectorPrivate);
300
301         selector->priv->checkable = TRUE;
302         selector->priv->selected_categories = g_hash_table_new_full (
303                 (GHashFunc) g_str_hash,
304                 (GEqualFunc) g_str_equal,
305                 (GDestroyNotify) g_free,
306                 (GDestroyNotify) g_free);
307         selector->priv->ignore_category_changes = FALSE;
308
309         renderer = gtk_cell_renderer_toggle_new ();
310         column = gtk_tree_view_column_new_with_attributes (
311                 "?", renderer, "active", COLUMN_ACTIVE, NULL);
312         gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
313
314         g_signal_connect (
315                 renderer, "toggled",
316                 G_CALLBACK (category_toggled_cb), selector);
317
318         renderer = gtk_cell_renderer_pixbuf_new ();
319         column = gtk_tree_view_column_new_with_attributes (
320                 _("Icon"), renderer, "pixbuf", COLUMN_ICON, NULL);
321         gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
322
323         renderer = gtk_cell_renderer_text_new ();
324         column = gtk_tree_view_column_new_with_attributes (
325                 _("Category"), renderer, "text", COLUMN_CATEGORY, NULL);
326         gtk_tree_view_append_column (GTK_TREE_VIEW (selector), column);
327
328         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
329         g_signal_connect (
330                 selection, "changed",
331                 G_CALLBACK (categories_selector_selection_changed), selector);
332
333         g_signal_connect (
334                 selector, "key-press-event",
335                 G_CALLBACK(categories_selector_key_press_event), NULL);
336
337         e_categories_register_change_listener (
338                 G_CALLBACK (categories_selector_listener_cb), selector);
339
340         categories_selector_build_model (selector);
341 }
342
343 /**
344  * e_categories_selector_new:
345  *
346  * Since: 3.2
347  **/
348 GtkWidget *
349 e_categories_selector_new (void)
350 {
351         return g_object_new (
352                 E_TYPE_CATEGORIES_SELECTOR,
353                 "items-checkable", TRUE, NULL);
354 }
355
356 /**
357  * e_categories_selector_get_items_checkable:
358  *
359  * Since: 3.2
360  **/
361 gboolean
362 e_categories_selector_get_items_checkable (ECategoriesSelector *selector)
363 {
364         g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), TRUE);
365
366         return selector->priv->checkable;
367 }
368
369 /**
370  * e_categories_selector_set_items_checkable:
371  *
372  * Since: 3.2
373  **/
374 void
375 e_categories_selector_set_items_checkable (ECategoriesSelector *selector,
376                                            gboolean checkable)
377 {
378         GtkTreeViewColumn *column;
379
380         g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
381
382         selector->priv->checkable = checkable;
383
384         column = gtk_tree_view_get_column (
385                 GTK_TREE_VIEW (selector), COLUMN_ACTIVE);
386         gtk_tree_view_column_set_visible (column, checkable);
387
388         g_object_notify (G_OBJECT (selector), "items-checkable");
389 }
390
391 /**
392  * e_categories_selector_get_checked:
393  *
394  * Since: 3.2
395  **/
396 const gchar *
397 e_categories_selector_get_checked (ECategoriesSelector *selector)
398 {
399         GString *str = g_string_new ("");
400         GList *list, *category;
401
402         g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), NULL);
403
404         list = g_hash_table_get_values (selector->priv->selected_categories);
405
406         for (category = list; category != NULL; category = category->next) {
407                 if (str->len > 0)
408                         g_string_append_printf (
409                                 str, ",%s", (gchar *) category->data);
410                 else
411                         g_string_append (str, (gchar *) category->data);
412         }
413
414         g_list_free (list);
415
416         return g_string_free (str, FALSE);
417 }
418
419 /**
420  * e_categories_selector_set_checked:
421  *
422  * Since: 3.2
423  **/
424 void
425 e_categories_selector_set_checked (ECategoriesSelector *selector,
426                                    const gchar *categories)
427 {
428         GtkTreeModel *model;
429         GtkTreeIter iter;
430         gchar **arr;
431         gint i;
432
433         g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
434
435         /* Clean up table of selected categories. */
436         g_hash_table_remove_all (selector->priv->selected_categories);
437
438         arr = g_strsplit (categories, ",", 0);
439         if (arr) {
440                 for (i = 0; arr[i] != NULL; i++) {
441                         g_strstrip (arr[i]);
442                         g_hash_table_insert (
443                                 selector->priv->selected_categories,
444                                 g_strdup (arr[i]), g_strdup (arr[i]));
445                 }
446                 g_strfreev (arr);
447         }
448
449         model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
450         if (gtk_tree_model_get_iter_first (model, &iter)) {
451                 do {
452                         gchar *category_name;
453                         gboolean found;
454
455                         gtk_tree_model_get (model, &iter,
456                                 COLUMN_CATEGORY, &category_name,
457                                 -1);
458                         found = (g_hash_table_lookup (
459                                 selector->priv->selected_categories,
460                                 category_name) != NULL);
461                         gtk_list_store_set (
462                                 GTK_LIST_STORE (model), &iter,
463                                 COLUMN_ACTIVE, found, -1);
464
465                         g_free (category_name);
466                 } while (gtk_tree_model_iter_next (model, &iter));
467         }
468 }
469
470 /**
471  * e_categories_selector_delete_selection:
472  *
473  * Since: 3.2
474  **/
475 void
476 e_categories_selector_delete_selection (ECategoriesSelector *selector)
477 {
478         GtkTreeModel *model;
479         GtkTreeSelection *selection;
480         GList *selected, *item;
481
482         g_return_if_fail (E_IS_CATEGORIES_SELECTOR (selector));
483
484         model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
485         g_return_if_fail (model != NULL);
486
487         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
488         selected = gtk_tree_selection_get_selected_rows (selection, &model);
489
490         /* Remove categories in reverse order to avoid invalidating
491          * tree paths as we iterate over the list. Note, the list is
492          * probably already sorted but we sort again just to be safe. */
493         selected = g_list_reverse (g_list_sort (
494                 selected, (GCompareFunc) gtk_tree_path_compare));
495
496         /* Prevent the model from being rebuilt every time we
497            remove a category, since we're already modifying it. */
498         selector->priv->ignore_category_changes = TRUE;
499
500         for (item = selected; item != NULL; item = item->next) {
501                 GtkTreePath *path = item->data;
502                 GtkTreeIter iter;
503                 gchar *category;
504
505                 gtk_tree_model_get_iter (model, &iter, path);
506                 gtk_tree_model_get (model, &iter,
507                         COLUMN_CATEGORY, &category, -1);
508                 gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
509                 e_categories_remove (category);
510                 g_free (category);
511         }
512
513         selector->priv->ignore_category_changes = FALSE;
514
515         /* If we only remove one category, try to select another */
516         if (g_list_length (selected) == 1) {
517                 GtkTreePath *path = selected->data;
518
519                 gtk_tree_selection_select_path (selection, path);
520                 if (!gtk_tree_selection_path_is_selected (selection, path))
521                         if (gtk_tree_path_prev (path))
522                                 gtk_tree_selection_select_path (selection, path);
523         }
524
525         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
526         g_list_free (selected);
527 }
528
529 /**
530  * e_categories_selector_get_selected:
531  *
532  * Since: 3.2
533  **/
534 const gchar *
535 e_categories_selector_get_selected (ECategoriesSelector *selector)
536 {
537         GtkTreeModel *model;
538         GtkTreeSelection *selection;
539         GList *selected, *item;
540         GString *str = g_string_new ("");
541
542         g_return_val_if_fail (E_IS_CATEGORIES_SELECTOR (selector), NULL);
543
544         model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
545         g_return_val_if_fail (model != NULL, NULL);
546
547         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
548         selected = gtk_tree_selection_get_selected_rows (selection, &model);
549
550         for (item = selected; item != NULL; item = item->next) {
551                 GtkTreePath *path = item->data;
552                 GtkTreeIter iter;
553                 gchar *category;
554
555                 gtk_tree_model_get_iter (model, &iter, path);
556                 gtk_tree_model_get (model, &iter,
557                         COLUMN_CATEGORY, &category, -1);
558                 if (str->len == 0)
559                         g_string_assign (str, category);
560                 else
561                         g_string_append_printf (str, ",%s", category);
562
563                 g_free (category);
564         }
565
566         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
567         g_list_free (selected);
568
569         return g_string_free (str, FALSE);
570 }