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