1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* e-source-selector.c
3  *
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by
8  * published by the Free Software Foundation; either the
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author: Ettore Perazzoli <ettore@ximian.com>
19  */
20 
21 #include "evolution-config.h"
22 
23 #include <string.h>
24 #include <glib/gi18n-lib.h>
25 
26 #include <libedataserverui/libedataserverui.h>
27 
28 #include "e-source-selector.h"
29 
30 #define E_SOURCE_SELECTOR_GET_PRIVATE(obj) \
31 	(G_TYPE_INSTANCE_GET_PRIVATE \
32 	((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorPrivate))
33 
34 typedef struct _AsyncContext AsyncContext;
35 
36 struct _ESourceSelectorPrivate {
37 	ESourceRegistry *registry;
38 	gulong source_added_handler_id;
39 	gulong source_changed_handler_id;
40 	gulong source_removed_handler_id;
41 	gulong source_enabled_handler_id;
42 	gulong source_disabled_handler_id;
43 
44 	GHashTable *source_index;
45 	gchar *extension_name;
46 
47 	GtkTreeRowReference *saved_primary_selection;
48 
49 	/* ESource -> GSource */
50 	GHashTable *pending_writes;
51 	GMainContext *main_context;
52 
53 	gboolean toggled_last;
54 	gboolean show_colors;
55 	gboolean show_icons;
56 	gboolean show_toggles;
57 
58 	GtkCellRenderer *busy_renderer;
59 	guint n_busy_sources;
60 	gulong update_busy_renderer_id;
61 
62 	GHashTable *hidden_groups;
63 	GSList *groups_order;
64 };
65 
66 struct _AsyncContext {
67 	ESourceSelector *selector;
68 	ESource *source;
69 };
70 
71 enum {
72 	PROP_0,
73 	PROP_EXTENSION_NAME,
74 	PROP_PRIMARY_SELECTION,
75 	PROP_REGISTRY,
76 	PROP_SHOW_COLORS,
77 	PROP_SHOW_ICONS,
78 	PROP_SHOW_TOGGLES
79 };
80 
81 enum {
82 	SELECTION_CHANGED,
83 	PRIMARY_SELECTION_CHANGED,
84 	POPUP_EVENT,
85 	DATA_DROPPED,
86 	SOURCE_SELECTED,
87 	SOURCE_UNSELECTED,
88 	FILTER_SOURCE,
89 	NUM_SIGNALS
90 };
91 
92 enum {
93 	COLUMN_NAME,
94 	COLUMN_COLOR,
95 	COLUMN_ACTIVE,
96 	COLUMN_ICON_NAME,
97 	COLUMN_SHOW_COLOR,
98 	COLUMN_SHOW_ICONS,
99 	COLUMN_SHOW_TOGGLE,
100 	COLUMN_WEIGHT,
101 	COLUMN_SOURCE,
102 	COLUMN_TOOLTIP,
103 	COLUMN_IS_BUSY,
104 	COLUMN_CONNECTION_STATUS,
105 	COLUMN_SORT_ORDER,
106 	NUM_COLUMNS
107 };
108 
109 static guint signals[NUM_SIGNALS];
110 
111 G_DEFINE_TYPE (ESourceSelector, e_source_selector, GTK_TYPE_TREE_VIEW)
112 
113 /* ESafeToggleRenderer does not emit 'toggled' signal
114  * on 'activate' when mouse is not over the toggle. */
115 
116 typedef GtkCellRendererToggle ECellRendererSafeToggle;
117 typedef GtkCellRendererToggleClass ECellRendererSafeToggleClass;
118 
119 /* Forward Declarations */
120 GType		e_cell_renderer_safe_toggle_get_type
121 						(void) G_GNUC_CONST;
122 static void	selection_changed_callback	(GtkTreeSelection *selection,
123 						 ESourceSelector *selector);
124 
G_DEFINE_TYPE(ECellRendererSafeToggle,e_cell_renderer_safe_toggle,GTK_TYPE_CELL_RENDERER_TOGGLE)125 G_DEFINE_TYPE (
126 	ECellRendererSafeToggle,
127 	e_cell_renderer_safe_toggle,
128 	GTK_TYPE_CELL_RENDERER_TOGGLE)
129 
130 static gboolean
131 safe_toggle_activate (GtkCellRenderer *cell,
132                       GdkEvent *event,
133                       GtkWidget *widget,
134                       const gchar *path,
135                       const GdkRectangle *background_area,
136                       const GdkRectangle *cell_area,
137                       GtkCellRendererState flags)
138 {
139 	gboolean point_in_cell_area = TRUE;
140 
141 	if (event && event->type == GDK_BUTTON_PRESS && cell_area != NULL) {
142 		cairo_region_t *region;
143 
144 		region = cairo_region_create_rectangle (cell_area);
145 		point_in_cell_area = cairo_region_contains_point (
146 			region, event->button.x, event->button.y);
147 		cairo_region_destroy (region);
148 	}
149 
150 	if (!point_in_cell_area)
151 		return FALSE;
152 
153 	return GTK_CELL_RENDERER_CLASS (
154 		e_cell_renderer_safe_toggle_parent_class)->activate (
155 		cell, event, widget, path, background_area, cell_area, flags);
156 }
157 
158 static void
e_cell_renderer_safe_toggle_class_init(ECellRendererSafeToggleClass * class)159 e_cell_renderer_safe_toggle_class_init (ECellRendererSafeToggleClass *class)
160 {
161 	GtkCellRendererClass *cell_renderer_class;
162 
163 	cell_renderer_class = GTK_CELL_RENDERER_CLASS (class);
164 	cell_renderer_class->activate = safe_toggle_activate;
165 }
166 
167 static void
e_cell_renderer_safe_toggle_init(ECellRendererSafeToggle * obj)168 e_cell_renderer_safe_toggle_init (ECellRendererSafeToggle *obj)
169 {
170 }
171 
172 static GtkCellRenderer *
e_cell_renderer_safe_toggle_new(void)173 e_cell_renderer_safe_toggle_new (void)
174 {
175 	return g_object_new (e_cell_renderer_safe_toggle_get_type (), NULL);
176 }
177 
178 static gboolean
source_selector_pulse_busy_renderer_cb(gpointer user_data)179 source_selector_pulse_busy_renderer_cb (gpointer user_data)
180 {
181 	ESourceSelector *selector = user_data;
182 	GObject *busy_renderer;
183 	guint pulse;
184 	GHashTableIter iter;
185 	gpointer key, value;
186 
187 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
188 
189 	if (!selector->priv->busy_renderer)
190 		return FALSE;
191 
192 	busy_renderer = G_OBJECT (selector->priv->busy_renderer);
193 
194 	g_object_get (busy_renderer, "pulse", &pulse, NULL);
195 
196 	pulse++;
197 
198 	g_object_set (busy_renderer, "pulse", pulse, NULL);
199 
200 	g_hash_table_iter_init (&iter, selector->priv->source_index);
201 	while (g_hash_table_iter_next (&iter, &key, &value)) {
202 		GtkTreeRowReference *reference = value;
203 		GtkTreeModel *model;
204 		GtkTreePath *path;
205 		GtkTreeIter tree_iter;
206 
207 		if (reference && gtk_tree_row_reference_valid (reference)) {
208 			gboolean is_busy = FALSE;
209 
210 			model = gtk_tree_row_reference_get_model (reference);
211 			path = gtk_tree_row_reference_get_path (reference);
212 			gtk_tree_model_get_iter (model, &tree_iter, path);
213 
214 			gtk_tree_model_get (
215 				model, &tree_iter,
216 				COLUMN_IS_BUSY, &is_busy,
217 				-1);
218 
219 			if (is_busy)
220 				gtk_tree_model_row_changed (model, path, &tree_iter);
221 
222 			gtk_tree_path_free (path);
223 		}
224 	}
225 
226 	return TRUE;
227 }
228 
229 static void
source_selector_inc_busy_sources(ESourceSelector * selector)230 source_selector_inc_busy_sources (ESourceSelector *selector)
231 {
232 	selector->priv->n_busy_sources++;
233 
234 	if (selector->priv->busy_renderer && !selector->priv->update_busy_renderer_id)
235 		selector->priv->update_busy_renderer_id =
236 			e_named_timeout_add (123, source_selector_pulse_busy_renderer_cb, selector);
237 }
238 
239 static void
source_selector_dec_busy_sources(ESourceSelector * selector)240 source_selector_dec_busy_sources (ESourceSelector *selector)
241 {
242 	g_return_if_fail (selector->priv->n_busy_sources > 0);
243 
244 	selector->priv->n_busy_sources--;
245 
246 	if (selector->priv->n_busy_sources == 0 && selector->priv->update_busy_renderer_id) {
247 		g_source_remove (selector->priv->update_busy_renderer_id);
248 		selector->priv->update_busy_renderer_id = 0;
249 	}
250 }
251 
252 static void
clear_saved_primary_selection(ESourceSelector * selector)253 clear_saved_primary_selection (ESourceSelector *selector)
254 {
255 	gtk_tree_row_reference_free (selector->priv->saved_primary_selection);
256 	selector->priv->saved_primary_selection = NULL;
257 }
258 
259 static void
async_context_free(AsyncContext * async_context)260 async_context_free (AsyncContext *async_context)
261 {
262 	if (async_context->selector != NULL)
263 		g_object_unref (async_context->selector);
264 
265 	if (async_context->source != NULL)
266 		g_object_unref (async_context->source);
267 
268 	g_slice_free (AsyncContext, async_context);
269 }
270 
271 static void
pending_writes_destroy_source(GSource * source)272 pending_writes_destroy_source (GSource *source)
273 {
274 	g_source_destroy (source);
275 	g_source_unref (source);
276 }
277 
278 static void
source_selector_write_done_cb(GObject * source_object,GAsyncResult * result,gpointer user_data)279 source_selector_write_done_cb (GObject *source_object,
280                                GAsyncResult *result,
281                                gpointer user_data)
282 {
283 	ESource *source;
284 	ESourceSelector *selector;
285 	GError *error = NULL;
286 
287 	source = E_SOURCE (source_object);
288 	selector = E_SOURCE_SELECTOR (user_data);
289 
290 	e_source_write_finish (source, result, &error);
291 
292 	/* FIXME Display the error in the selector somehow? */
293 	if (error != NULL) {
294 		g_warning ("%s: %s", G_STRFUNC, error->message);
295 		g_error_free (error);
296 	}
297 
298 	g_object_unref (selector);
299 }
300 
301 static gboolean
source_selector_write_idle_cb(gpointer user_data)302 source_selector_write_idle_cb (gpointer user_data)
303 {
304 	AsyncContext *async_context = user_data;
305 	GHashTable *pending_writes;
306 
307 	/* XXX This operation is not cancellable. */
308 	e_source_write (
309 		async_context->source, NULL,
310 		source_selector_write_done_cb,
311 		g_object_ref (async_context->selector));
312 
313 	pending_writes = async_context->selector->priv->pending_writes;
314 	g_hash_table_remove (pending_writes, async_context->source);
315 
316 	return FALSE;
317 }
318 
319 static void
source_selector_cancel_write(ESourceSelector * selector,ESource * source)320 source_selector_cancel_write (ESourceSelector *selector,
321                               ESource *source)
322 {
323 	GHashTable *pending_writes;
324 
325 	/* Cancel any pending writes for this ESource so as not
326 	 * to overwrite whatever change we're being notified of. */
327 	pending_writes = selector->priv->pending_writes;
328 	g_hash_table_remove (pending_writes, source);
329 }
330 
331 static const gchar *
source_selector_get_icon_name(ESourceSelector * selector,ESource * source)332 source_selector_get_icon_name (ESourceSelector *selector,
333                                ESource *source)
334 {
335 	const gchar *extension_name;
336 	const gchar *icon_name = NULL;
337 
338 	/* XXX These are the same icons used in EShellView subclasses.
339 	 *     We should really centralize these icon names somewhere. */
340 
341 	extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
342 	if (e_source_has_extension (source, extension_name))
343 		icon_name = "x-office-address-book";
344 
345 	extension_name = E_SOURCE_EXTENSION_CALENDAR;
346 	if (e_source_has_extension (source, extension_name))
347 		icon_name = "x-office-calendar";
348 
349 	extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
350 	if (e_source_has_extension (source, extension_name))
351 		icon_name = "evolution-mail";
352 
353 	extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
354 	if (e_source_has_extension (source, extension_name))
355 		icon_name = "mail-send";
356 
357 	extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
358 	if (e_source_has_extension (source, extension_name))
359 		icon_name = "evolution-memos";
360 
361 	extension_name = E_SOURCE_EXTENSION_TASK_LIST;
362 	if (e_source_has_extension (source, extension_name))
363 		icon_name = "evolution-tasks";
364 
365 	return icon_name;
366 }
367 
368 static gboolean
source_selector_source_is_enabled_and_selected(ESource * source,const gchar * extension_name)369 source_selector_source_is_enabled_and_selected (ESource *source,
370 						const gchar *extension_name)
371 {
372 	gpointer extension;
373 
374 	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
375 
376 	if (!extension_name ||
377 	    !e_source_get_enabled (source))
378 		return e_source_get_enabled (source);
379 
380 	if (!e_source_has_extension (source, extension_name))
381 		return FALSE;
382 
383 	extension = e_source_get_extension (source, extension_name);
384 	if (!E_IS_SOURCE_SELECTABLE (extension))
385 		return TRUE;
386 
387 	return e_source_selectable_get_selected (extension);
388 }
389 
390 typedef struct {
391 	const gchar *extension_name;
392 	gboolean show_toggles;
393 	gboolean any_selected;
394 } LookupSelectedData;
395 
396 static gboolean
source_selector_lookup_selected_cb(GNode * node,gpointer user_data)397 source_selector_lookup_selected_cb (GNode *node,
398 				    gpointer user_data)
399 {
400 	LookupSelectedData *data = user_data;
401 	ESource *source;
402 
403 	g_return_val_if_fail (data != NULL, TRUE);
404 	g_return_val_if_fail (data->extension_name != NULL, TRUE);
405 
406 	source = node->data;
407 	if (!E_IS_SOURCE (source))
408 		return TRUE;
409 
410 	data->any_selected = data->show_toggles && source_selector_source_is_enabled_and_selected (source, data->extension_name);
411 
412 	return data->any_selected;
413 }
414 
415 static gboolean
source_selector_node_is_hidden(ESourceSelector * selector,GNode * main_node)416 source_selector_node_is_hidden (ESourceSelector *selector,
417 				GNode *main_node)
418 {
419 	GNode *node;
420 	ESource *source;
421 	const gchar *extension_name;
422 	LookupSelectedData data;
423 	gboolean hidden;
424 
425 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
426 	g_return_val_if_fail (main_node != NULL, FALSE);
427 
428 	if (G_NODE_IS_ROOT (main_node))
429 		return FALSE;
430 
431 	extension_name = e_source_selector_get_extension_name (selector);
432 	data.show_toggles = e_source_selector_get_show_toggles (selector);
433 	hidden = FALSE;
434 
435 	/* Check the path to the root, any is hidden, this one can be also hidden */
436 	node = main_node;
437 	while (node) {
438 		gboolean hidden_by_filter = FALSE;
439 
440 		source = node->data;
441 
442 		if (!source || G_NODE_IS_ROOT (node))
443 			break;
444 
445 		g_signal_emit (selector, signals[FILTER_SOURCE], 0, source, &hidden_by_filter);
446 
447 		if (hidden_by_filter)
448 			return TRUE;
449 
450 		if (data.show_toggles && source_selector_source_is_enabled_and_selected (source, extension_name)) {
451 			hidden = FALSE;
452 			break;
453 		}
454 
455 		hidden = hidden || g_hash_table_contains (selector->priv->hidden_groups, e_source_get_uid (source));
456 
457 		node = node->parent;
458 	}
459 
460 	if (!hidden)
461 		return FALSE;
462 
463 	/* If any source in this subtree/group is enabled and selected,
464 	   then the group cannot be hidden */
465 
466 	node = main_node;
467 	if (node->parent && !G_NODE_IS_ROOT (node->parent)) {
468 		node = node->parent;
469 	}
470 
471 	data.extension_name = extension_name;
472 	data.any_selected = FALSE;
473 
474 	g_node_traverse (node, G_IN_ORDER, G_TRAVERSE_ALL, -1, source_selector_lookup_selected_cb, &data);
475 
476 	return !data.any_selected;
477 }
478 
479 static gboolean
source_selector_traverse(GNode * node,ESourceSelector * selector)480 source_selector_traverse (GNode *node,
481                           ESourceSelector *selector)
482 {
483 	ESource *source;
484 	GHashTable *source_index;
485 	GtkTreeRowReference *reference = NULL;
486 	GtkTreeModel *model;
487 	GtkTreePath *path;
488 	GtkTreeIter iter;
489 
490 	/* Skip the root node. */
491 	if (G_NODE_IS_ROOT (node))
492 		return FALSE;
493 
494 	if (source_selector_node_is_hidden (selector, node))
495 		return FALSE;
496 
497 	source_index = selector->priv->source_index;
498 
499 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
500 
501 	if (node->parent != NULL && node->parent->data != NULL)
502 		reference = g_hash_table_lookup (
503 			source_index, node->parent->data);
504 
505 	if (gtk_tree_row_reference_valid (reference)) {
506 		GtkTreeIter parent;
507 
508 		path = gtk_tree_row_reference_get_path (reference);
509 		gtk_tree_model_get_iter (model, &parent, path);
510 		gtk_tree_path_free (path);
511 
512 		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
513 	} else
514 		gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
515 
516 	source = E_SOURCE (node->data);
517 
518 	path = gtk_tree_model_get_path (model, &iter);
519 	reference = gtk_tree_row_reference_new (model, path);
520 	g_hash_table_insert (source_index, g_object_ref (source), reference);
521 	gtk_tree_path_free (path);
522 
523 	e_source_selector_update_row (selector, source);
524 
525 	return FALSE;
526 }
527 
528 static void
source_selector_save_expanded(GtkTreeView * tree_view,GtkTreePath * path,GQueue * queue)529 source_selector_save_expanded (GtkTreeView *tree_view,
530                                GtkTreePath *path,
531                                GQueue *queue)
532 {
533 	GtkTreeModel *model;
534 	GtkTreeIter iter;
535 	ESource *source;
536 
537 	model = gtk_tree_view_get_model (tree_view);
538 	gtk_tree_model_get_iter (model, &iter, path);
539 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
540 	g_queue_push_tail (queue, source);
541 }
542 
543 typedef struct _SavedStatus
544 {
545 	gboolean is_busy;
546 	gchar *tooltip;
547 } SavedStatusData;
548 
549 static void
saved_status_data_free(gpointer ptr)550 saved_status_data_free (gpointer ptr)
551 {
552 	SavedStatusData *data = ptr;
553 
554 	if (data) {
555 		g_free (data->tooltip);
556 		g_slice_free (SavedStatusData, data);
557 	}
558 }
559 
560 static GHashTable *
source_selector_save_sources_status(ESourceSelector * selector)561 source_selector_save_sources_status (ESourceSelector *selector)
562 {
563 	GHashTable *status;
564 	GHashTableIter iter;
565 	gpointer key, value;
566 
567 	status = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, saved_status_data_free);
568 
569 	g_hash_table_iter_init (&iter, selector->priv->source_index);
570 	while (g_hash_table_iter_next (&iter, &key, &value)) {
571 		ESource *source = key;
572 		GtkTreeRowReference *reference = value;
573 		GtkTreeModel *model;
574 		GtkTreePath *path;
575 		GtkTreeIter tree_iter;
576 
577 		if (reference && gtk_tree_row_reference_valid (reference)) {
578 			SavedStatusData *data;
579 
580 			model = gtk_tree_row_reference_get_model (reference);
581 			path = gtk_tree_row_reference_get_path (reference);
582 			gtk_tree_model_get_iter (model, &tree_iter, path);
583 
584 			data = g_slice_new0 (SavedStatusData);
585 
586 			gtk_tree_model_get (
587 				model, &tree_iter,
588 				COLUMN_IS_BUSY, &data->is_busy,
589 				COLUMN_TOOLTIP, &data->tooltip,
590 				-1);
591 
592 			if (data->is_busy)
593 				source_selector_dec_busy_sources (selector);
594 
595 			gtk_tree_path_free (path);
596 
597 			g_hash_table_insert (status, g_strdup (e_source_get_uid (source)), data);
598 		}
599 	}
600 
601 	return status;
602 }
603 
604 static void
source_selector_load_sources_status(ESourceSelector * selector,GHashTable * status)605 source_selector_load_sources_status (ESourceSelector *selector,
606 				     GHashTable *status)
607 {
608 	GHashTableIter iter;
609 	gpointer key, value;
610 
611 	g_hash_table_iter_init (&iter, selector->priv->source_index);
612 	while (g_hash_table_iter_next (&iter, &key, &value)) {
613 		ESource *source = key;
614 		GtkTreeRowReference *reference = value;
615 		GtkTreeModel *model;
616 		GtkTreePath *path;
617 		GtkTreeIter tree_iter;
618 
619 		if (reference && gtk_tree_row_reference_valid (reference)) {
620 			SavedStatusData *data;
621 
622 			model = gtk_tree_row_reference_get_model (reference);
623 			path = gtk_tree_row_reference_get_path (reference);
624 			gtk_tree_model_get_iter (model, &tree_iter, path);
625 
626 			gtk_tree_path_free (path);
627 
628 			data = g_hash_table_lookup (status, e_source_get_uid (source));
629 			if (!data)
630 				continue;
631 
632 			gtk_tree_store_set (
633 				GTK_TREE_STORE (model), &tree_iter,
634 				COLUMN_IS_BUSY, data->is_busy,
635 				COLUMN_TOOLTIP, data->tooltip,
636 				-1);
637 
638 			if (data->is_busy)
639 				source_selector_inc_busy_sources (selector);
640 		}
641 	}
642 }
643 
644 static void
source_selector_sort_groups(ESourceSelector * selector,GNode * root)645 source_selector_sort_groups (ESourceSelector *selector,
646 			     GNode *root)
647 {
648 	GHashTable *groups; /* gchar *uid, GUINT index into node_sources */
649 	GPtrArray *node_sources; /* GNode * as stored in the root first sub-level */
650 	ESource *source;
651 	GNode *node;
652 	GSList *link;
653 	guint ii;
654 
655 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
656 	g_return_if_fail (G_NODE_IS_ROOT (root));
657 
658 	if (!selector->priv->groups_order ||
659 	    !g_node_n_children (root))
660 		return;
661 
662 	groups = g_hash_table_new (g_str_hash, g_str_equal);
663 	node_sources = g_ptr_array_sized_new (g_node_n_children (root));
664 
665 	node = g_node_first_child (root);
666 	while (node) {
667 		GNode *next_node = g_node_next_sibling (node);
668 
669 		source = node->data;
670 
671 		if (source) {
672 			g_node_unlink (node);
673 
674 			g_hash_table_insert (groups, (gpointer) e_source_get_uid (source), GUINT_TO_POINTER (node_sources->len));
675 			g_ptr_array_add (node_sources, node);
676 		}
677 
678 		node = next_node;
679 	}
680 
681 	/* First add known nodes as defined by the user... */
682 	for (link = selector->priv->groups_order; link; link = g_slist_next (link)) {
683 		const gchar *uid = link->data;
684 
685 		if (!uid || !g_hash_table_contains (groups, uid))
686 			continue;
687 
688 		ii = GPOINTER_TO_UINT (g_hash_table_lookup (groups, uid));
689 		g_warn_if_fail (ii < node_sources->len);
690 
691 		node = node_sources->pdata[ii];
692 		node_sources->pdata[ii] = NULL;
693 
694 		if (node)
695 			g_node_append (root, node);
696 	}
697 
698 	/* ... then add all unknown (new) sources in the order
699 	   as they were in the passed-in tree */
700 	for (ii = 0; ii < node_sources->len; ii++) {
701 		node = node_sources->pdata[ii];
702 
703 		if (node)
704 			g_node_append (root, node);
705 	}
706 
707 	g_ptr_array_unref (node_sources);
708 	g_hash_table_destroy (groups);
709 }
710 
711 static void
source_selector_build_model(ESourceSelector * selector)712 source_selector_build_model (ESourceSelector *selector)
713 {
714 	ESourceRegistry *registry;
715 	GQueue queue = G_QUEUE_INIT;
716 	GHashTable *source_index;
717 	GtkTreeView *tree_view;
718 	GtkTreeSelection *selection;
719 	GtkTreeModel *model;
720 	GHashTable *saved_status;
721 	ESource *selected;
722 	const gchar *extension_name;
723 	GNode *root;
724 
725 	tree_view = GTK_TREE_VIEW (selector);
726 
727 	registry = e_source_selector_get_registry (selector);
728 	extension_name = e_source_selector_get_extension_name (selector);
729 
730 	/* Make sure we have what we need to build the model, since
731 	 * this can get called early in the initialization phase. */
732 	if (registry == NULL || extension_name == NULL)
733 		return;
734 
735 	source_index = selector->priv->source_index;
736 	selected = e_source_selector_ref_primary_selection (selector);
737 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
738 	saved_status = source_selector_save_sources_status (selector);
739 
740 	/* Signal is blocked to avoid "primary-selection-changed" signal
741 	 * on model clear. */
742 	g_signal_handlers_block_matched (
743 		selection, G_SIGNAL_MATCH_FUNC,
744 		0, 0, NULL, selection_changed_callback, NULL);
745 
746 	/* Save expanded sources to restore later. */
747 	gtk_tree_view_map_expanded_rows (
748 		tree_view, (GtkTreeViewMappingFunc)
749 		source_selector_save_expanded, &queue);
750 
751 	model = gtk_tree_view_get_model (tree_view);
752 	gtk_tree_store_clear (GTK_TREE_STORE (model));
753 
754 	g_hash_table_remove_all (source_index);
755 
756 	root = e_source_registry_build_display_tree (registry, extension_name);
757 
758 	source_selector_sort_groups (selector, root);
759 
760 	g_node_traverse (
761 		root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
762 		(GNodeTraverseFunc) source_selector_traverse,
763 		selector);
764 
765 	e_source_registry_free_display_tree (root);
766 
767 	/* Restore previously expanded sources. */
768 	while (!g_queue_is_empty (&queue)) {
769 		GtkTreeRowReference *reference;
770 		ESource *source;
771 
772 		source = g_queue_pop_head (&queue);
773 		reference = g_hash_table_lookup (source_index, source);
774 
775 		if (gtk_tree_row_reference_valid (reference)) {
776 			GtkTreePath *path;
777 
778 			path = gtk_tree_row_reference_get_path (reference);
779 			gtk_tree_view_expand_to_path (tree_view, path);
780 			gtk_tree_path_free (path);
781 		}
782 
783 		g_object_unref (source);
784 	}
785 
786 	/* Restore the primary selection. */
787 	if (selected != NULL) {
788 		e_source_selector_set_primary_selection (selector, selected);
789 		g_object_unref (selected);
790 	}
791 
792 	/* If the first succeeded, then there is no selection change,
793 	 * thus no need for notification; notify about the change in
794 	 * any other cases. */
795 	g_signal_handlers_unblock_matched (
796 		selection, G_SIGNAL_MATCH_FUNC,
797 		0, 0, NULL, selection_changed_callback, NULL);
798 
799 	/* Make sure we have a primary selection.  If not, pick one. */
800 	selected = e_source_selector_ref_primary_selection (selector);
801 	if (selected == NULL) {
802 		selected = e_source_registry_ref_default_for_extension_name (
803 			registry, extension_name);
804 	}
805 	if (selected != NULL) {
806 		e_source_selector_set_primary_selection (selector, selected);
807 		g_object_unref (selected);
808 	}
809 
810 	source_selector_load_sources_status (selector, saved_status);
811 	g_hash_table_destroy (saved_status);
812 }
813 
814 static void
source_selector_expand_to_source(ESourceSelector * selector,ESource * source)815 source_selector_expand_to_source (ESourceSelector *selector,
816                                   ESource *source)
817 {
818 	GHashTable *source_index;
819 	GtkTreeRowReference *reference;
820 	GtkTreePath *path;
821 
822 	source_index = selector->priv->source_index;
823 	reference = g_hash_table_lookup (source_index, source);
824 
825 	/* If the ESource is not in our tree model then return silently. */
826 	if (reference == NULL)
827 		return;
828 
829 	/* If we do have a row reference, it should be valid. */
830 	g_return_if_fail (gtk_tree_row_reference_valid (reference));
831 
832 	/* Expand the tree view to the path containing the ESource */
833 	path = gtk_tree_row_reference_get_path (reference);
834 	gtk_tree_view_expand_to_path (GTK_TREE_VIEW (selector), path);
835 	gtk_tree_path_free (path);
836 }
837 
838 static void
source_selector_source_added_cb(ESourceRegistry * registry,ESource * source,ESourceSelector * selector)839 source_selector_source_added_cb (ESourceRegistry *registry,
840                                  ESource *source,
841                                  ESourceSelector *selector)
842 {
843 	const gchar *extension_name;
844 
845 	extension_name = e_source_selector_get_extension_name (selector);
846 
847 	if (extension_name == NULL)
848 		return;
849 
850 	if (!e_source_has_extension (source, extension_name))
851 		return;
852 
853 	source_selector_build_model (selector);
854 
855 	source_selector_expand_to_source (selector, source);
856 
857 	if (e_source_selector_source_is_selected (selector, source))
858 		g_signal_emit (selector, signals[SOURCE_SELECTED], 0, source);
859 }
860 
861 static void
source_selector_source_changed_cb(ESourceRegistry * registry,ESource * source,ESourceSelector * selector)862 source_selector_source_changed_cb (ESourceRegistry *registry,
863                                    ESource *source,
864                                    ESourceSelector *selector)
865 {
866 	const gchar *extension_name;
867 
868 	extension_name = e_source_selector_get_extension_name (selector);
869 
870 	if (extension_name == NULL)
871 		return;
872 
873 	if (!e_source_has_extension (source, extension_name))
874 		return;
875 
876 	source_selector_cancel_write (selector, source);
877 
878 	e_source_selector_update_row (selector, source);
879 }
880 
881 static void
source_selector_source_removed_cb(ESourceRegistry * registry,ESource * source,ESourceSelector * selector)882 source_selector_source_removed_cb (ESourceRegistry *registry,
883                                    ESource *source,
884                                    ESourceSelector *selector)
885 {
886 	const gchar *extension_name;
887 
888 	extension_name = e_source_selector_get_extension_name (selector);
889 
890 	if (extension_name == NULL)
891 		return;
892 
893 	if (!e_source_has_extension (source, extension_name))
894 		return;
895 
896 	if (e_source_selector_get_source_is_busy (selector, source))
897 		source_selector_dec_busy_sources (selector);
898 
899 	/* Always notify about deselect, regardless whether it is really selected,
900 	   because listeners won't always know the source is gone otherwise. */
901 	g_signal_emit (selector, signals[SOURCE_UNSELECTED], 0, source);
902 
903 	source_selector_build_model (selector);
904 }
905 
906 static void
source_selector_source_enabled_cb(ESourceRegistry * registry,ESource * source,ESourceSelector * selector)907 source_selector_source_enabled_cb (ESourceRegistry *registry,
908                                    ESource *source,
909                                    ESourceSelector *selector)
910 {
911 	const gchar *extension_name;
912 
913 	extension_name = e_source_selector_get_extension_name (selector);
914 
915 	if (extension_name == NULL)
916 		return;
917 
918 	if (!e_source_has_extension (source, extension_name))
919 		return;
920 
921 	source_selector_build_model (selector);
922 
923 	source_selector_expand_to_source (selector, source);
924 
925 	if (e_source_selector_source_is_selected (selector, source))
926 		g_signal_emit (selector, signals[SOURCE_SELECTED], 0, source);
927 }
928 
929 static void
source_selector_source_disabled_cb(ESourceRegistry * registry,ESource * source,ESourceSelector * selector)930 source_selector_source_disabled_cb (ESourceRegistry *registry,
931                                     ESource *source,
932                                     ESourceSelector *selector)
933 {
934 	const gchar *extension_name;
935 
936 	extension_name = e_source_selector_get_extension_name (selector);
937 
938 	if (extension_name == NULL)
939 		return;
940 
941 	if (!e_source_has_extension (source, extension_name))
942 		return;
943 
944 	if (e_source_selector_get_source_is_busy (selector, source))
945 		source_selector_dec_busy_sources (selector);
946 
947 	/* Always notify about deselect, regardless whether it is really selected,
948 	   because listeners won't always know the source is gone otherwise. */
949 	g_signal_emit (selector, signals[SOURCE_UNSELECTED], 0, source);
950 
951 	source_selector_build_model (selector);
952 }
953 
954 static gboolean
same_source_name_exists(ESourceSelector * selector,const gchar * display_name)955 same_source_name_exists (ESourceSelector *selector,
956                          const gchar *display_name)
957 {
958 	GHashTable *source_index;
959 	GHashTableIter iter;
960 	gpointer key;
961 
962 	source_index = selector->priv->source_index;
963 	g_hash_table_iter_init (&iter, source_index);
964 
965 	while (g_hash_table_iter_next (&iter, &key, NULL)) {
966 		ESource *source = E_SOURCE (key);
967 		const gchar *source_name;
968 
969 		source_name = e_source_get_display_name (source);
970 		if (g_strcmp0 (display_name, source_name) == 0)
971 			return TRUE;
972 	}
973 
974 	return FALSE;
975 }
976 
977 static gboolean
selection_func(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean path_currently_selected,ESourceSelector * selector)978 selection_func (GtkTreeSelection *selection,
979                 GtkTreeModel *model,
980                 GtkTreePath *path,
981                 gboolean path_currently_selected,
982                 ESourceSelector *selector)
983 {
984 	ESource *source;
985 	GtkTreeIter iter;
986 	const gchar *extension_name;
987 
988 	if (selector->priv->toggled_last) {
989 		selector->priv->toggled_last = FALSE;
990 		return FALSE;
991 	}
992 
993 	if (path_currently_selected)
994 		return TRUE;
995 
996 	if (!gtk_tree_model_get_iter (model, &iter, path))
997 		return FALSE;
998 
999 	extension_name = e_source_selector_get_extension_name (selector);
1000 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1001 
1002 	if (!e_source_has_extension (source, extension_name)) {
1003 		g_object_unref (source);
1004 		return FALSE;
1005 	}
1006 
1007 	clear_saved_primary_selection (selector);
1008 
1009 	g_object_unref (source);
1010 
1011 	return TRUE;
1012 }
1013 
1014 static void
text_cell_edited_cb(ESourceSelector * selector,const gchar * path_string,const gchar * new_name)1015 text_cell_edited_cb (ESourceSelector *selector,
1016                      const gchar *path_string,
1017                      const gchar *new_name)
1018 {
1019 	GtkTreeView *tree_view;
1020 	GtkTreeModel *model;
1021 	GtkTreePath *path;
1022 	GtkTreeIter iter;
1023 	ESource *source;
1024 
1025 	if (new_name == NULL || *new_name == '\0')
1026 		return;
1027 
1028 	if (same_source_name_exists (selector, new_name))
1029 		return;
1030 
1031 	tree_view = GTK_TREE_VIEW (selector);
1032 	model = gtk_tree_view_get_model (tree_view);
1033 
1034 	path = gtk_tree_path_new_from_string (path_string);
1035 	gtk_tree_model_get_iter (model, &iter, path);
1036 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1037 	gtk_tree_path_free (path);
1038 
1039 	e_source_set_display_name (source, new_name);
1040 
1041 	e_source_selector_queue_write (selector, source);
1042 
1043 	g_object_unref (source);
1044 }
1045 
1046 static void
cell_toggled_callback(GtkCellRendererToggle * renderer,const gchar * path_string,ESourceSelector * selector)1047 cell_toggled_callback (GtkCellRendererToggle *renderer,
1048                        const gchar *path_string,
1049                        ESourceSelector *selector)
1050 {
1051 	ESource *source;
1052 	GtkTreeModel *model;
1053 	GtkTreePath *path;
1054 	GtkTreeIter iter;
1055 
1056 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
1057 	path = gtk_tree_path_new_from_string (path_string);
1058 
1059 	if (!gtk_tree_model_get_iter (model, &iter, path)) {
1060 		gtk_tree_path_free (path);
1061 		return;
1062 	}
1063 
1064 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1065 
1066 	if (e_source_selector_source_is_selected (selector, source))
1067 		e_source_selector_unselect_source (selector, source);
1068 	else
1069 		e_source_selector_select_source (selector, source);
1070 
1071 	selector->priv->toggled_last = TRUE;
1072 
1073 	gtk_tree_path_free (path);
1074 
1075 	g_object_unref (source);
1076 }
1077 
1078 static void
selection_changed_callback(GtkTreeSelection * selection,ESourceSelector * selector)1079 selection_changed_callback (GtkTreeSelection *selection,
1080                             ESourceSelector *selector)
1081 {
1082 	g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
1083 	g_object_notify (G_OBJECT (selector), "primary-selection");
1084 }
1085 
1086 static void
source_selector_set_extension_name(ESourceSelector * selector,const gchar * extension_name)1087 source_selector_set_extension_name (ESourceSelector *selector,
1088                                     const gchar *extension_name)
1089 {
1090 	g_return_if_fail (extension_name != NULL);
1091 	g_return_if_fail (selector->priv->extension_name == NULL);
1092 
1093 	selector->priv->extension_name = g_strdup (extension_name);
1094 }
1095 
1096 static void
source_selector_set_registry(ESourceSelector * selector,ESourceRegistry * registry)1097 source_selector_set_registry (ESourceSelector *selector,
1098                               ESourceRegistry *registry)
1099 {
1100 	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
1101 	g_return_if_fail (selector->priv->registry == NULL);
1102 
1103 	selector->priv->registry = g_object_ref (registry);
1104 }
1105 
1106 static void
source_selector_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1107 source_selector_set_property (GObject *object,
1108                               guint property_id,
1109                               const GValue *value,
1110                               GParamSpec *pspec)
1111 {
1112 	switch (property_id) {
1113 		case PROP_EXTENSION_NAME:
1114 			source_selector_set_extension_name (
1115 				E_SOURCE_SELECTOR (object),
1116 				g_value_get_string (value));
1117 			return;
1118 
1119 		case PROP_PRIMARY_SELECTION:
1120 			e_source_selector_set_primary_selection (
1121 				E_SOURCE_SELECTOR (object),
1122 				g_value_get_object (value));
1123 			return;
1124 
1125 		case PROP_REGISTRY:
1126 			source_selector_set_registry (
1127 				E_SOURCE_SELECTOR (object),
1128 				g_value_get_object (value));
1129 			return;
1130 
1131 		case PROP_SHOW_COLORS:
1132 			e_source_selector_set_show_colors (
1133 				E_SOURCE_SELECTOR (object),
1134 				g_value_get_boolean (value));
1135 			return;
1136 
1137 		case PROP_SHOW_ICONS:
1138 			e_source_selector_set_show_icons (
1139 				E_SOURCE_SELECTOR (object),
1140 				g_value_get_boolean (value));
1141 			return;
1142 
1143 		case PROP_SHOW_TOGGLES:
1144 			e_source_selector_set_show_toggles (
1145 				E_SOURCE_SELECTOR (object),
1146 				g_value_get_boolean (value));
1147 			return;
1148 	}
1149 
1150 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1151 }
1152 
1153 static void
source_selector_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1154 source_selector_get_property (GObject *object,
1155                               guint property_id,
1156                               GValue *value,
1157                               GParamSpec *pspec)
1158 {
1159 	switch (property_id) {
1160 		case PROP_EXTENSION_NAME:
1161 			g_value_set_string (
1162 				value,
1163 				e_source_selector_get_extension_name (
1164 				E_SOURCE_SELECTOR (object)));
1165 			return;
1166 
1167 		case PROP_PRIMARY_SELECTION:
1168 			g_value_take_object (
1169 				value,
1170 				e_source_selector_ref_primary_selection (
1171 				E_SOURCE_SELECTOR (object)));
1172 			return;
1173 
1174 		case PROP_REGISTRY:
1175 			g_value_set_object (
1176 				value,
1177 				e_source_selector_get_registry (
1178 				E_SOURCE_SELECTOR (object)));
1179 			return;
1180 
1181 		case PROP_SHOW_COLORS:
1182 			g_value_set_boolean (
1183 				value,
1184 				e_source_selector_get_show_colors (
1185 				E_SOURCE_SELECTOR (object)));
1186 			return;
1187 
1188 		case PROP_SHOW_ICONS:
1189 			g_value_set_boolean (
1190 				value,
1191 				e_source_selector_get_show_icons (
1192 				E_SOURCE_SELECTOR (object)));
1193 			return;
1194 
1195 		case PROP_SHOW_TOGGLES:
1196 			g_value_set_boolean (
1197 				value,
1198 				e_source_selector_get_show_toggles (
1199 				E_SOURCE_SELECTOR (object)));
1200 			return;
1201 	}
1202 
1203 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1204 }
1205 
1206 static void
source_selector_dispose(GObject * object)1207 source_selector_dispose (GObject *object)
1208 {
1209 	ESourceSelectorPrivate *priv;
1210 
1211 	priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
1212 
1213 	if (priv->update_busy_renderer_id) {
1214 		g_source_remove (priv->update_busy_renderer_id);
1215 		priv->update_busy_renderer_id = 0;
1216 	}
1217 
1218 	if (priv->source_added_handler_id > 0) {
1219 		g_signal_handler_disconnect (
1220 			priv->registry,
1221 			priv->source_added_handler_id);
1222 		priv->source_added_handler_id = 0;
1223 	}
1224 
1225 	if (priv->source_changed_handler_id > 0) {
1226 		g_signal_handler_disconnect (
1227 			priv->registry,
1228 			priv->source_changed_handler_id);
1229 		priv->source_changed_handler_id = 0;
1230 	}
1231 
1232 	if (priv->source_removed_handler_id > 0) {
1233 		g_signal_handler_disconnect (
1234 			priv->registry,
1235 			priv->source_removed_handler_id);
1236 		priv->source_removed_handler_id = 0;
1237 	}
1238 
1239 	if (priv->source_enabled_handler_id > 0) {
1240 		g_signal_handler_disconnect (
1241 			priv->registry,
1242 			priv->source_enabled_handler_id);
1243 		priv->source_enabled_handler_id = 0;
1244 	}
1245 
1246 	if (priv->source_disabled_handler_id > 0) {
1247 		g_signal_handler_disconnect (
1248 			priv->registry,
1249 			priv->source_disabled_handler_id);
1250 		priv->source_disabled_handler_id = 0;
1251 	}
1252 
1253 	g_clear_object (&priv->registry);
1254 	g_clear_object (&priv->busy_renderer);
1255 
1256 	g_hash_table_remove_all (priv->source_index);
1257 	g_hash_table_remove_all (priv->pending_writes);
1258 	g_hash_table_remove_all (priv->hidden_groups);
1259 
1260 	g_slist_free_full (priv->groups_order, g_free);
1261 	priv->groups_order = NULL;
1262 
1263 	clear_saved_primary_selection (E_SOURCE_SELECTOR (object));
1264 
1265 	/* Chain up to parent's dispose() method. */
1266 	G_OBJECT_CLASS (e_source_selector_parent_class)->dispose (object);
1267 }
1268 
1269 static void
source_selector_finalize(GObject * object)1270 source_selector_finalize (GObject *object)
1271 {
1272 	ESourceSelectorPrivate *priv;
1273 
1274 	priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);
1275 
1276 	g_hash_table_destroy (priv->source_index);
1277 	g_hash_table_destroy (priv->pending_writes);
1278 	g_hash_table_destroy (priv->hidden_groups);
1279 
1280 	g_free (priv->extension_name);
1281 
1282 	if (priv->main_context != NULL)
1283 		g_main_context_unref (priv->main_context);
1284 
1285 	/* Chain up to parent's finalize() method. */
1286 	G_OBJECT_CLASS (e_source_selector_parent_class)->finalize (object);
1287 }
1288 
1289 static void
source_selector_constructed(GObject * object)1290 source_selector_constructed (GObject *object)
1291 {
1292 	ESourceRegistry *registry;
1293 	ESourceSelector *selector;
1294 	gulong handler_id;
1295 
1296 	/* Chain up to parent's method. */
1297 	G_OBJECT_CLASS (e_source_selector_parent_class)->constructed (object);
1298 
1299 	selector = E_SOURCE_SELECTOR (object);
1300 	registry = e_source_selector_get_registry (selector);
1301 
1302 	handler_id = g_signal_connect (
1303 		registry, "source-added",
1304 		G_CALLBACK (source_selector_source_added_cb), selector);
1305 	selector->priv->source_added_handler_id = handler_id;
1306 
1307 	handler_id = g_signal_connect (
1308 		registry, "source-changed",
1309 		G_CALLBACK (source_selector_source_changed_cb), selector);
1310 	selector->priv->source_changed_handler_id = handler_id;
1311 
1312 	handler_id = g_signal_connect (
1313 		registry, "source-removed",
1314 		G_CALLBACK (source_selector_source_removed_cb), selector);
1315 	selector->priv->source_removed_handler_id = handler_id;
1316 
1317 	handler_id = g_signal_connect (
1318 		registry, "source-enabled",
1319 		G_CALLBACK (source_selector_source_enabled_cb), selector);
1320 	selector->priv->source_enabled_handler_id = handler_id;
1321 
1322 	handler_id = g_signal_connect (
1323 		registry, "source-disabled",
1324 		G_CALLBACK (source_selector_source_disabled_cb), selector);
1325 	selector->priv->source_disabled_handler_id = handler_id;
1326 
1327 	source_selector_build_model (selector);
1328 
1329 	gtk_tree_view_expand_all (GTK_TREE_VIEW (selector));
1330 }
1331 
1332 static gboolean
source_selector_button_press_event(GtkWidget * widget,GdkEventButton * event)1333 source_selector_button_press_event (GtkWidget *widget,
1334                                     GdkEventButton *event)
1335 {
1336 	ESourceSelector *selector;
1337 	GtkWidgetClass *widget_class;
1338 	GtkTreePath *path = NULL;
1339 	ESource *source = NULL;
1340 	ESource *primary;
1341 	gboolean right_click = FALSE;
1342 	gboolean triple_click = FALSE;
1343 	gboolean row_exists;
1344 	gboolean res = FALSE;
1345 
1346 	selector = E_SOURCE_SELECTOR (widget);
1347 
1348 	selector->priv->toggled_last = FALSE;
1349 
1350 	/* Triple-clicking a source selects it exclusively. */
1351 
1352 	if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1353 		right_click = TRUE;
1354 	else if (event->button == 1 && event->type == GDK_3BUTTON_PRESS)
1355 		triple_click = TRUE;
1356 	else
1357 		goto chainup;
1358 
1359 	row_exists = gtk_tree_view_get_path_at_pos (
1360 		GTK_TREE_VIEW (widget), event->x, event->y,
1361 		&path, NULL, NULL, NULL);
1362 
1363 	/* Get the source/group */
1364 	if (row_exists) {
1365 		GtkTreeModel *model;
1366 		GtkTreeIter iter;
1367 
1368 		model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1369 
1370 		gtk_tree_model_get_iter (model, &iter, path);
1371 		gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1372 	}
1373 
1374 	if (path != NULL)
1375 		gtk_tree_path_free (path);
1376 
1377 	if (source == NULL)
1378 		goto chainup;
1379 
1380 	primary = e_source_selector_ref_primary_selection (selector);
1381 	if (source != primary)
1382 		e_source_selector_set_primary_selection (selector, source);
1383 	if (primary != NULL)
1384 		g_object_unref (primary);
1385 
1386 	if (right_click)
1387 		g_signal_emit (
1388 			widget, signals[POPUP_EVENT], 0, source, event, &res);
1389 
1390 	if (triple_click) {
1391 		e_source_selector_select_exclusive (selector, source);
1392 		res = TRUE;
1393 	}
1394 
1395 	g_object_unref (source);
1396 
1397 	return res;
1398 
1399 chainup:
1400 
1401 	/* Chain up to parent's button_press_event() method. */
1402 	widget_class = GTK_WIDGET_CLASS (e_source_selector_parent_class);
1403 	return widget_class->button_press_event (widget, event);
1404 }
1405 
1406 static void
source_selector_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time_)1407 source_selector_drag_leave (GtkWidget *widget,
1408                             GdkDragContext *context,
1409                             guint time_)
1410 {
1411 	GtkTreeView *tree_view;
1412 	GtkTreeViewDropPosition pos;
1413 
1414 	tree_view = GTK_TREE_VIEW (widget);
1415 	pos = GTK_TREE_VIEW_DROP_BEFORE;
1416 
1417 	gtk_tree_view_set_drag_dest_row (tree_view, NULL, pos);
1418 }
1419 
1420 static gboolean
source_selector_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time_)1421 source_selector_drag_motion (GtkWidget *widget,
1422                              GdkDragContext *context,
1423                              gint x,
1424                              gint y,
1425                              guint time_)
1426 {
1427 	ESource *source = NULL;
1428 	GtkTreeView *tree_view;
1429 	GtkTreeModel *model;
1430 	GtkTreePath *path = NULL;
1431 	GtkTreeIter iter;
1432 	GtkTreeViewDropPosition pos;
1433 	GdkDragAction action = 0;
1434 
1435 	tree_view = GTK_TREE_VIEW (widget);
1436 	model = gtk_tree_view_get_model (tree_view);
1437 
1438 	if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
1439 		goto exit;
1440 
1441 	if (!gtk_tree_model_get_iter (model, &iter, path))
1442 		goto exit;
1443 
1444 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1445 
1446 	if (!e_source_get_writable (source))
1447 		goto exit;
1448 
1449 	pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
1450 	gtk_tree_view_set_drag_dest_row (tree_view, path, pos);
1451 
1452 	if (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE)
1453 		action = GDK_ACTION_MOVE;
1454 	else
1455 		action = gdk_drag_context_get_suggested_action (context);
1456 
1457 exit:
1458 	if (path != NULL)
1459 		gtk_tree_path_free (path);
1460 
1461 	if (source != NULL)
1462 		g_object_unref (source);
1463 
1464 	gdk_drag_status (context, action, time_);
1465 
1466 	return TRUE;
1467 }
1468 
1469 static gboolean
source_selector_drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time_)1470 source_selector_drag_drop (GtkWidget *widget,
1471                            GdkDragContext *context,
1472                            gint x,
1473                            gint y,
1474                            guint time_)
1475 {
1476 	ESource *source;
1477 	ESourceSelector *selector;
1478 	GtkTreeView *tree_view;
1479 	GtkTreeModel *model;
1480 	GtkTreePath *path;
1481 	GtkTreeIter iter;
1482 	const gchar *extension_name;
1483 	gboolean drop_zone;
1484 	gboolean valid;
1485 
1486 	tree_view = GTK_TREE_VIEW (widget);
1487 	model = gtk_tree_view_get_model (tree_view);
1488 
1489 	if (!gtk_tree_view_get_path_at_pos (
1490 		tree_view, x, y, &path, NULL, NULL, NULL))
1491 		return FALSE;
1492 
1493 	valid = gtk_tree_model_get_iter (model, &iter, path);
1494 	gtk_tree_path_free (path);
1495 	g_return_val_if_fail (valid, FALSE);
1496 
1497 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1498 
1499 	selector = E_SOURCE_SELECTOR (widget);
1500 	extension_name = e_source_selector_get_extension_name (selector);
1501 	drop_zone = e_source_has_extension (source, extension_name);
1502 
1503 	g_object_unref (source);
1504 
1505 	return drop_zone;
1506 }
1507 
1508 static void
source_selector_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time_)1509 source_selector_drag_data_received (GtkWidget *widget,
1510                                     GdkDragContext *context,
1511                                     gint x,
1512                                     gint y,
1513                                     GtkSelectionData *selection_data,
1514                                     guint info,
1515                                     guint time_)
1516 {
1517 	ESource *source = NULL;
1518 	GtkTreeView *tree_view;
1519 	GtkTreeModel *model;
1520 	GtkTreePath *path = NULL;
1521 	GtkTreeIter iter;
1522 	GdkDragAction action;
1523 	gboolean delete;
1524 	gboolean success = FALSE;
1525 
1526 	tree_view = GTK_TREE_VIEW (widget);
1527 	model = gtk_tree_view_get_model (tree_view);
1528 
1529 	action = gdk_drag_context_get_selected_action (context);
1530 	delete = (action == GDK_ACTION_MOVE);
1531 
1532 	if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
1533 		goto exit;
1534 
1535 	if (!gtk_tree_model_get_iter (model, &iter, path))
1536 		goto exit;
1537 
1538 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
1539 
1540 	if (!e_source_get_writable (source))
1541 		goto exit;
1542 
1543 	g_signal_emit (
1544 		widget, signals[DATA_DROPPED], 0, selection_data,
1545 		source, gdk_drag_context_get_selected_action (context),
1546 		info, &success);
1547 
1548 exit:
1549 	if (path != NULL)
1550 		gtk_tree_path_free (path);
1551 
1552 	if (source != NULL)
1553 		g_object_unref (source);
1554 
1555 	gtk_drag_finish (context, success, delete, time_);
1556 }
1557 
1558 static gboolean
source_selector_popup_menu(GtkWidget * widget)1559 source_selector_popup_menu (GtkWidget *widget)
1560 {
1561 	ESourceSelector *selector;
1562 	ESource *source;
1563 	gboolean res = FALSE;
1564 
1565 	selector = E_SOURCE_SELECTOR (widget);
1566 	source = e_source_selector_ref_primary_selection (selector);
1567 	g_signal_emit (selector, signals[POPUP_EVENT], 0, source, NULL, &res);
1568 
1569 	if (source != NULL)
1570 		g_object_unref (source);
1571 
1572 	return res;
1573 }
1574 
1575 static gboolean
source_selector_test_collapse_row(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path)1576 source_selector_test_collapse_row (GtkTreeView *tree_view,
1577                                    GtkTreeIter *iter,
1578                                    GtkTreePath *path)
1579 {
1580 	ESourceSelectorPrivate *priv;
1581 	GtkTreeSelection *selection;
1582 	GtkTreeModel *model;
1583 	GtkTreeIter child_iter;
1584 
1585 	priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);
1586 
1587 	/* Clear this because something else has been clicked on now */
1588 	priv->toggled_last = FALSE;
1589 
1590 	if (priv->saved_primary_selection)
1591 		return FALSE;
1592 
1593 	selection = gtk_tree_view_get_selection (tree_view);
1594 
1595 	if (!gtk_tree_selection_get_selected (selection, &model, &child_iter))
1596 		return FALSE;
1597 
1598 	if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
1599 		GtkTreeRowReference *reference;
1600 		GtkTreePath *child_path;
1601 
1602 		child_path = gtk_tree_model_get_path (model, &child_iter);
1603 		reference = gtk_tree_row_reference_new (model, child_path);
1604 		priv->saved_primary_selection = reference;
1605 		gtk_tree_path_free (child_path);
1606 	}
1607 
1608 	return FALSE;
1609 }
1610 
1611 static void
source_selector_row_expanded(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path)1612 source_selector_row_expanded (GtkTreeView *tree_view,
1613                               GtkTreeIter *iter,
1614                               GtkTreePath *path)
1615 {
1616 	ESourceSelectorPrivate *priv;
1617 	GtkTreeModel *model;
1618 	GtkTreePath *child_path;
1619 	GtkTreeIter child_iter;
1620 
1621 	priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);
1622 
1623 	if (!priv->saved_primary_selection)
1624 		return;
1625 
1626 	model = gtk_tree_view_get_model (tree_view);
1627 
1628 	child_path = gtk_tree_row_reference_get_path (
1629 		priv->saved_primary_selection);
1630 	gtk_tree_model_get_iter (model, &child_iter, child_path);
1631 
1632 	if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
1633 		GtkTreeSelection *selection;
1634 
1635 		selection = gtk_tree_view_get_selection (tree_view);
1636 		gtk_tree_selection_select_iter (selection, &child_iter);
1637 
1638 		clear_saved_primary_selection (E_SOURCE_SELECTOR (tree_view));
1639 	}
1640 
1641 	gtk_tree_path_free (child_path);
1642 }
1643 
1644 static gboolean
source_selector_get_source_selected(ESourceSelector * selector,ESource * source)1645 source_selector_get_source_selected (ESourceSelector *selector,
1646                                      ESource *source)
1647 {
1648 	ESourceSelectable *extension;
1649 	const gchar *extension_name;
1650 	gboolean selected = TRUE;
1651 
1652 	extension_name = e_source_selector_get_extension_name (selector);
1653 
1654 	if (!e_source_has_extension (source, extension_name))
1655 		return FALSE;
1656 
1657 	extension = e_source_get_extension (source, extension_name);
1658 
1659 	if (E_IS_SOURCE_SELECTABLE (extension))
1660 		selected = e_source_selectable_get_selected (extension);
1661 
1662 	return selected;
1663 }
1664 
1665 static gboolean
source_selector_set_source_selected(ESourceSelector * selector,ESource * source,gboolean selected)1666 source_selector_set_source_selected (ESourceSelector *selector,
1667                                      ESource *source,
1668                                      gboolean selected)
1669 {
1670 	ESourceSelectable *extension;
1671 	const gchar *extension_name;
1672 
1673 	extension_name = e_source_selector_get_extension_name (selector);
1674 
1675 	if (!e_source_has_extension (source, extension_name))
1676 		return FALSE;
1677 
1678 	extension = e_source_get_extension (source, extension_name);
1679 
1680 	if (!E_IS_SOURCE_SELECTABLE (extension))
1681 		return FALSE;
1682 
1683 	if (selected != e_source_selectable_get_selected (extension)) {
1684 		e_source_selectable_set_selected (extension, selected);
1685 		e_source_selector_queue_write (selector, source);
1686 
1687 		return TRUE;
1688 	}
1689 
1690 	return FALSE;
1691 }
1692 
1693 static gboolean
ess_bool_accumulator(GSignalInvocationHint * ihint,GValue * out,const GValue * in,gpointer data)1694 ess_bool_accumulator (GSignalInvocationHint *ihint,
1695                       GValue *out,
1696                       const GValue *in,
1697                       gpointer data)
1698 {
1699 	gboolean v_boolean;
1700 
1701 	v_boolean = g_value_get_boolean (in);
1702 	g_value_set_boolean (out, v_boolean);
1703 
1704 	return !v_boolean;
1705 }
1706 
1707 static void
e_source_selector_class_init(ESourceSelectorClass * class)1708 e_source_selector_class_init (ESourceSelectorClass *class)
1709 {
1710 	GObjectClass *object_class;
1711 	GtkWidgetClass *widget_class;
1712 	GtkTreeViewClass *tree_view_class;
1713 
1714 	g_type_class_add_private (class, sizeof (ESourceSelectorPrivate));
1715 
1716 	object_class = G_OBJECT_CLASS (class);
1717 	object_class->set_property = source_selector_set_property;
1718 	object_class->get_property = source_selector_get_property;
1719 	object_class->dispose = source_selector_dispose;
1720 	object_class->finalize = source_selector_finalize;
1721 	object_class->constructed = source_selector_constructed;
1722 
1723 	widget_class = GTK_WIDGET_CLASS (class);
1724 	widget_class->button_press_event = source_selector_button_press_event;
1725 	widget_class->drag_leave = source_selector_drag_leave;
1726 	widget_class->drag_motion = source_selector_drag_motion;
1727 	widget_class->drag_drop = source_selector_drag_drop;
1728 	widget_class->drag_data_received = source_selector_drag_data_received;
1729 	widget_class->popup_menu = source_selector_popup_menu;
1730 
1731 	tree_view_class = GTK_TREE_VIEW_CLASS (class);
1732 	tree_view_class->test_collapse_row = source_selector_test_collapse_row;
1733 	tree_view_class->row_expanded = source_selector_row_expanded;
1734 
1735 	class->get_source_selected = source_selector_get_source_selected;
1736 	class->set_source_selected = source_selector_set_source_selected;
1737 
1738 	g_object_class_install_property (
1739 		object_class,
1740 		PROP_EXTENSION_NAME,
1741 		g_param_spec_string (
1742 			"extension-name",
1743 			NULL,
1744 			NULL,
1745 			NULL,
1746 			G_PARAM_READWRITE |
1747 			G_PARAM_CONSTRUCT_ONLY |
1748 			G_PARAM_STATIC_STRINGS));
1749 
1750 	g_object_class_install_property (
1751 		object_class,
1752 		PROP_PRIMARY_SELECTION,
1753 		g_param_spec_object (
1754 			"primary-selection",
1755 			NULL,
1756 			NULL,
1757 			E_TYPE_SOURCE,
1758 			G_PARAM_READWRITE |
1759 			G_PARAM_STATIC_STRINGS));
1760 
1761 	g_object_class_install_property (
1762 		object_class,
1763 		PROP_REGISTRY,
1764 		g_param_spec_object (
1765 			"registry",
1766 			NULL,
1767 			NULL,
1768 			E_TYPE_SOURCE_REGISTRY,
1769 			G_PARAM_READWRITE |
1770 			G_PARAM_CONSTRUCT_ONLY |
1771 			G_PARAM_STATIC_STRINGS));
1772 
1773 	g_object_class_install_property (
1774 		object_class,
1775 		PROP_SHOW_COLORS,
1776 		g_param_spec_boolean (
1777 			"show-colors",
1778 			NULL,
1779 			NULL,
1780 			TRUE,
1781 			G_PARAM_READWRITE |
1782 			G_PARAM_STATIC_STRINGS));
1783 
1784 	g_object_class_install_property (
1785 		object_class,
1786 		PROP_SHOW_ICONS,
1787 		g_param_spec_boolean (
1788 			"show-icons",
1789 			NULL,
1790 			NULL,
1791 			TRUE,
1792 			G_PARAM_READWRITE |
1793 			G_PARAM_STATIC_STRINGS));
1794 
1795 	g_object_class_install_property (
1796 		object_class,
1797 		PROP_SHOW_TOGGLES,
1798 		g_param_spec_boolean (
1799 			"show-toggles",
1800 			NULL,
1801 			NULL,
1802 			TRUE,
1803 			G_PARAM_READWRITE |
1804 			G_PARAM_STATIC_STRINGS));
1805 
1806 	signals[SELECTION_CHANGED] = g_signal_new (
1807 		"selection-changed",
1808 		G_OBJECT_CLASS_TYPE (object_class),
1809 		G_SIGNAL_RUN_LAST,
1810 		G_STRUCT_OFFSET (ESourceSelectorClass, selection_changed),
1811 		NULL, NULL,
1812 		g_cclosure_marshal_VOID__VOID,
1813 		G_TYPE_NONE, 0);
1814 
1815 	/* XXX Consider this signal deprecated.  Connect
1816 	 *     to "notify::primary-selection" instead. */
1817 	signals[PRIMARY_SELECTION_CHANGED] = g_signal_new (
1818 		"primary-selection-changed",
1819 		G_OBJECT_CLASS_TYPE (object_class),
1820 		G_SIGNAL_RUN_LAST,
1821 		G_STRUCT_OFFSET (ESourceSelectorClass, primary_selection_changed),
1822 		NULL, NULL,
1823 		g_cclosure_marshal_VOID__VOID,
1824 		G_TYPE_NONE, 0);
1825 
1826 	signals[POPUP_EVENT] = g_signal_new (
1827 		"popup-event",
1828 		G_OBJECT_CLASS_TYPE (object_class),
1829 		G_SIGNAL_RUN_LAST,
1830 		G_STRUCT_OFFSET (ESourceSelectorClass, popup_event),
1831 		ess_bool_accumulator, NULL, NULL,
1832 		G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT,
1833 		GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
1834 
1835 	signals[DATA_DROPPED] = g_signal_new (
1836 		"data-dropped",
1837 		G_OBJECT_CLASS_TYPE (object_class),
1838 		G_SIGNAL_RUN_LAST,
1839 		G_STRUCT_OFFSET (ESourceSelectorClass, data_dropped),
1840 		NULL, NULL, NULL,
1841 		G_TYPE_BOOLEAN, 4,
1842 		GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
1843 		E_TYPE_SOURCE,
1844 		GDK_TYPE_DRAG_ACTION,
1845 		G_TYPE_UINT);
1846 
1847 	signals[SOURCE_SELECTED] = g_signal_new (
1848 		"source-selected",
1849 		G_OBJECT_CLASS_TYPE (object_class),
1850 		G_SIGNAL_RUN_LAST,
1851 		G_STRUCT_OFFSET (ESourceSelectorClass, source_selected),
1852 		NULL, NULL, NULL,
1853 		G_TYPE_NONE, 1, E_TYPE_SOURCE);
1854 
1855 	signals[SOURCE_UNSELECTED] = g_signal_new (
1856 		"source-unselected",
1857 		G_OBJECT_CLASS_TYPE (object_class),
1858 		G_SIGNAL_RUN_LAST,
1859 		G_STRUCT_OFFSET (ESourceSelectorClass, source_unselected),
1860 		NULL, NULL, NULL,
1861 		G_TYPE_NONE, 1, E_TYPE_SOURCE);
1862 
1863 	/* Return TRUE when the source should be hidden, FALSE when not. */
1864 	signals[FILTER_SOURCE] = g_signal_new (
1865 		"filter-source",
1866 		G_OBJECT_CLASS_TYPE (object_class),
1867 		G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1868 		G_STRUCT_OFFSET (ESourceSelectorClass, filter_source),
1869 		NULL, NULL, NULL,
1870 		G_TYPE_BOOLEAN, 1, E_TYPE_SOURCE);
1871 }
1872 
1873 static void
e_source_selector_init(ESourceSelector * selector)1874 e_source_selector_init (ESourceSelector *selector)
1875 {
1876 	GHashTable *pending_writes;
1877 	GtkTreeViewColumn *column;
1878 	GtkTreeSelection *selection;
1879 	GtkCellRenderer *renderer;
1880 	GtkTreeStore *tree_store;
1881 	GtkTreeView *tree_view;
1882 
1883 	pending_writes = g_hash_table_new_full (
1884 		(GHashFunc) g_direct_hash,
1885 		(GEqualFunc) g_direct_equal,
1886 		(GDestroyNotify) g_object_unref,
1887 		(GDestroyNotify) pending_writes_destroy_source);
1888 
1889 	selector->priv = E_SOURCE_SELECTOR_GET_PRIVATE (selector);
1890 
1891 	selector->priv->pending_writes = pending_writes;
1892 	selector->priv->hidden_groups = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1893 
1894 	selector->priv->main_context = g_main_context_get_thread_default ();
1895 	if (selector->priv->main_context != NULL)
1896 		g_main_context_ref (selector->priv->main_context);
1897 
1898 	tree_view = GTK_TREE_VIEW (selector);
1899 
1900 	gtk_tree_view_set_search_column (tree_view, COLUMN_SOURCE);
1901 	gtk_tree_view_set_enable_search (tree_view, TRUE);
1902 
1903 	selector->priv->toggled_last = FALSE;
1904 	selector->priv->show_colors = TRUE;
1905 	selector->priv->show_toggles = TRUE;
1906 
1907 	selector->priv->source_index = g_hash_table_new_full (
1908 		(GHashFunc) e_source_hash,
1909 		(GEqualFunc) e_source_equal,
1910 		(GDestroyNotify) g_object_unref,
1911 		(GDestroyNotify) gtk_tree_row_reference_free);
1912 
1913 	tree_store = gtk_tree_store_new (
1914 		NUM_COLUMNS,
1915 		G_TYPE_STRING,		/* COLUMN_NAME */
1916 		GDK_TYPE_RGBA,		/* COLUMN_COLOR */
1917 		G_TYPE_BOOLEAN,		/* COLUMN_ACTIVE */
1918 		G_TYPE_STRING,		/* COLUMN_ICON_NAME */
1919 		G_TYPE_BOOLEAN,		/* COLUMN_SHOW_COLOR */
1920 		G_TYPE_BOOLEAN,		/* COLUMN_SHOW_ICON */
1921 		G_TYPE_BOOLEAN,		/* COLUMN_SHOW_TOGGLE */
1922 		G_TYPE_INT,		/* COLUMN_WEIGHT */
1923 		E_TYPE_SOURCE,		/* COLUMN_SOURCE */
1924 		G_TYPE_STRING,		/* COLUMN_TOOLTIP */
1925 		G_TYPE_BOOLEAN,		/* COLUMN_IS_BUSY */
1926 		G_TYPE_UINT,		/* COLUMN_CONNECTION_STATUS */
1927 		G_TYPE_UINT);		/* COLUMN_SORT_ORDER */
1928 
1929 	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (tree_store));
1930 
1931 	column = gtk_tree_view_column_new ();
1932 	gtk_tree_view_column_set_expand (column, TRUE);
1933 	gtk_tree_view_append_column (tree_view, column);
1934 
1935 	renderer = e_cell_renderer_color_new ();
1936 	g_object_set (
1937 		G_OBJECT (renderer), "mode",
1938 		GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
1939 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
1940 	gtk_tree_view_column_add_attribute (
1941 		column, renderer, "rgba", COLUMN_COLOR);
1942 	gtk_tree_view_column_add_attribute (
1943 		column, renderer, "visible", COLUMN_SHOW_COLOR);
1944 
1945 	renderer = e_cell_renderer_safe_toggle_new ();
1946 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
1947 	gtk_tree_view_column_add_attribute (
1948 		column, renderer, "active", COLUMN_ACTIVE);
1949 	gtk_tree_view_column_add_attribute (
1950 		column, renderer, "visible", COLUMN_SHOW_TOGGLE);
1951 	g_signal_connect (
1952 		renderer, "toggled",
1953 		G_CALLBACK (cell_toggled_callback), selector);
1954 
1955 	renderer = gtk_cell_renderer_pixbuf_new ();
1956 	g_object_set (
1957 		G_OBJECT (renderer),
1958 		"stock-size", GTK_ICON_SIZE_MENU, NULL);
1959 	gtk_tree_view_column_pack_start (column, renderer, FALSE);
1960 	gtk_tree_view_column_add_attribute (
1961 		column, renderer, "icon-name", COLUMN_ICON_NAME);
1962 	gtk_tree_view_column_add_attribute (
1963 		column, renderer, "visible", COLUMN_SHOW_ICONS);
1964 
1965 	renderer = gtk_cell_renderer_text_new ();
1966 	g_object_set (
1967 		G_OBJECT (renderer),
1968 		"ellipsize", PANGO_ELLIPSIZE_END, NULL);
1969 	g_signal_connect_swapped (
1970 		renderer, "edited",
1971 		G_CALLBACK (text_cell_edited_cb), selector);
1972 	gtk_tree_view_column_pack_start (column, renderer, TRUE);
1973 	gtk_tree_view_column_set_attributes (
1974 		column, renderer,
1975 		"text", COLUMN_NAME,
1976 		"weight", COLUMN_WEIGHT,
1977 		NULL);
1978 
1979 	renderer = gtk_cell_renderer_spinner_new ();
1980 	selector->priv->busy_renderer = g_object_ref (renderer);
1981 	gtk_tree_view_column_pack_end (column, renderer, FALSE);
1982 	gtk_tree_view_column_set_attributes (
1983 		column, renderer,
1984 		"visible", COLUMN_IS_BUSY,
1985 		"active", COLUMN_IS_BUSY,
1986 		NULL);
1987 
1988 	selection = gtk_tree_view_get_selection (tree_view);
1989 	gtk_tree_selection_set_select_function (
1990 		selection, (GtkTreeSelectionFunc)
1991 		selection_func, selector, NULL);
1992 	g_signal_connect_object (
1993 		selection, "changed",
1994 		G_CALLBACK (selection_changed_callback),
1995 		G_OBJECT (selector), 0);
1996 
1997 	gtk_tree_view_set_headers_visible (tree_view, FALSE);
1998 	gtk_tree_view_set_tooltip_column (tree_view, COLUMN_TOOLTIP);
1999 	gtk_widget_set_has_tooltip (GTK_WIDGET (tree_view), TRUE);
2000 }
2001 
2002 /**
2003  * e_source_selector_new:
2004  * @registry: an #ESourceRegistry
2005  * @extension_name: the name of an #ESource extension
2006  *
2007  * Displays a list of sources from @registry having an extension named
2008  * @extension_name.  The sources are grouped by backend or groupware
2009  * account, which are described by the parent source.
2010  *
2011  * Returns: a new #ESourceSelector
2012  **/
2013 GtkWidget *
e_source_selector_new(ESourceRegistry * registry,const gchar * extension_name)2014 e_source_selector_new (ESourceRegistry *registry,
2015                        const gchar *extension_name)
2016 {
2017 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
2018 	g_return_val_if_fail (extension_name != NULL, NULL);
2019 
2020 	return g_object_new (
2021 		E_TYPE_SOURCE_SELECTOR, "registry", registry,
2022 		"extension-name", extension_name, NULL);
2023 }
2024 
2025 /**
2026  * e_source_selector_get_registry:
2027  * @selector: an #ESourceSelector
2028  *
2029  * Returns the #ESourceRegistry that @selector is getting sources from.
2030  *
2031  * Returns: an #ESourceRegistry
2032  *
2033  * Since: 3.6
2034  **/
2035 ESourceRegistry *
e_source_selector_get_registry(ESourceSelector * selector)2036 e_source_selector_get_registry (ESourceSelector *selector)
2037 {
2038 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
2039 
2040 	return selector->priv->registry;
2041 }
2042 
2043 /**
2044  * e_source_selector_get_extension_name:
2045  * @selector: an #ESourceSelector
2046  *
2047  * Returns the extension name used to filter which sources are displayed.
2048  *
2049  * Returns: the #ESource extension name
2050  *
2051  * Since: 3.6
2052  **/
2053 const gchar *
e_source_selector_get_extension_name(ESourceSelector * selector)2054 e_source_selector_get_extension_name (ESourceSelector *selector)
2055 {
2056 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
2057 
2058 	return selector->priv->extension_name;
2059 }
2060 
2061 /**
2062  * e_source_selector_get_show_colors:
2063  * @selector: an #ESourceSelector
2064  *
2065  * Returns whether colors are shown next to data sources.
2066  *
2067  * Returns: %TRUE if colors are being shown
2068  *
2069  * Since: 3.6
2070  **/
2071 gboolean
e_source_selector_get_show_colors(ESourceSelector * selector)2072 e_source_selector_get_show_colors (ESourceSelector *selector)
2073 {
2074 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
2075 
2076 	return selector->priv->show_colors;
2077 }
2078 
2079 /**
2080  * e_source_selector_set_show_colors:
2081  * @selector: an #ESourceSelector
2082  * @show_colors: whether to show colors
2083  *
2084  * Sets whether to show colors next to data sources.
2085  *
2086  * Since: 3.6
2087  **/
2088 void
e_source_selector_set_show_colors(ESourceSelector * selector,gboolean show_colors)2089 e_source_selector_set_show_colors (ESourceSelector *selector,
2090                                    gboolean show_colors)
2091 {
2092 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2093 
2094 	if (show_colors == selector->priv->show_colors)
2095 		return;
2096 
2097 	selector->priv->show_colors = show_colors;
2098 
2099 	g_object_notify (G_OBJECT (selector), "show-colors");
2100 
2101 	source_selector_build_model (selector);
2102 }
2103 
2104 /**
2105  * e_source_selector_get_show_icons:
2106  * @selector: an #ESourceSelector
2107  *
2108  * Returns whether icons are shown next to data sources.
2109  *
2110  * Generally the icon shown will be based on the presence of a backend-based
2111  * extension, such as #ESourceAddressBook or #ESourceCalendar.  For #ESource
2112  * instances with no such extension, no icon is shown.
2113  *
2114  * Returns: %TRUE if icons are being shown
2115  *
2116  * Since: 3.12
2117  **/
2118 gboolean
e_source_selector_get_show_icons(ESourceSelector * selector)2119 e_source_selector_get_show_icons (ESourceSelector *selector)
2120 {
2121 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
2122 
2123 	return selector->priv->show_icons;
2124 }
2125 
2126 /**
2127  * e_source_selector_set_show_icons:
2128  * @selector: an #ESourceSelector
2129  * @show_icons: whether to show icons
2130  *
2131  * Sets whether to show icons next to data sources.
2132  *
2133  * Generally the icon shown will be based on the presence of a backend-based
2134  * extension, such as #ESourceAddressBook or #ESourceCalendar.  For #ESource
2135  * instances with no such extension, no icon is shown.
2136  *
2137  * Since: 3.12
2138  **/
2139 void
e_source_selector_set_show_icons(ESourceSelector * selector,gboolean show_icons)2140 e_source_selector_set_show_icons (ESourceSelector *selector,
2141                                   gboolean show_icons)
2142 {
2143 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2144 
2145 	if (show_icons == selector->priv->show_icons)
2146 		return;
2147 
2148 	selector->priv->show_icons = show_icons;
2149 
2150 	g_object_notify (G_OBJECT (selector), "show-icons");
2151 
2152 	source_selector_build_model (selector);
2153 }
2154 
2155 /**
2156  * e_source_selector_get_show_toggles:
2157  * @selector: an #ESourceSelector
2158  *
2159  * Returns whether toggles are shown next to data sources.
2160  *
2161  * Returns: %TRUE if toggles are being shown
2162  *
2163  * Since: 3.6
2164  **/
2165 gboolean
e_source_selector_get_show_toggles(ESourceSelector * selector)2166 e_source_selector_get_show_toggles (ESourceSelector *selector)
2167 {
2168 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
2169 
2170 	return selector->priv->show_toggles;
2171 }
2172 
2173 /**
2174  * e_source_selector_set_show_toggles:
2175  * @selector: an #ESourceSelector
2176  * @show_toggles: whether to show toggles
2177  *
2178  * Sets whether to show toggles next to data sources.
2179  *
2180  * Since: 3.6
2181  **/
2182 void
e_source_selector_set_show_toggles(ESourceSelector * selector,gboolean show_toggles)2183 e_source_selector_set_show_toggles (ESourceSelector *selector,
2184                                    gboolean show_toggles)
2185 {
2186 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2187 
2188 	if (show_toggles == selector->priv->show_toggles)
2189 		return;
2190 
2191 	selector->priv->show_toggles = show_toggles;
2192 
2193 	g_object_notify (G_OBJECT (selector), "show-toggles");
2194 
2195 	source_selector_build_model (selector);
2196 }
2197 
2198 /* Helper for e_source_selector_get_selection() */
2199 static gboolean
source_selector_check_selected(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)2200 source_selector_check_selected (GtkTreeModel *model,
2201                                 GtkTreePath *path,
2202                                 GtkTreeIter *iter,
2203                                 gpointer user_data)
2204 {
2205 	ESource *source;
2206 
2207 	struct {
2208 		ESourceSelector *selector;
2209 		GQueue queue;
2210 	} *closure = user_data;
2211 
2212 	gtk_tree_model_get (model, iter, COLUMN_SOURCE, &source, -1);
2213 
2214 	if (e_source_selector_source_is_selected (closure->selector, source))
2215 		g_queue_push_tail (&closure->queue, g_object_ref (source));
2216 
2217 	g_object_unref (source);
2218 
2219 	return FALSE;
2220 }
2221 
2222 /**
2223  * e_source_selector_get_selection:
2224  * @selector: an #ESourceSelector
2225  *
2226  * Returns a list of selected sources, i.e. those that were enabled through
2227  * the corresponding checkboxes in the tree.  The sources are ordered as they
2228  * appear in @selector.
2229  *
2230  * The sources returned in the list are referenced for thread-safety.
2231  * They must each be unreferenced with g_object_unref() when finished
2232  * with them.  Free the returned list itself with g_list_free().
2233  *
2234  * An easy way to free the list properly in one step is as follows:
2235  *
2236  * |[
2237  *   g_list_free_full (list, g_object_unref);
2238  * ]|
2239  *
2240  * Returns: a ordered list of selected sources
2241  **/
2242 GList *
e_source_selector_get_selection(ESourceSelector * selector)2243 e_source_selector_get_selection (ESourceSelector *selector)
2244 {
2245 	struct {
2246 		ESourceSelector *selector;
2247 		GQueue queue;
2248 	} closure;
2249 
2250 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
2251 
2252 	closure.selector = selector;
2253 	g_queue_init (&closure.queue);
2254 
2255 	gtk_tree_model_foreach (
2256 		gtk_tree_view_get_model (GTK_TREE_VIEW (selector)),
2257 		(GtkTreeModelForeachFunc) source_selector_check_selected,
2258 		&closure);
2259 
2260 	return g_queue_peek_head_link (&closure.queue);
2261 }
2262 
2263 struct CountData {
2264 	ESourceSelector *selector;
2265 	guint count;
2266 	gboolean selected;
2267 };
2268 
2269 static gboolean
source_selector_count_sources(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)2270 source_selector_count_sources (GtkTreeModel *model,
2271 			       GtkTreePath *path,
2272 			       GtkTreeIter *iter,
2273 			       gpointer user_data)
2274 {
2275 	struct CountData *cd = user_data;
2276 	ESource *source;
2277 
2278 	gtk_tree_model_get (model, iter, COLUMN_SOURCE, &source, -1);
2279 
2280 	if (e_source_has_extension (source, e_source_selector_get_extension_name (cd->selector))) {
2281 		if (cd->selected) {
2282 			if (e_source_selector_source_is_selected (cd->selector, source))
2283 				cd->count++;
2284 		} else {
2285 			cd->count++;
2286 		}
2287 	}
2288 
2289 	g_object_unref (source);
2290 
2291 	return FALSE;
2292 }
2293 
2294 /**
2295  * e_source_selector_count_total:
2296  * @selector: an #ESourceSelector
2297  *
2298  * Counts how many ESource-s are shown in the @selector.
2299  *
2300  * Returns: How many ESource-s are shown in the @selector.
2301  *
2302  * Since: 3.20
2303  **/
2304 guint
e_source_selector_count_total(ESourceSelector * selector)2305 e_source_selector_count_total (ESourceSelector *selector)
2306 {
2307 	struct CountData cd;
2308 
2309 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), 0);
2310 
2311 	cd.selector = selector;
2312 	cd.count = 0;
2313 	cd.selected = FALSE;
2314 
2315 	gtk_tree_model_foreach (
2316 		gtk_tree_view_get_model (GTK_TREE_VIEW (selector)),
2317 		source_selector_count_sources, &cd);
2318 
2319 	return cd.count;
2320 }
2321 
2322 /**
2323  * e_source_selector_count_selected:
2324  * @selector: an #ESourceSelector
2325  *
2326  * Counts how many ESource-s are selected in the @selector.
2327  *
2328  * Returns: How many ESource-s are selected in the @selector.
2329  *
2330  * Since: 3.20
2331  **/
2332 guint
e_source_selector_count_selected(ESourceSelector * selector)2333 e_source_selector_count_selected (ESourceSelector *selector)
2334 {
2335 	struct CountData cd;
2336 
2337 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), 0);
2338 
2339 	cd.selector = selector;
2340 	cd.count = 0;
2341 	cd.selected = TRUE;
2342 
2343 	gtk_tree_model_foreach (
2344 		gtk_tree_view_get_model (GTK_TREE_VIEW (selector)),
2345 		source_selector_count_sources, &cd);
2346 
2347 	return cd.count;
2348 }
2349 
2350 /**
2351  * e_source_selector_select_source:
2352  * @selector: An #ESourceSelector widget
2353  * @source: An #ESource.
2354  *
2355  * Select @source in @selector.
2356  **/
2357 void
e_source_selector_select_source(ESourceSelector * selector,ESource * source)2358 e_source_selector_select_source (ESourceSelector *selector,
2359                                  ESource *source)
2360 {
2361 	ESourceSelectorClass *class;
2362 	GtkTreeRowReference *reference;
2363 	GHashTable *source_index;
2364 
2365 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2366 	g_return_if_fail (E_IS_SOURCE (source));
2367 
2368 	/* Make sure the ESource is in our tree model. */
2369 	source_index = selector->priv->source_index;
2370 	reference = g_hash_table_lookup (source_index, source);
2371 	g_return_if_fail (gtk_tree_row_reference_valid (reference));
2372 
2373 	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
2374 	g_return_if_fail (class != NULL);
2375 	g_return_if_fail (class->set_source_selected != NULL);
2376 
2377 	if (class->set_source_selected (selector, source, TRUE)) {
2378 		g_signal_emit (selector, signals[SOURCE_SELECTED], 0, source);
2379 		g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
2380 	}
2381 }
2382 
2383 /**
2384  * e_source_selector_unselect_source:
2385  * @selector: An #ESourceSelector widget
2386  * @source: An #ESource.
2387  *
2388  * Unselect @source in @selector.
2389  **/
2390 void
e_source_selector_unselect_source(ESourceSelector * selector,ESource * source)2391 e_source_selector_unselect_source (ESourceSelector *selector,
2392                                    ESource *source)
2393 {
2394 	ESourceSelectorClass *class;
2395 	GtkTreeRowReference *reference;
2396 	GHashTable *source_index;
2397 
2398 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2399 	g_return_if_fail (E_IS_SOURCE (source));
2400 
2401 	/* Make sure the ESource is in our tree model. */
2402 	source_index = selector->priv->source_index;
2403 	reference = g_hash_table_lookup (source_index, source);
2404 
2405 	/* can be NULL when the source was just removed */
2406 	if (!reference)
2407 		return;
2408 
2409 	g_return_if_fail (gtk_tree_row_reference_valid (reference));
2410 
2411 	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
2412 	g_return_if_fail (class != NULL);
2413 	g_return_if_fail (class->set_source_selected != NULL);
2414 
2415 	if (class->set_source_selected (selector, source, FALSE)) {
2416 		g_signal_emit (selector, signals[SOURCE_UNSELECTED], 0, source);
2417 		g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
2418 	}
2419 }
2420 
2421 /**
2422  * e_source_selector_select_exclusive:
2423  * @selector: An #ESourceSelector widget
2424  * @source: An #ESource.
2425  *
2426  * Select @source in @selector and unselect all others.
2427  *
2428  * Since: 2.30
2429  **/
2430 void
e_source_selector_select_exclusive(ESourceSelector * selector,ESource * source)2431 e_source_selector_select_exclusive (ESourceSelector *selector,
2432                                     ESource *source)
2433 {
2434 	ESourceSelectorClass *class;
2435 	GHashTable *source_index;
2436 	GHashTableIter iter;
2437 	gpointer key;
2438 	gboolean any_changed = FALSE;
2439 
2440 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2441 	g_return_if_fail (E_IS_SOURCE (source));
2442 
2443 	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
2444 	g_return_if_fail (class != NULL);
2445 	g_return_if_fail (class->set_source_selected != NULL);
2446 
2447 	source_index = selector->priv->source_index;
2448 	g_hash_table_iter_init (&iter, source_index);
2449 
2450 	while (g_hash_table_iter_next (&iter, &key, NULL)) {
2451 		gboolean selected = e_source_equal (key, source);
2452 		if (class->set_source_selected (selector, key, selected)) {
2453 			any_changed = TRUE;
2454 			if (selected)
2455 				g_signal_emit (selector, signals[SOURCE_SELECTED], 0, key);
2456 			else
2457 				g_signal_emit (selector, signals[SOURCE_UNSELECTED], 0, key);
2458 		}
2459 	}
2460 
2461 	if (any_changed)
2462 		g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
2463 }
2464 
2465 /**
2466  * e_source_selector_select_all:
2467  * @selector: An #ESourceSelector widget
2468  *
2469  * Selects all ESource-s in the @selector.
2470  *
2471  * Since: 3.20
2472  **/
2473 void
e_source_selector_select_all(ESourceSelector * selector)2474 e_source_selector_select_all (ESourceSelector *selector)
2475 {
2476 	ESourceSelectorClass *class;
2477 	GHashTable *source_index;
2478 	GHashTableIter iter;
2479 	gpointer key;
2480 	gboolean any_changed = FALSE;
2481 
2482 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2483 
2484 	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
2485 	g_return_if_fail (class != NULL);
2486 	g_return_if_fail (class->set_source_selected != NULL);
2487 
2488 	source_index = selector->priv->source_index;
2489 	g_hash_table_iter_init (&iter, source_index);
2490 
2491 	while (g_hash_table_iter_next (&iter, &key, NULL)) {
2492 		if (class->set_source_selected (selector, key, TRUE)) {
2493 			any_changed = TRUE;
2494 			g_signal_emit (selector, signals[SOURCE_SELECTED], 0, key);
2495 		}
2496 	}
2497 
2498 	if (any_changed)
2499 		g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
2500 }
2501 
2502 /**
2503  * e_source_selector_source_is_selected:
2504  * @selector: An #ESourceSelector widget
2505  * @source: An #ESource.
2506  *
2507  * Check whether @source is selected in @selector.
2508  *
2509  * Returns: %TRUE if @source is currently selected, %FALSE otherwise.
2510  **/
2511 gboolean
e_source_selector_source_is_selected(ESourceSelector * selector,ESource * source)2512 e_source_selector_source_is_selected (ESourceSelector *selector,
2513                                       ESource *source)
2514 {
2515 	ESourceSelectorClass *class;
2516 	GtkTreeRowReference *reference;
2517 	GHashTable *source_index;
2518 
2519 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
2520 	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
2521 
2522 	/* Make sure the ESource is in our tree model. */
2523 	source_index = selector->priv->source_index;
2524 	reference = g_hash_table_lookup (source_index, source);
2525 
2526 	/* Can be NULL when the source was just removed */
2527 	if (!reference)
2528 		return FALSE;
2529 
2530 	g_return_val_if_fail (gtk_tree_row_reference_valid (reference), FALSE);
2531 
2532 	class = E_SOURCE_SELECTOR_GET_CLASS (selector);
2533 	g_return_val_if_fail (class != NULL, FALSE);
2534 	g_return_val_if_fail (class->get_source_selected != NULL, FALSE);
2535 
2536 	return class->get_source_selected (selector, source);
2537 }
2538 
2539 /**
2540  * e_source_selector_edit_primary_selection:
2541  * @selector: An #ESourceSelector widget
2542  *
2543  * Allows the user to rename the primary selected source by opening an
2544  * entry box directly in @selector.
2545  *
2546  * Since: 2.26
2547  **/
2548 void
e_source_selector_edit_primary_selection(ESourceSelector * selector)2549 e_source_selector_edit_primary_selection (ESourceSelector *selector)
2550 {
2551 	GtkTreeRowReference *reference;
2552 	GtkTreeSelection *selection;
2553 	GtkTreeViewColumn *column;
2554 	GtkCellRenderer *renderer;
2555 	GtkTreeView *tree_view;
2556 	GtkTreeModel *model;
2557 	GtkTreePath *path = NULL;
2558 	GtkTreeIter iter;
2559 	GList *list;
2560 
2561 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2562 
2563 	tree_view = GTK_TREE_VIEW (selector);
2564 	column = gtk_tree_view_get_column (tree_view, 0);
2565 	reference = selector->priv->saved_primary_selection;
2566 	selection = gtk_tree_view_get_selection (tree_view);
2567 
2568 	if (reference != NULL)
2569 		path = gtk_tree_row_reference_get_path (reference);
2570 	else if (gtk_tree_selection_get_selected (selection, &model, &iter))
2571 		path = gtk_tree_model_get_path (model, &iter);
2572 
2573 	if (path == NULL)
2574 		return;
2575 
2576 	/* XXX Because we stuff three renderers in a single column,
2577 	 *     we have to manually hunt for the text renderer. */
2578 	renderer = NULL;
2579 	list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
2580 	while (list != NULL) {
2581 		renderer = list->data;
2582 		if (GTK_IS_CELL_RENDERER_TEXT (renderer))
2583 			break;
2584 		list = g_list_delete_link (list, list);
2585 	}
2586 	g_list_free (list);
2587 
2588 	/* Make the text cell renderer editable, but only temporarily.
2589 	 * We don't want editing to be activated by simply clicking on
2590 	 * the source name.  Too easy for accidental edits to occur. */
2591 	g_object_set (renderer, "editable", TRUE, NULL);
2592 	gtk_tree_view_expand_to_path (tree_view, path);
2593 	gtk_tree_view_set_cursor_on_cell (
2594 		tree_view, path, column, renderer, TRUE);
2595 	g_object_set (renderer, "editable", FALSE, NULL);
2596 
2597 	gtk_tree_path_free (path);
2598 }
2599 
2600 /**
2601  * e_source_selector_ref_primary_selection:
2602  * @selector: An #ESourceSelector widget
2603  *
2604  * Get the primary selected source.  The primary selection is the one that is
2605  * highlighted through the normal #GtkTreeView selection mechanism (as opposed
2606  * to the "normal" selection, which is the set of source whose checkboxes are
2607  * checked).
2608  *
2609  * The returned #ESource is referenced for thread-safety and must be
2610  * unreferenced with g_object_unref() when finished with it.
2611  *
2612  * Returns: The selected source.
2613  *
2614  * Since: 3.6
2615  **/
2616 ESource *
e_source_selector_ref_primary_selection(ESourceSelector * selector)2617 e_source_selector_ref_primary_selection (ESourceSelector *selector)
2618 {
2619 	ESource *source;
2620 	GtkTreeRowReference *reference;
2621 	GtkTreeSelection *selection;
2622 	GtkTreeView *tree_view;
2623 	GtkTreeModel *model;
2624 	GtkTreeIter iter;
2625 	const gchar *extension_name;
2626 	gboolean have_iter = FALSE;
2627 
2628 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
2629 
2630 	tree_view = GTK_TREE_VIEW (selector);
2631 	model = gtk_tree_view_get_model (tree_view);
2632 	selection = gtk_tree_view_get_selection (tree_view);
2633 
2634 	reference = selector->priv->saved_primary_selection;
2635 
2636 	if (gtk_tree_row_reference_valid (reference)) {
2637 		GtkTreePath *path;
2638 
2639 		path = gtk_tree_row_reference_get_path (reference);
2640 		have_iter = gtk_tree_model_get_iter (model, &iter, path);
2641 		gtk_tree_path_free (path);
2642 	}
2643 
2644 	if (!have_iter)
2645 		have_iter = gtk_tree_selection_get_selected (
2646 			selection, NULL, &iter);
2647 
2648 	if (!have_iter)
2649 		return NULL;
2650 
2651 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
2652 
2653 	extension_name = e_source_selector_get_extension_name (selector);
2654 
2655 	if (!e_source_has_extension (source, extension_name)) {
2656 		g_object_unref (source);
2657 		return NULL;
2658 	}
2659 
2660 	return source;
2661 }
2662 
2663 /**
2664  * e_source_selector_set_primary_selection:
2665  * @selector: an #ESourceSelector widget
2666  * @source: an #ESource to select
2667  *
2668  * Highlights @source in @selector.  The highlighted #ESource is called
2669  * the primary selection.
2670  *
2671  * Do not confuse this function with e_source_selector_select_source(),
2672  * which activates the check box next to an #ESource's display name in
2673  * @selector.  This function does not alter the check box.
2674  **/
2675 void
e_source_selector_set_primary_selection(ESourceSelector * selector,ESource * source)2676 e_source_selector_set_primary_selection (ESourceSelector *selector,
2677                                          ESource *source)
2678 {
2679 	GHashTable *source_index;
2680 	GtkTreeRowReference *reference;
2681 	GtkTreeSelection *selection;
2682 	GtkTreeView *tree_view;
2683 	GtkTreePath *child_path;
2684 	GtkTreePath *parent_path;
2685 	const gchar *extension_name;
2686 
2687 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2688 	g_return_if_fail (E_IS_SOURCE (source));
2689 
2690 	tree_view = GTK_TREE_VIEW (selector);
2691 	selection = gtk_tree_view_get_selection (tree_view);
2692 
2693 	source_index = selector->priv->source_index;
2694 	reference = g_hash_table_lookup (source_index, source);
2695 
2696 	/* XXX Maybe we should return a success/fail boolean? */
2697 	if (!gtk_tree_row_reference_valid (reference))
2698 		return;
2699 
2700 	extension_name = e_source_selector_get_extension_name (selector);
2701 
2702 	/* Return silently if attempting to select a parent node
2703 	 * lacking the expected extension (e.g. On This Computer). */
2704 	if (!e_source_has_extension (source, extension_name))
2705 		return;
2706 
2707 	/* We block the signal because this all needs to be atomic */
2708 	g_signal_handlers_block_matched (
2709 		selection, G_SIGNAL_MATCH_FUNC,
2710 		0, 0, NULL, selection_changed_callback, NULL);
2711 	gtk_tree_selection_unselect_all (selection);
2712 	g_signal_handlers_unblock_matched (
2713 		selection, G_SIGNAL_MATCH_FUNC,
2714 		0, 0, NULL, selection_changed_callback, NULL);
2715 
2716 	clear_saved_primary_selection (selector);
2717 
2718 	child_path = gtk_tree_row_reference_get_path (reference);
2719 
2720 	parent_path = gtk_tree_path_copy (child_path);
2721 	gtk_tree_path_up (parent_path);
2722 
2723 	if (gtk_tree_view_row_expanded (tree_view, parent_path)) {
2724 		gtk_tree_selection_select_path (selection, child_path);
2725 	} else {
2726 		selector->priv->saved_primary_selection =
2727 			gtk_tree_row_reference_copy (reference);
2728 		g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
2729 		g_object_notify (G_OBJECT (selector), "primary-selection");
2730 	}
2731 
2732 	gtk_tree_path_free (child_path);
2733 	gtk_tree_path_free (parent_path);
2734 }
2735 
2736 /**
2737  * e_source_selector_ref_source_by_iter:
2738  * @selector: an #ESourceSelector
2739  * @iter: a #GtkTreeIter
2740  *
2741  * Returns the #ESource object at @iter.
2742  *
2743  * The returned #ESource is referenced for thread-safety and must be
2744  * unreferenced with g_object_unref() when finished with it.
2745  *
2746  * Returns: the #ESource object at @iter, or %NULL
2747  *
2748  * Since: 3.8
2749  **/
2750 ESource *
e_source_selector_ref_source_by_iter(ESourceSelector * selector,GtkTreeIter * iter)2751 e_source_selector_ref_source_by_iter (ESourceSelector *selector,
2752                                       GtkTreeIter *iter)
2753 {
2754 	ESource *source = NULL;
2755 	GtkTreeModel *model;
2756 
2757 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
2758 	g_return_val_if_fail (iter != NULL, NULL);
2759 
2760 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
2761 
2762 	gtk_tree_model_get (model, iter, COLUMN_SOURCE, &source, -1);
2763 
2764 	return source;
2765 }
2766 
2767 /**
2768  * e_source_selector_ref_source_by_path:
2769  * @selector: an #ESourceSelector
2770  * @path: a #GtkTreePath
2771  *
2772  * Returns the #ESource object at @path, or %NULL if @path is invalid.
2773  *
2774  * The returned #ESource is referenced for thread-safety and must be
2775  * unreferenced with g_object_unref() when finished with it.
2776  *
2777  * Returns: the #ESource object at @path, or %NULL
2778  *
2779  * Since: 3.6
2780  **/
2781 ESource *
e_source_selector_ref_source_by_path(ESourceSelector * selector,GtkTreePath * path)2782 e_source_selector_ref_source_by_path (ESourceSelector *selector,
2783                                       GtkTreePath *path)
2784 {
2785 	ESource *source = NULL;
2786 	GtkTreeModel *model;
2787 	GtkTreeIter iter;
2788 
2789 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
2790 	g_return_val_if_fail (path != NULL, NULL);
2791 
2792 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
2793 
2794 	if (gtk_tree_model_get_iter (model, &iter, path))
2795 		gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
2796 
2797 	return source;
2798 }
2799 
2800 /**
2801  * e_source_selector_queue_write:
2802  * @selector: an #ESourceSelector
2803  * @source: an #ESource with changes to be written
2804  *
2805  * Queues a main loop idle callback to write changes to @source back to
2806  * the D-Bus registry service.
2807  *
2808  * Since: 3.6
2809  **/
2810 void
e_source_selector_queue_write(ESourceSelector * selector,ESource * source)2811 e_source_selector_queue_write (ESourceSelector *selector,
2812                                ESource *source)
2813 {
2814 	GSource *idle_source;
2815 	GHashTable *pending_writes;
2816 	GMainContext *main_context;
2817 	AsyncContext *async_context;
2818 
2819 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2820 	g_return_if_fail (E_IS_SOURCE (source));
2821 
2822 	main_context = selector->priv->main_context;
2823 	pending_writes = selector->priv->pending_writes;
2824 
2825 	idle_source = g_hash_table_lookup (pending_writes, source);
2826 	if (idle_source != NULL && !g_source_is_destroyed (idle_source))
2827 		return;
2828 
2829 	async_context = g_slice_new0 (AsyncContext);
2830 	async_context->selector = g_object_ref (selector);
2831 	async_context->source = g_object_ref (source);
2832 
2833 	/* Set a higher priority so this idle source runs before our
2834 	 * source_selector_cancel_write() signal handler, which will
2835 	 * cancel this idle source.  Cancellation is the right thing
2836 	 * to do when receiving changes from OTHER registry clients,
2837 	 * but we don't want to cancel our own changes.
2838 	 *
2839 	 * XXX This might be an argument for using etags.
2840 	 */
2841 	idle_source = g_idle_source_new ();
2842 	g_hash_table_insert (
2843 		pending_writes,
2844 		g_object_ref (source),
2845 		g_source_ref (idle_source));
2846 	g_source_set_callback (
2847 		idle_source,
2848 		source_selector_write_idle_cb,
2849 		async_context,
2850 		(GDestroyNotify) async_context_free);
2851 	g_source_set_priority (idle_source, G_PRIORITY_HIGH_IDLE);
2852 	g_source_attach (idle_source, main_context);
2853 	g_source_unref (idle_source);
2854 }
2855 
2856 static void
source_selector_sort_sibling(ESourceSelector * selector,GtkTreeModel * model,GtkTreeIter * changed_child)2857 source_selector_sort_sibling (ESourceSelector *selector,
2858 			      GtkTreeModel *model,
2859 			      GtkTreeIter *changed_child)
2860 {
2861 	GtkTreeIter iter, dest_iter;
2862 	ESource *child_source = NULL;
2863 	gboolean changed = FALSE, insert_before;
2864 
2865 	gtk_tree_model_get (model, changed_child, COLUMN_SOURCE, &child_source, -1);
2866 
2867 	if (!child_source)
2868 		return;
2869 
2870 	insert_before = TRUE;
2871 	iter = *changed_child;
2872 
2873 	while (gtk_tree_model_iter_previous (model, &iter)) {
2874 		ESource *source = NULL;
2875 		gint cmp_value;
2876 
2877 		gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
2878 
2879 		cmp_value = e_util_source_compare_for_sort (source, child_source);
2880 
2881 		g_clear_object (&source);
2882 
2883 		if (cmp_value <= 0)
2884 			break;
2885 
2886 		dest_iter = iter;
2887 		changed = TRUE;
2888 	}
2889 
2890 	if (!changed) {
2891 		insert_before = FALSE;
2892 		iter = *changed_child;
2893 
2894 		while (gtk_tree_model_iter_next (model, &iter)) {
2895 			ESource *source = NULL;
2896 			gint cmp_value;
2897 
2898 			gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
2899 
2900 			cmp_value = e_util_source_compare_for_sort (child_source, source);
2901 
2902 			g_clear_object (&source);
2903 
2904 			if (cmp_value <= 0)
2905 				break;
2906 
2907 			dest_iter = iter;
2908 			changed = TRUE;
2909 		}
2910 	}
2911 
2912 	if (changed) {
2913 		GtkTreeStore *tree_store = GTK_TREE_STORE (model);
2914 		GtkTreeSelection *selection;
2915 		GtkTreeRowReference *reference;
2916 		GtkTreeIter new_child;
2917 		GtkTreePath *path;
2918 		gboolean been_selected;
2919 
2920 		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));
2921 		been_selected = gtk_tree_selection_iter_is_selected (selection, changed_child);
2922 
2923 		gtk_tree_store_remove (tree_store, changed_child);
2924 
2925 		if (insert_before)
2926 			gtk_tree_store_insert_before (tree_store, &new_child, NULL, &dest_iter);
2927 		else
2928 			gtk_tree_store_insert_after (tree_store, &new_child, NULL, &dest_iter);
2929 
2930 		g_hash_table_remove (selector->priv->source_index, child_source);
2931 
2932 		path = gtk_tree_model_get_path (model, &new_child);
2933 		reference = gtk_tree_row_reference_new (model, path);
2934 		g_hash_table_insert (selector->priv->source_index, g_object_ref (child_source), reference);
2935 		gtk_tree_path_free (path);
2936 
2937 		e_source_selector_update_row (selector, child_source);
2938 
2939 		if (been_selected)
2940 			gtk_tree_selection_select_iter (selection, &new_child);
2941 	}
2942 
2943 	g_object_unref (child_source);
2944 }
2945 
2946 /**
2947  * e_source_selector_update_row:
2948  * @selector: an #ESourceSelector
2949  * @source: an #ESource
2950  *
2951  * Updates the corresponding #GtkTreeModel row for @source.
2952  *
2953  * This function is public so it can be called from subclasses like
2954  * #EClientSelector.
2955  *
2956  * Since: 3.8
2957  **/
2958 void
e_source_selector_update_row(ESourceSelector * selector,ESource * source)2959 e_source_selector_update_row (ESourceSelector *selector,
2960                               ESource *source)
2961 {
2962 	GHashTable *source_index;
2963 	ESourceExtension *extension = NULL;
2964 	GtkTreeRowReference *reference;
2965 	GtkTreeModel *model;
2966 	GtkTreePath *path;
2967 	GtkTreeIter iter;
2968 	const gchar *extension_name;
2969 	const gchar *display_name;
2970 	gboolean selected;
2971 
2972 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
2973 	g_return_if_fail (E_IS_SOURCE (source));
2974 
2975 	source_index = selector->priv->source_index;
2976 	reference = g_hash_table_lookup (source_index, source);
2977 
2978 	/* This function runs when ANY ESource in the registry changes.
2979 	 * If the ESource is not in our tree model then return silently. */
2980 	if (reference == NULL)
2981 		return;
2982 
2983 	/* If we do have a row reference, it should be valid. */
2984 	g_return_if_fail (gtk_tree_row_reference_valid (reference));
2985 
2986 	model = gtk_tree_row_reference_get_model (reference);
2987 	path = gtk_tree_row_reference_get_path (reference);
2988 	gtk_tree_model_get_iter (model, &iter, path);
2989 	gtk_tree_path_free (path);
2990 
2991 	display_name = e_source_get_display_name (source);
2992 
2993 	extension_name = e_source_selector_get_extension_name (selector);
2994 	selected = e_source_selector_source_is_selected (selector, source);
2995 
2996 	if (e_source_has_extension (source, extension_name))
2997 		extension = e_source_get_extension (source, extension_name);
2998 
2999 	if (extension != NULL) {
3000 		ESource *current_source = NULL;
3001 		GdkRGBA rgba;
3002 		const gchar *color_spec = NULL;
3003 		const gchar *icon_name;
3004 		gboolean show_color;
3005 		gboolean show_icons;
3006 		gboolean show_toggle;
3007 		guint old_sort_order = 0, new_sort_order = 0;
3008 
3009 		show_color =
3010 			E_IS_SOURCE_SELECTABLE (extension) &&
3011 			e_source_selector_get_show_colors (selector);
3012 
3013 		if (show_color)
3014 			color_spec = e_source_selectable_get_color (
3015 				E_SOURCE_SELECTABLE (extension));
3016 
3017 		if (color_spec != NULL && *color_spec != '\0')
3018 			show_color = gdk_rgba_parse (&rgba, color_spec);
3019 
3020 		show_icons = e_source_selector_get_show_icons (selector);
3021 		icon_name = source_selector_get_icon_name (selector, source);
3022 
3023 		show_toggle = e_source_selector_get_show_toggles (selector);
3024 
3025 		gtk_tree_model_get (model, &iter,
3026 			COLUMN_SORT_ORDER, &old_sort_order,
3027 			COLUMN_SOURCE, &current_source,
3028 			-1);
3029 
3030 		if (E_IS_SOURCE_SELECTABLE (extension))
3031 			new_sort_order = e_source_selectable_get_order (E_SOURCE_SELECTABLE (extension));
3032 		else if (E_IS_SOURCE_ADDRESS_BOOK (extension))
3033 			new_sort_order = e_source_address_book_get_order (E_SOURCE_ADDRESS_BOOK (extension));
3034 		else
3035 			new_sort_order = old_sort_order;
3036 
3037 		gtk_tree_store_set (
3038 			GTK_TREE_STORE (model), &iter,
3039 			COLUMN_NAME, display_name,
3040 			COLUMN_COLOR, show_color ? &rgba : NULL,
3041 			COLUMN_ACTIVE, selected,
3042 			COLUMN_ICON_NAME, icon_name,
3043 			COLUMN_SHOW_COLOR, show_color,
3044 			COLUMN_SHOW_ICONS, show_icons,
3045 			COLUMN_SHOW_TOGGLE, show_toggle,
3046 			COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
3047 			COLUMN_SOURCE, source,
3048 			COLUMN_SORT_ORDER, new_sort_order,
3049 			-1);
3050 
3051 		/* The 'current_source' is NULL on newly added rows and non-NULL when refreshing existing row */
3052 		if (new_sort_order != old_sort_order && current_source != NULL)
3053 			source_selector_sort_sibling (selector, model, &iter);
3054 
3055 		g_clear_object (&current_source);
3056 	} else {
3057 		gtk_tree_store_set (
3058 			GTK_TREE_STORE (model), &iter,
3059 			COLUMN_NAME, display_name,
3060 			COLUMN_COLOR, NULL,
3061 			COLUMN_ACTIVE, FALSE,
3062 			COLUMN_ICON_NAME, NULL,
3063 			COLUMN_SHOW_COLOR, FALSE,
3064 			COLUMN_SHOW_ICONS, FALSE,
3065 			COLUMN_SHOW_TOGGLE, FALSE,
3066 			COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
3067 			COLUMN_SOURCE, source,
3068 			-1);
3069 	}
3070 }
3071 
3072 /**
3073  * e_source_selector_update_all_rows:
3074  * @selector: an #ESourceSelector
3075  *
3076  * Calls e_source_selector_update_row() for each #ESource being shown by
3077  * @selector, according to the #ESourceSelector:extension_name property.
3078  *
3079  * Since: 3.10
3080  **/
3081 void
e_source_selector_update_all_rows(ESourceSelector * selector)3082 e_source_selector_update_all_rows (ESourceSelector *selector)
3083 {
3084 	ESourceRegistry *registry;
3085 	GList *list, *link;
3086 	const gchar *extension_name;
3087 
3088 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
3089 
3090 	registry = e_source_selector_get_registry (selector);
3091 	extension_name = e_source_selector_get_extension_name (selector);
3092 
3093 	list = e_source_registry_list_sources (registry, extension_name);
3094 
3095 	for (link = list; link != NULL; link = g_list_next (link)) {
3096 		ESource *source = E_SOURCE (link->data);
3097 		e_source_selector_update_row (selector, source);
3098 	}
3099 
3100 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
3101 }
3102 
3103 static gboolean
e_source_selector_get_source_iter(ESourceSelector * selector,ESource * source,GtkTreeIter * iter,GtkTreeModel ** out_model)3104 e_source_selector_get_source_iter (ESourceSelector *selector,
3105 				   ESource *source,
3106 				   GtkTreeIter *iter,
3107 				   GtkTreeModel **out_model)
3108 {
3109 	GtkTreeRowReference *reference;
3110 	GtkTreeModel *model;
3111 	GtkTreePath *path;
3112 	gboolean found;
3113 
3114 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
3115 	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
3116 	g_return_val_if_fail (iter, FALSE);
3117 
3118 	reference = g_hash_table_lookup (selector->priv->source_index, source);
3119 
3120 	/* If the ESource is not in our tree model then return silently. */
3121 	if (!reference)
3122 		return FALSE;
3123 
3124 	/* If we do have a row reference, it should be valid. */
3125 	g_return_val_if_fail (gtk_tree_row_reference_valid (reference), FALSE);
3126 
3127 	model = gtk_tree_row_reference_get_model (reference);
3128 	path = gtk_tree_row_reference_get_path (reference);
3129 	found = gtk_tree_model_get_iter (model, iter, path);
3130 	gtk_tree_path_free (path);
3131 
3132 	if (found && out_model)
3133 		*out_model = model;
3134 
3135 	return found;
3136 }
3137 
3138 /**
3139  * e_source_selector_set_source_tooltip:
3140  * @selector: an #ESourceSelector
3141  * @source: an #ESource for which to set the tooltip
3142  *
3143  * Updates tooltip for the given @source.
3144  *
3145  * Since: 3.16
3146  **/
3147 void
e_source_selector_set_source_tooltip(ESourceSelector * selector,ESource * source,const gchar * tooltip)3148 e_source_selector_set_source_tooltip (ESourceSelector *selector,
3149 				      ESource *source,
3150 				      const gchar *tooltip)
3151 {
3152 	GtkTreeModel *model = NULL;
3153 	GtkTreeIter iter;
3154 	gchar *current_tooltip = NULL;
3155 
3156 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
3157 	g_return_if_fail (E_IS_SOURCE (source));
3158 
3159 	if (!e_source_selector_get_source_iter (selector, source, &iter, &model))
3160 		return;
3161 
3162 	gtk_tree_model_get (model, &iter,
3163 		COLUMN_TOOLTIP, &current_tooltip,
3164 		-1);
3165 
3166 	/* This avoids possible recursion with ATK enabled */
3167 	if (e_util_strcmp0 (current_tooltip, tooltip) != 0) {
3168 		gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
3169 			COLUMN_TOOLTIP, tooltip && *tooltip ? tooltip : NULL,
3170 			-1);
3171 	}
3172 
3173 	g_free (current_tooltip);
3174 }
3175 
3176 /**
3177  * e_source_selector_dup_source_tooltip:
3178  * @selector: an #ESourceSelector
3179  * @source: an #ESource for which to read the tooltip
3180  *
3181  * Returns: Current tooltip for the given @source. Free the returned
3182  *    string with g_free() when done with it.
3183  *
3184  * Since: 3.16
3185  **/
3186 gchar *
e_source_selector_dup_source_tooltip(ESourceSelector * selector,ESource * source)3187 e_source_selector_dup_source_tooltip (ESourceSelector *selector,
3188 				      ESource *source)
3189 {
3190 	GtkTreeModel *model = NULL;
3191 	GtkTreeIter iter;
3192 	gchar *tooltip = NULL;
3193 
3194 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
3195 	g_return_val_if_fail (E_IS_SOURCE (source), NULL);
3196 
3197 	if (!e_source_selector_get_source_iter (selector, source, &iter, &model))
3198 		return NULL;
3199 
3200 	gtk_tree_model_get (
3201 		model, &iter,
3202 		COLUMN_TOOLTIP, &tooltip,
3203 		-1);
3204 
3205 	return tooltip;
3206 }
3207 
3208 /**
3209  * e_source_selector_set_source_is_busy:
3210  * @selector: an #ESourceSelector
3211  * @source: an #ESource for which to set the is-busy status
3212  *
3213  * Updates the is-busy flag status for the given @source.
3214  *
3215  * Since: 3.16
3216  **/
3217 void
e_source_selector_set_source_is_busy(ESourceSelector * selector,ESource * source,gboolean is_busy)3218 e_source_selector_set_source_is_busy (ESourceSelector *selector,
3219 				      ESource *source,
3220 				      gboolean is_busy)
3221 {
3222 	GtkTreeModel *model = NULL;
3223 	GtkTreeIter iter;
3224 	gboolean old_is_busy = FALSE;
3225 
3226 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
3227 	g_return_if_fail (E_IS_SOURCE (source));
3228 
3229 	if (!e_source_selector_get_source_iter (selector, source, &iter, &model))
3230 		return;
3231 
3232 	gtk_tree_model_get (model, &iter,
3233 		COLUMN_IS_BUSY, &old_is_busy,
3234 		-1);
3235 
3236 	if ((old_is_busy ? 1 : 0) == (is_busy ? 1 : 0))
3237 		return;
3238 
3239 	gtk_tree_store_set (
3240 		GTK_TREE_STORE (model), &iter,
3241 		COLUMN_IS_BUSY, is_busy,
3242 		-1);
3243 
3244 	if (is_busy)
3245 		source_selector_inc_busy_sources (selector);
3246 	else
3247 		source_selector_dec_busy_sources (selector);
3248 }
3249 
3250 /**
3251  * e_source_selector_get_source_is_busy:
3252  * @selector: an #ESourceSelector
3253  * @source: an #ESource for which to read the is-busy status
3254  *
3255  * Returns: Current is-busy flag status for the given @source.
3256  *
3257  * Since: 3.16
3258  **/
3259 gboolean
e_source_selector_get_source_is_busy(ESourceSelector * selector,ESource * source)3260 e_source_selector_get_source_is_busy (ESourceSelector *selector,
3261 				      ESource *source)
3262 {
3263 	GtkTreeModel *model = NULL;
3264 	GtkTreeIter iter;
3265 	gboolean is_busy = FALSE;
3266 
3267 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
3268 	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
3269 
3270 	if (!e_source_selector_get_source_iter (selector, source, &iter, &model))
3271 		return FALSE;
3272 
3273 	gtk_tree_model_get (
3274 		model, &iter,
3275 		COLUMN_IS_BUSY, &is_busy,
3276 		-1);
3277 
3278 	return is_busy;
3279 }
3280 
3281 /**
3282  * e_source_selector_set_source_connection_status:
3283  * @selector: an #ESourceSelector
3284  * @source: an #ESource for which to set the connection status
3285  * @value: the value to set
3286  *
3287  * Sets connection status for the @source. It's not interpreted by the @selector,
3288  * it only saves it into the model. Read it back with e_source_selector_get_source_connection_status().
3289  *
3290  * Since: 3.40
3291  **/
3292 void
e_source_selector_set_source_connection_status(ESourceSelector * selector,ESource * source,guint value)3293 e_source_selector_set_source_connection_status (ESourceSelector *selector,
3294 						ESource *source,
3295 						guint value)
3296 {
3297 	GtkTreeModel *model = NULL;
3298 	GtkTreeIter iter;
3299 	guint current_value = 0;
3300 
3301 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
3302 	g_return_if_fail (E_IS_SOURCE (source));
3303 
3304 	if (!e_source_selector_get_source_iter (selector, source, &iter, &model))
3305 		return;
3306 
3307 	gtk_tree_model_get (
3308 		model, &iter,
3309 		COLUMN_CONNECTION_STATUS, &current_value,
3310 		-1);
3311 
3312 	/* This avoids possible recursion with ATK enabled */
3313 	if (current_value != value) {
3314 		gtk_tree_store_set (
3315 			GTK_TREE_STORE (model), &iter,
3316 			COLUMN_CONNECTION_STATUS, value,
3317 			-1);
3318 	}
3319 }
3320 
3321 /**
3322  * e_source_selector_get_source_connection_status:
3323  * @selector: an #ESourceSelector
3324  * @source: an #ESource for which to get the stored connection status
3325  *
3326  * Gets connection status for the @source. It's not interpreted by the @selector,
3327  * it only returns what had been saved into the model with e_source_selector_set_source_connection_status().
3328  *
3329  * Returns: Value previously stored with e_source_selector_set_source_connection_status(),
3330  *    or 0 when not set or when the @source was not found.
3331  *
3332  * Since: 3.40
3333  **/
3334 guint
e_source_selector_get_source_connection_status(ESourceSelector * selector,ESource * source)3335 e_source_selector_get_source_connection_status (ESourceSelector *selector,
3336 						ESource *source)
3337 {
3338 	GtkTreeModel *model = NULL;
3339 	GtkTreeIter iter;
3340 	guint value = 0;
3341 
3342 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), 0);
3343 	g_return_val_if_fail (E_IS_SOURCE (source), 0);
3344 
3345 	if (!e_source_selector_get_source_iter (selector, source, &iter, &model))
3346 		return 0;
3347 
3348 	gtk_tree_model_get (
3349 		model, &iter,
3350 		COLUMN_CONNECTION_STATUS, &value,
3351 		-1);
3352 
3353 	return value;
3354 }
3355 
3356 static gboolean
source_selector_get_source_hidden(ESourceSelector * selector,ESource * source)3357 source_selector_get_source_hidden (ESourceSelector *selector,
3358 				   ESource *source)
3359 {
3360 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
3361 	g_return_val_if_fail (E_IS_SOURCE (source), FALSE);
3362 	g_return_val_if_fail (e_source_get_uid (source) != NULL, FALSE);
3363 
3364 	return g_hash_table_contains (selector->priv->hidden_groups, e_source_get_uid (source));
3365 }
3366 
3367 static void
tree_show_toggled(GtkCellRendererToggle * renderer,gchar * path_str,gpointer user_data)3368 tree_show_toggled (GtkCellRendererToggle *renderer,
3369 		   gchar *path_str,
3370 		   gpointer user_data)
3371 {
3372 	GtkWidget *table = user_data;
3373 	GtkTreeModel *model;
3374 	GtkTreePath *path;
3375 	GtkTreeIter iter;
3376 
3377 	path = gtk_tree_path_new_from_string (path_str);
3378 	model = gtk_tree_view_get_model (GTK_TREE_VIEW (table));
3379 
3380 	if (gtk_tree_model_get_iter (model, &iter, path)) {
3381 		gboolean shown = TRUE;
3382 
3383 		gtk_tree_model_get (model, &iter, 2, &shown, -1);
3384 		shown = !shown;
3385 		gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, shown, -1);
3386 
3387 		/* to have buttons synced with the change */
3388 		g_signal_emit_by_name (table, "cursor-changed");
3389 	}
3390 
3391 	gtk_tree_path_free (path);
3392 }
3393 
3394 static GtkWidget *
create_tree(ESourceSelector * selector,GtkWidget ** tree)3395 create_tree (ESourceSelector *selector,
3396 	     GtkWidget **tree)
3397 {
3398 	ESourceRegistry *registry;
3399 	GtkWidget *table, *scrolled;
3400 	GtkTreeSelection *selection;
3401 	GtkCellRenderer *renderer;
3402 	GtkListStore *model;
3403 	GNode *root;
3404 
3405 	scrolled = gtk_scrolled_window_new (NULL, NULL);
3406 	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
3407 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
3408 					GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
3409 
3410 	model = gtk_list_store_new (3, G_TYPE_STRING, E_TYPE_SOURCE, G_TYPE_BOOLEAN);
3411 	table = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
3412 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (table), FALSE);
3413 
3414 	renderer = gtk_cell_renderer_toggle_new ();
3415 	g_object_set (G_OBJECT (renderer), "activatable", TRUE, NULL);
3416 	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (table), -1,
3417 						     _("Show"), renderer,
3418 						     "active", 2, NULL);
3419 	g_signal_connect (renderer, "toggled", G_CALLBACK (tree_show_toggled), table);
3420 
3421 	renderer = gtk_cell_renderer_text_new ();
3422 	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (table), -1,
3423 						     _("Group name"), renderer,
3424 						     "text", 0, NULL);
3425 
3426 	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (table));
3427 	gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
3428 
3429 	gtk_container_add (GTK_CONTAINER (scrolled), table);
3430 
3431 	*tree = table;
3432 
3433 	registry = e_source_selector_get_registry (selector);
3434 	root = e_source_registry_build_display_tree (registry, e_source_selector_get_extension_name (selector));
3435 
3436 	source_selector_sort_groups (selector, root);
3437 
3438 	if (root) {
3439 		GNode *node;
3440 
3441 		for (node = g_node_first_child (root); node; node = g_node_next_sibling (node)) {
3442 			GtkTreeIter iter;
3443 			ESource *source;
3444 
3445 			source = node->data;
3446 
3447 			if (source) {
3448 				gtk_list_store_append (model, &iter);
3449 				gtk_list_store_set (model, &iter,
3450 						0, e_source_get_display_name (source),
3451 						1, source,
3452 						2, !source_selector_get_source_hidden (selector, source),
3453 						-1);
3454 			}
3455 		}
3456 	}
3457 
3458 	e_source_registry_free_display_tree (root);
3459 
3460 	g_object_unref (model);
3461 
3462 	return scrolled;
3463 }
3464 
3465 static void
process_move_button(GtkButton * button,GtkTreeView * tree,gboolean is_up,gboolean do_move)3466 process_move_button (GtkButton *button,
3467 		     GtkTreeView *tree,
3468 		     gboolean is_up,
3469 		     gboolean do_move)
3470 {
3471 	GtkTreeModel *model;
3472 	GtkTreeSelection *selection;
3473 	GtkTreeIter iter;
3474 	gboolean enable = FALSE;
3475 
3476 	g_return_if_fail (button != NULL);
3477 	g_return_if_fail (tree != NULL);
3478 
3479 	selection = gtk_tree_view_get_selection (tree);
3480 
3481 	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3482 		gpointer ptr = NULL, ptr2;
3483 		GtkTreeIter iter2;
3484 		int i, cnt = gtk_tree_model_iter_n_children (model, NULL);
3485 		gboolean can_move = FALSE;
3486 
3487 		gtk_tree_model_get (model, &iter, 1, &ptr, -1);
3488 
3489 		for (i = 0; i < cnt; i++) {
3490 			if (!gtk_tree_model_iter_nth_child (model, &iter2, NULL, i))
3491 				break;
3492 
3493 			ptr2 = NULL;
3494 			gtk_tree_model_get (model, &iter2, 1, &ptr2, -1);
3495 
3496 			if (ptr == ptr2 || (is_up && !do_move && i > 0)) {
3497 				can_move = TRUE;
3498 				break;
3499 			}
3500 		}
3501 
3502 		if (can_move)
3503 			can_move = ((is_up && i > 0) || (!is_up && i + 1 < cnt)) && i < cnt;
3504 
3505 		if (can_move && do_move) {
3506 			i = i + (is_up ? -1 : 1);
3507 			if (gtk_tree_model_iter_nth_child (model, &iter2, NULL, i)) {
3508 				GtkTreePath *path;
3509 
3510 				gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &iter2);
3511 				gtk_tree_selection_select_iter (selection, &iter);
3512 
3513 				/* scroll to the selected cell */
3514 				path = gtk_tree_model_get_path (model, &iter);
3515 				gtk_tree_view_scroll_to_cell (tree, path, NULL, FALSE, 0.0, 0.0);
3516 				gtk_tree_path_free (path);
3517 
3518 				/* cursor has been moved to the other row */
3519 				can_move = (is_up && i > 0) || (!is_up && i + 1 < cnt);
3520 
3521 				g_signal_emit_by_name (tree, "cursor-changed");
3522 			}
3523 		}
3524 
3525 		enable = can_move;
3526 	}
3527 
3528 	if (!do_move)
3529 		gtk_widget_set_sensitive (GTK_WIDGET (button), enable);
3530 }
3531 
3532 static void
up_clicked(GtkButton * button,GtkTreeView * tree)3533 up_clicked (GtkButton *button,
3534 	    GtkTreeView *tree)
3535 {
3536 	process_move_button (button, tree, TRUE, TRUE);
3537 }
3538 
3539 static void
up_cursor_changed(GtkTreeView * tree,GtkButton * button)3540 up_cursor_changed (GtkTreeView *tree,
3541 		   GtkButton *button)
3542 {
3543 	process_move_button (button, tree, TRUE, FALSE);
3544 }
3545 
3546 static void
down_clicked(GtkButton * button,GtkTreeView * tree)3547 down_clicked (GtkButton *button,
3548 	      GtkTreeView *tree)
3549 {
3550 	process_move_button (button, tree, FALSE, TRUE);
3551 }
3552 
3553 static void
down_cursor_changed(GtkTreeView * tree,GtkButton * button)3554 down_cursor_changed (GtkTreeView *tree,
3555 		     GtkButton *button)
3556 {
3557 	process_move_button (button, tree, FALSE, FALSE);
3558 }
3559 
3560 static void
show_hide_cursor_changed(GtkTreeView * tree,GtkButton * button)3561 show_hide_cursor_changed (GtkTreeView *tree,
3562 			  GtkButton *button)
3563 {
3564 	GtkTreeModel *model;
3565 	GtkTreeSelection *selection;
3566 	GtkTreeIter iter;
3567 
3568 	g_return_if_fail (button != NULL);
3569 	g_return_if_fail (tree != NULL);
3570 
3571 	selection = gtk_tree_view_get_selection (tree);
3572 
3573 	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3574 		gboolean shown = FALSE;
3575 
3576 		gtk_tree_model_get (model, &iter, 2, &shown, -1);
3577 
3578 		gtk_button_set_label (button, shown ? _("_Hide") : _("_Show"));
3579 	}
3580 }
3581 
3582 static void
show_hide_clicked(GtkButton * button,GtkTreeView * tree)3583 show_hide_clicked (GtkButton *button,
3584 		   GtkTreeView *tree)
3585 {
3586 	GtkTreeModel *model;
3587 	GtkTreeSelection *selection;
3588 	GtkTreeIter iter;
3589 
3590 	g_return_if_fail (button != NULL);
3591 	g_return_if_fail (tree != NULL);
3592 
3593 	selection = gtk_tree_view_get_selection (tree);
3594 
3595 	if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
3596 		gboolean shown = TRUE;
3597 
3598 		gtk_tree_model_get (model, &iter, 2, &shown, -1);
3599 		shown = !shown;
3600 		gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, shown, -1);
3601 
3602 		show_hide_cursor_changed (tree, button);
3603 	}
3604 }
3605 
3606 /**
3607  * e_source_selector_manage_groups:
3608  * @selector: an #ESourceSelector
3609  *
3610  * Manages list of groups, like their order in the source selector,
3611  * and a hidden property of the group.
3612  *
3613  * Returns: Whether user confirmed changes in the dialog.
3614  *
3615  * Since: 3.20
3616  **/
3617 gboolean
e_source_selector_manage_groups(ESourceSelector * selector)3618 e_source_selector_manage_groups (ESourceSelector *selector)
3619 {
3620 	GtkWidget *dlg, *box, *pbox, *tree, *w, *w2;
3621 	gchar *txt;
3622 	gboolean confirmed = FALSE;
3623 
3624 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
3625 
3626 	w = gtk_widget_get_toplevel (GTK_WIDGET (selector));
3627 	if (!w || !gtk_widget_is_toplevel (w))
3628 		w = NULL;
3629 
3630 	dlg = gtk_dialog_new_with_buttons (_("Manage Groups"),
3631 			w ? GTK_WINDOW (w) : NULL,
3632 			GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
3633 			GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
3634 			GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
3635 			NULL);
3636 
3637 	w = gtk_dialog_get_content_area (GTK_DIALOG (dlg));
3638 	box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
3639 	gtk_container_set_border_width (GTK_CONTAINER (box), 12);
3640 	gtk_box_pack_start (GTK_BOX (w), box, TRUE, TRUE, 0);
3641 
3642 	txt = g_strconcat ("<b>", _("Available Groups:"), "</b>", NULL);
3643 	w = gtk_label_new ("");
3644 	gtk_label_set_markup (GTK_LABEL (w), txt);
3645 	g_free (txt);
3646 	gtk_misc_set_alignment (GTK_MISC (w), 0.0, 0.5);
3647 	gtk_box_pack_start (GTK_BOX (box), w, FALSE, FALSE, 2);
3648 
3649 	pbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
3650 	gtk_box_pack_start (GTK_BOX (box), pbox, TRUE, TRUE, 2);
3651 
3652 	/* space on the left */
3653 	w = gtk_label_new ("");
3654 	gtk_box_pack_start (GTK_BOX (pbox), w, FALSE, FALSE, 6);
3655 
3656 	w = create_tree (selector, &tree);
3657 	gtk_widget_set_size_request (w, 200, 240);
3658 	gtk_box_pack_start (GTK_BOX (pbox), w, TRUE, TRUE, 2);
3659 
3660 	/* box of buttons */
3661 	w2 = gtk_button_box_new (GTK_ORIENTATION_VERTICAL);
3662 	gtk_button_box_set_layout (GTK_BUTTON_BOX (w2), GTK_BUTTONBOX_START);
3663 	gtk_box_pack_start (GTK_BOX (pbox), w2, FALSE, FALSE, 2);
3664 
3665 	#define add_button(_x,_y,_cb,_cb2) \
3666 		w = (_x) ? gtk_button_new_from_icon_name (_x, GTK_ICON_SIZE_BUTTON) : gtk_button_new (); \
3667 		gtk_button_set_label (GTK_BUTTON (w), _y); \
3668 		gtk_button_set_use_underline (GTK_BUTTON (w), TRUE); \
3669 		gtk_box_pack_start (GTK_BOX (w2), w, FALSE, FALSE, 2); \
3670 		g_signal_connect (w, "clicked", (GCallback)_cb, tree); \
3671 		g_signal_connect (tree, "cursor-changed", (GCallback)_cb2, w);
3672 
3673 	add_button ("go-up", _("_Up"), up_clicked, up_cursor_changed);
3674 	add_button ("go-down", _("_Down"), down_clicked, down_cursor_changed);
3675 
3676 	add_button (NULL, _("_Show"), show_hide_clicked, show_hide_cursor_changed);
3677 	gtk_button_set_use_underline (GTK_BUTTON (w), TRUE);
3678 
3679 	#undef add_button
3680 
3681 	gtk_widget_show_all (box);
3682 
3683 	if (gtk_dialog_run (GTK_DIALOG (dlg)) == GTK_RESPONSE_ACCEPT) {
3684 		GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree));
3685 		GtkTreeIter iter;
3686 		gint ii, cnt = gtk_tree_model_iter_n_children (model, NULL);
3687 
3688 		g_hash_table_remove_all (selector->priv->hidden_groups);
3689 		g_slist_free_full (selector->priv->groups_order, g_free);
3690 		selector->priv->groups_order = NULL;
3691 
3692 		for (ii = 0; ii < cnt; ii++) {
3693 			gpointer group = NULL;
3694 			gboolean shown = TRUE;
3695 
3696 			if (!gtk_tree_model_iter_nth_child (model, &iter, NULL, ii))
3697 				break;
3698 
3699 			gtk_tree_model_get (model, &iter, 1, &group, 2, &shown, -1);
3700 
3701 			if (group) {
3702 				const gchar *uid = e_source_get_uid (group);
3703 
3704 				selector->priv->groups_order = g_slist_prepend (selector->priv->groups_order, g_strdup (uid));
3705 
3706 				if (!shown)
3707 					g_hash_table_insert (selector->priv->hidden_groups, g_strdup (uid), GINT_TO_POINTER (1));
3708 			}
3709 		}
3710 
3711 		selector->priv->groups_order = g_slist_reverse (selector->priv->groups_order);
3712 
3713 		source_selector_build_model (selector);
3714 
3715 		confirmed = TRUE;
3716 	}
3717 
3718 	gtk_widget_destroy (dlg);
3719 
3720 	return confirmed;
3721 }
3722 
3723 static gboolean
source_selector_store_value(GKeyFile * key_file,const gchar * group_key,const gchar * const * value,gsize value_length)3724 source_selector_store_value (GKeyFile *key_file,
3725 			     const gchar *group_key,
3726 			     const gchar * const *value,
3727 			     gsize value_length)
3728 {
3729 	gchar **stored;
3730 	gsize length = 0, ii;
3731 	gboolean changed = FALSE;
3732 
3733 	g_return_val_if_fail (key_file != NULL, FALSE);
3734 	g_return_val_if_fail (group_key != NULL, FALSE);
3735 
3736 	stored = g_key_file_get_string_list (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, &length, NULL);
3737 	if (stored) {
3738 		changed = value_length != length;
3739 		if (!changed) {
3740 			for (ii = 0; ii < length && !changed; ii++) {
3741 				changed = g_strcmp0 (value[ii], stored[ii]) != 0;
3742 			}
3743 		}
3744 
3745 		g_strfreev (stored);
3746 	} else {
3747 		changed = value != NULL;
3748 	}
3749 
3750 	if (changed) {
3751 		if (value)
3752 			g_key_file_set_string_list (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, value, value_length);
3753 		else
3754 			changed = g_key_file_remove_key (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, NULL);
3755 	}
3756 
3757 	return changed;
3758 }
3759 
3760 /**
3761  * e_source_selector_save_groups_setup:
3762  * @selector: an #ESourceSelector
3763  * @key_file: a #GKeyFile to store the sgroups setup to
3764  *
3765  * Stores current setup of the groups in the @key_file.
3766  *
3767  * Use e_source_selector_load_groups_setup() to pass the settings
3768  * back to the @selector.
3769  *
3770  * Returns: Whether the saved values are different, aka whether it's
3771  *    required to store the changes.
3772  *
3773  * Since: 3.20
3774  **/
3775 gboolean
e_source_selector_save_groups_setup(ESourceSelector * selector,GKeyFile * key_file)3776 e_source_selector_save_groups_setup (ESourceSelector *selector,
3777 				     GKeyFile *key_file)
3778 {
3779 	GPtrArray *value;
3780 	const gchar *extension_name;
3781 	gchar *group_key;
3782 	gboolean changed;
3783 
3784 	g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
3785 	g_return_val_if_fail (key_file != NULL, FALSE);
3786 
3787 	extension_name = e_source_selector_get_extension_name (selector);
3788 	g_return_val_if_fail (extension_name != NULL, FALSE);
3789 
3790 	group_key = g_strconcat (extension_name, "-hidden-groups", NULL);
3791 
3792 	if (g_hash_table_size (selector->priv->hidden_groups) > 0) {
3793 		GHashTableIter iter;
3794 		gpointer key, unused;
3795 
3796 		value = g_ptr_array_sized_new (g_hash_table_size (selector->priv->hidden_groups));
3797 
3798 		g_hash_table_iter_init (&iter, selector->priv->hidden_groups);
3799 		while (g_hash_table_iter_next (&iter, &key, &unused)) {
3800 			if (key)
3801 				g_ptr_array_add (value, key);
3802 		}
3803 
3804 		/* expects NULL-terminated array of strings, thus terminate it */
3805 		g_ptr_array_add (value, NULL);
3806 
3807 		changed = source_selector_store_value (key_file, group_key, (const gchar * const *) value->pdata, value->len - 1);
3808 
3809 		g_ptr_array_unref (value);
3810 	} else {
3811 		changed = source_selector_store_value (key_file, group_key, NULL, 0);
3812 	}
3813 
3814 	g_free (group_key);
3815 	group_key = g_strconcat (extension_name, "-groups-order", NULL);
3816 
3817 	if (selector->priv->groups_order) {
3818 		GSList *link;
3819 
3820 		value = g_ptr_array_sized_new (g_slist_length (selector->priv->groups_order));
3821 
3822 		for (link = selector->priv->groups_order; link; link = g_slist_next (link)) {
3823 			if (link->data)
3824 				g_ptr_array_add (value, link->data);
3825 		}
3826 
3827 		/* expects NULL-terminated array of strings, thus terminate it */
3828 		g_ptr_array_add (value, NULL);
3829 
3830 		changed = source_selector_store_value (key_file, group_key, (const gchar * const *) value->pdata, value->len - 1) || changed;
3831 
3832 		g_ptr_array_unref (value);
3833 	} else {
3834 		changed = source_selector_store_value (key_file, group_key, NULL, 0) || changed;
3835 	}
3836 
3837 	g_free (group_key);
3838 
3839 	return changed;
3840 }
3841 
3842 /**
3843  * e_source_selector_load_groups_setup:
3844  * @selector: an #ESourceSelector
3845  * @key_file: a #GKeyFile to load the groups setup from
3846  *
3847  * Loads setup of the groups from the @key_file.
3848  *
3849  * Use e_source_selector_save_groups_setup() to store
3850  * the settings of the @selector.
3851  *
3852  * Since: 3.20
3853  **/
3854 void
e_source_selector_load_groups_setup(ESourceSelector * selector,GKeyFile * key_file)3855 e_source_selector_load_groups_setup (ESourceSelector *selector,
3856 				     GKeyFile *key_file)
3857 {
3858 	const gchar *extension_name;
3859 	gchar **stored;
3860 	gchar *group_key;
3861 	gsize ii;
3862 
3863 	g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
3864 
3865 	extension_name = e_source_selector_get_extension_name (selector);
3866 	g_return_if_fail (extension_name != NULL);
3867 
3868 	g_hash_table_remove_all (selector->priv->hidden_groups);
3869 	g_slist_free_full (selector->priv->groups_order, g_free);
3870 	selector->priv->groups_order = NULL;
3871 
3872 	group_key = g_strconcat (extension_name, "-hidden-groups", NULL);
3873 
3874 	stored = g_key_file_get_string_list (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, NULL, NULL);
3875 	if (stored) {
3876 		for (ii = 0; stored[ii]; ii++) {
3877 			g_hash_table_insert (selector->priv->hidden_groups, g_strdup (stored[ii]), GINT_TO_POINTER (1));
3878 		}
3879 
3880 		g_strfreev (stored);
3881 	}
3882 
3883 	g_free (group_key);
3884 	group_key = g_strconcat (extension_name, "-groups-order", NULL);
3885 
3886 	stored = g_key_file_get_string_list (key_file, E_SOURCE_SELECTOR_GROUPS_SETUP_NAME, group_key, NULL, NULL);
3887 	if (stored) {
3888 		for (ii = 0; stored[ii]; ii++) {
3889 			selector->priv->groups_order = g_slist_prepend (selector->priv->groups_order, g_strdup (stored[ii]));
3890 		}
3891 
3892 		g_strfreev (stored);
3893 	}
3894 
3895 	g_free (group_key);
3896 
3897 	selector->priv->groups_order = g_slist_reverse (selector->priv->groups_order);
3898 
3899 	source_selector_build_model (selector);
3900 }
3901