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