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