1 /*
2  * e-proxy-selector.c
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program; if not, see <http://www.gnu.org/licenses/>.
15  *
16  */
17 
18 /**
19  * SECTION: e-proxy-selector
20  * @include: e-util/e-util.h
21  * @short_description: Select and manage proxy profiles
22  *
23  * #EProxySelector displays a list of available proxy profiles, with inline
24  * toolbar controls for adding and removing profiles.
25  **/
26 
27 #include "evolution-config.h"
28 
29 #include <glib/gi18n-lib.h>
30 
31 #include "e-proxy-selector.h"
32 
33 #define E_PROXY_SELECTOR_GET_PRIVATE(obj) \
34 	(G_TYPE_INSTANCE_GET_PRIVATE \
35 	((obj), E_TYPE_PROXY_SELECTOR, EProxySelectorPrivate))
36 
37 typedef struct _AsyncContext AsyncContext;
38 
39 struct _EProxySelectorPrivate {
40 	ESourceRegistry *registry;
41 	gulong source_added_handler_id;
42 	gulong source_changed_handler_id;
43 	gulong source_removed_handler_id;
44 
45 	GtkTreeSelection *selection;
46 	gulong selection_changed_handler_id;
47 
48 	guint refresh_idle_id;
49 };
50 
51 struct _AsyncContext {
52 	EProxySelector *selector;
53 	ESource *scratch_source;
54 };
55 
56 enum {
57 	PROP_0,
58 	PROP_REGISTRY,
59 	PROP_SELECTED
60 };
61 
62 enum {
63 	COLUMN_DISPLAY_NAME,
64 	COLUMN_SOURCE
65 };
66 
G_DEFINE_TYPE(EProxySelector,e_proxy_selector,E_TYPE_TREE_VIEW_FRAME)67 G_DEFINE_TYPE (
68 	EProxySelector,
69 	e_proxy_selector,
70 	E_TYPE_TREE_VIEW_FRAME)
71 
72 static void
73 async_context_free (AsyncContext *async_context)
74 {
75 	g_clear_object (&async_context->selector);
76 	g_clear_object (&async_context->scratch_source);
77 
78 	g_slice_free (AsyncContext, async_context);
79 }
80 
81 static gchar *
proxy_selector_pick_display_name(EProxySelector * selector)82 proxy_selector_pick_display_name (EProxySelector *selector)
83 {
84 	ESourceRegistry *registry;
85 	GList *list, *link;
86 	const gchar *base_name = _("Custom Proxy");
87 	const gchar *extension_name;
88 	gchar *display_name;
89 	guint ii = 0;
90 
91 	extension_name = E_SOURCE_EXTENSION_PROXY;
92 	registry = e_proxy_selector_get_registry (selector);
93 	list = e_source_registry_list_sources (registry, extension_name);
94 
95 	/* Convert the list of ESources to a list of display names. */
96 	for (link = list; link != NULL; link = g_list_next (link)) {
97 		ESource *source = E_SOURCE (link->data);
98 		link->data = e_source_dup_display_name (source);
99 		g_object_unref (source);
100 	}
101 
102 	display_name = g_strdup (base_name);
103 
104 try_again:
105 	link = g_list_find_custom (
106 		list, display_name, (GCompareFunc) g_utf8_collate);
107 
108 	if (link != NULL) {
109 		g_free (display_name);
110 		display_name = g_strdup_printf ("%s (%u)", base_name, ++ii);
111 		goto try_again;
112 	}
113 
114 	g_list_free_full (list, (GDestroyNotify) g_free);
115 
116 	return display_name;
117 }
118 
119 static void
proxy_selector_commit_source_cb(GObject * object,GAsyncResult * result,gpointer user_data)120 proxy_selector_commit_source_cb (GObject *object,
121                                  GAsyncResult *result,
122                                  gpointer user_data)
123 {
124 	AsyncContext *async_context;
125 	GError *local_error = NULL;
126 
127 	async_context = (AsyncContext *) user_data;
128 
129 	e_source_registry_commit_source_finish (
130 		E_SOURCE_REGISTRY (object), result, &local_error);
131 
132 	if (local_error == NULL) {
133 		/* Refresh the tree model immediately. */
134 		e_proxy_selector_refresh (async_context->selector);
135 
136 		/* Select the newly added proxy data source.  Note that
137 		 * e_proxy_selector_set_selected() uses e_source_equal()
138 		 * to match the input ESource to a tree model row, so it
139 		 * should match our scratch ESource to the newly-created
140 		 * ESource since they both have the same UID. */
141 		e_proxy_selector_set_selected (
142 			async_context->selector,
143 			async_context->scratch_source);
144 	} else {
145 		/* FIXME Hand the error off to an EAlertSink. */
146 		g_warning ("%s: %s", G_STRFUNC, local_error->message);
147 		g_error_free (local_error);
148 	}
149 
150 	gtk_widget_set_sensitive (GTK_WIDGET (async_context->selector), TRUE);
151 
152 	async_context_free (async_context);
153 }
154 
155 static void
proxy_selector_remove_source_cb(GObject * object,GAsyncResult * result,gpointer user_data)156 proxy_selector_remove_source_cb (GObject *object,
157                                  GAsyncResult *result,
158                                  gpointer user_data)
159 {
160 	EProxySelector *selector;
161 	GError *local_error = NULL;
162 
163 	selector = E_PROXY_SELECTOR (user_data);
164 
165 	e_source_remove_finish (E_SOURCE (object), result, &local_error);
166 
167 	if (local_error != NULL) {
168 		/* FIXME Hand the error off to an EAlertSink. */
169 		g_warning ("%s: %s", G_STRFUNC, local_error->message);
170 		g_error_free (local_error);
171 	}
172 
173 	gtk_widget_set_sensitive (GTK_WIDGET (selector), TRUE);
174 
175 	g_object_unref (selector);
176 }
177 
178 static gboolean
proxy_selector_action_add_cb(EProxySelector * selector,GtkAction * action)179 proxy_selector_action_add_cb (EProxySelector *selector,
180                               GtkAction *action)
181 {
182 	AsyncContext *async_context;
183 	ESourceRegistry *registry;
184 	ESourceProxy *extension;
185 	ESource *scratch_source;
186 	const gchar *extension_name;
187 	gchar *display_name;
188 
189 	const gchar * const ignore_hosts[] = {
190 		"localhost",
191 		"127.0.0.0/8",
192 		"::1",
193 		NULL
194 	};
195 
196 	scratch_source = e_source_new (NULL, NULL, NULL);
197 
198 	display_name = proxy_selector_pick_display_name (selector);
199 	e_source_set_display_name (scratch_source, display_name);
200 	g_free (display_name);
201 
202 	extension_name = E_SOURCE_EXTENSION_PROXY;
203 	extension = e_source_get_extension (scratch_source, extension_name);
204 	e_source_proxy_set_ignore_hosts (extension, ignore_hosts);
205 
206 	registry = e_proxy_selector_get_registry (selector);
207 
208 	/* Disable the selector until the commit operation completes. */
209 	gtk_widget_set_sensitive (GTK_WIDGET (selector), FALSE);
210 
211 	async_context = g_slice_new0 (AsyncContext);
212 	async_context->selector = g_object_ref (selector);
213 	async_context->scratch_source = g_object_ref (scratch_source);
214 
215 	e_source_registry_commit_source (
216 		registry, scratch_source, NULL,
217 		proxy_selector_commit_source_cb,
218 		async_context);
219 
220 	g_object_unref (scratch_source);
221 
222 	return TRUE;
223 }
224 
225 static gboolean
proxy_selector_action_remove_cb(EProxySelector * selector,GtkAction * action)226 proxy_selector_action_remove_cb (EProxySelector *selector,
227                                  GtkAction *action)
228 {
229 	ESource *selected_source;
230 
231 	selected_source = e_proxy_selector_ref_selected (selector);
232 	g_return_val_if_fail (selected_source != NULL, FALSE);
233 
234 	/* Disable the selector until the remove operation completes. */
235 	gtk_widget_set_sensitive (GTK_WIDGET (selector), FALSE);
236 
237 	e_source_remove (
238 		selected_source, NULL,
239 		proxy_selector_remove_source_cb,
240 		g_object_ref (selector));
241 
242 	g_object_unref (selected_source);
243 
244 	return TRUE;
245 }
246 
247 static gboolean
proxy_selector_refresh_idle_cb(gpointer user_data)248 proxy_selector_refresh_idle_cb (gpointer user_data)
249 {
250 	EProxySelector *selector = user_data;
251 
252 	/* The refresh function will clear the idle ID. */
253 	e_proxy_selector_refresh (selector);
254 
255 	return FALSE;
256 }
257 
258 static void
proxy_selector_schedule_refresh(EProxySelector * selector)259 proxy_selector_schedule_refresh (EProxySelector *selector)
260 {
261 	/* Use an idle callback to limit how frequently we refresh
262 	 * the tree model in case the registry is emitting lots of
263 	 * signals at once. */
264 
265 	if (selector->priv->refresh_idle_id == 0) {
266 		selector->priv->refresh_idle_id = g_idle_add (
267 			proxy_selector_refresh_idle_cb, selector);
268 	}
269 }
270 
271 static void
proxy_selector_cell_edited_cb(GtkCellRendererText * renderer,const gchar * path_string,const gchar * new_name,EProxySelector * selector)272 proxy_selector_cell_edited_cb (GtkCellRendererText *renderer,
273                                const gchar *path_string,
274                                const gchar *new_name,
275                                EProxySelector *selector)
276 {
277 	ETreeViewFrame *tree_view_frame;
278 	GtkTreeView *tree_view;
279 	GtkTreeModel *model;
280 	GtkTreePath *path;
281 	GtkTreeIter iter;
282 	ESource *source;
283 
284 	if (new_name == NULL || *new_name == '\0')
285 		return;
286 
287 	tree_view_frame = E_TREE_VIEW_FRAME (selector);
288 	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
289 	model = gtk_tree_view_get_model (tree_view);
290 
291 	path = gtk_tree_path_new_from_string (path_string);
292 	gtk_tree_model_get_iter (model, &iter, path);
293 	gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
294 	gtk_tree_path_free (path);
295 
296 	/* EProxyPreferences will detect the change and commit it. */
297 	e_source_set_display_name (source, new_name);
298 
299 	e_proxy_selector_refresh (selector);
300 
301 	g_object_unref (source);
302 }
303 
304 static void
proxy_selector_source_added_cb(ESourceRegistry * registry,ESource * source,EProxySelector * selector)305 proxy_selector_source_added_cb (ESourceRegistry *registry,
306                                 ESource *source,
307                                 EProxySelector *selector)
308 {
309 	if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY))
310 		proxy_selector_schedule_refresh (selector);
311 }
312 
313 static void
proxy_selector_source_changed_cb(ESourceRegistry * registry,ESource * source,EProxySelector * selector)314 proxy_selector_source_changed_cb (ESourceRegistry *registry,
315                                   ESource *source,
316                                   EProxySelector *selector)
317 {
318 	if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY))
319 		proxy_selector_schedule_refresh (selector);
320 }
321 
322 static void
proxy_selector_source_removed_cb(ESourceRegistry * registry,ESource * source,EProxySelector * selector)323 proxy_selector_source_removed_cb (ESourceRegistry *registry,
324                                   ESource *source,
325                                   EProxySelector *selector)
326 {
327 	if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY))
328 		proxy_selector_schedule_refresh (selector);
329 }
330 
331 static void
proxy_selector_selection_changed_cb(GtkTreeSelection * selection,EProxySelector * selector)332 proxy_selector_selection_changed_cb (GtkTreeSelection *selection,
333                                      EProxySelector *selector)
334 {
335 	g_object_notify (G_OBJECT (selector), "selected");
336 }
337 
338 static void
proxy_selector_set_registry(EProxySelector * selector,ESourceRegistry * registry)339 proxy_selector_set_registry (EProxySelector *selector,
340                              ESourceRegistry *registry)
341 {
342 	gulong handler_id;
343 
344 	g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
345 	g_return_if_fail (selector->priv->registry == NULL);
346 
347 	selector->priv->registry = g_object_ref (registry);
348 
349 	handler_id = g_signal_connect (
350 		registry, "source-added",
351 		G_CALLBACK (proxy_selector_source_added_cb), selector);
352 	selector->priv->source_added_handler_id = handler_id;
353 
354 	handler_id = g_signal_connect (
355 		registry, "source-changed",
356 		G_CALLBACK (proxy_selector_source_changed_cb), selector);
357 	selector->priv->source_changed_handler_id = handler_id;
358 
359 	handler_id = g_signal_connect (
360 		registry, "source-removed",
361 		G_CALLBACK (proxy_selector_source_removed_cb), selector);
362 	selector->priv->source_removed_handler_id = handler_id;
363 }
364 
365 static void
proxy_selector_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)366 proxy_selector_set_property (GObject *object,
367                              guint property_id,
368                              const GValue *value,
369                              GParamSpec *pspec)
370 {
371 	switch (property_id) {
372 		case PROP_REGISTRY:
373 			proxy_selector_set_registry (
374 				E_PROXY_SELECTOR (object),
375 				g_value_get_object (value));
376 			return;
377 
378 		case PROP_SELECTED:
379 			e_proxy_selector_set_selected (
380 				E_PROXY_SELECTOR (object),
381 				g_value_get_object (value));
382 			return;
383 	}
384 
385 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
386 }
387 
388 static void
proxy_selector_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)389 proxy_selector_get_property (GObject *object,
390                              guint property_id,
391                              GValue *value,
392                              GParamSpec *pspec)
393 {
394 	switch (property_id) {
395 		case PROP_REGISTRY:
396 			g_value_set_object (
397 				value,
398 				e_proxy_selector_get_registry (
399 				E_PROXY_SELECTOR (object)));
400 			return;
401 
402 		case PROP_SELECTED:
403 			g_value_take_object (
404 				value,
405 				e_proxy_selector_ref_selected (
406 				E_PROXY_SELECTOR (object)));
407 			return;
408 	}
409 
410 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
411 }
412 
413 static void
proxy_selector_dispose(GObject * object)414 proxy_selector_dispose (GObject *object)
415 {
416 	EProxySelectorPrivate *priv;
417 
418 	priv = E_PROXY_SELECTOR_GET_PRIVATE (object);
419 
420 	if (priv->source_added_handler_id > 0) {
421 		g_signal_handler_disconnect (
422 			priv->registry,
423 			priv->source_added_handler_id);
424 		priv->source_added_handler_id = 0;
425 	}
426 
427 	if (priv->source_changed_handler_id > 0) {
428 		g_signal_handler_disconnect (
429 			priv->registry,
430 			priv->source_changed_handler_id);
431 		priv->source_changed_handler_id = 0;
432 	}
433 
434 	if (priv->source_removed_handler_id > 0) {
435 		g_signal_handler_disconnect (
436 			priv->registry,
437 			priv->source_removed_handler_id);
438 		priv->source_removed_handler_id = 0;
439 	}
440 
441 	if (priv->selection_changed_handler_id > 0) {
442 		g_signal_handler_disconnect (
443 			priv->selection,
444 			priv->selection_changed_handler_id);
445 		priv->selection_changed_handler_id = 0;
446 	}
447 
448 	if (priv->refresh_idle_id > 0) {
449 		g_source_remove (priv->refresh_idle_id);
450 		priv->refresh_idle_id = 0;
451 	}
452 
453 	g_clear_object (&priv->registry);
454 
455 	/* Chain up to parent's dispose() method. */
456 	G_OBJECT_CLASS (e_proxy_selector_parent_class)->dispose (object);
457 }
458 
459 static void
proxy_selector_constructed(GObject * object)460 proxy_selector_constructed (GObject *object)
461 {
462 	EProxySelector *selector;
463 	ETreeViewFrame *tree_view_frame;
464 	GtkTreeView *tree_view;
465 	GtkTreeViewColumn *column;
466 	GtkCellRenderer *renderer;
467 	GtkTreeSelection *selection;
468 	GtkListStore *list_store;
469 	GtkAction *action;
470 	const gchar *tooltip;
471 	gulong handler_id;
472 
473 	/* Chain up to parent's constructed() method. */
474 	G_OBJECT_CLASS (e_proxy_selector_parent_class)->constructed (object);
475 
476 	selector = E_PROXY_SELECTOR (object);
477 
478 	tree_view_frame = E_TREE_VIEW_FRAME (object);
479 	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
480 
481 	gtk_tree_view_set_reorderable (tree_view, FALSE);
482 	gtk_tree_view_set_headers_visible (tree_view, FALSE);
483 
484 	/* Configure the toolbar actions. */
485 
486 	action = e_tree_view_frame_lookup_toolbar_action (
487 		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_ADD);
488 	tooltip = _("Create a new proxy profile");
489 	gtk_action_set_tooltip (action, tooltip);
490 
491 	action = e_tree_view_frame_lookup_toolbar_action (
492 		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_REMOVE);
493 	tooltip = _("Delete the selected proxy profile");
494 	gtk_action_set_tooltip (action, tooltip);
495 
496 	/* Configure the tree view column. */
497 
498 	column = gtk_tree_view_column_new ();
499 	renderer = gtk_cell_renderer_text_new ();
500 	g_object_set (
501 		G_OBJECT (renderer),
502 		"editable", TRUE,
503 		"ellipsize", PANGO_ELLIPSIZE_END, NULL);
504 	g_signal_connect (
505 		renderer, "edited",
506 		G_CALLBACK (proxy_selector_cell_edited_cb), selector);
507 	gtk_tree_view_column_pack_start (column, renderer, TRUE);
508 	gtk_tree_view_column_add_attribute (
509 		column, renderer, "text", COLUMN_DISPLAY_NAME);
510 	gtk_tree_view_append_column (tree_view, column);
511 
512 	/* Listen for tree view selection changes. */
513 
514 	selection = gtk_tree_view_get_selection (tree_view);
515 	selector->priv->selection = g_object_ref (selection);
516 
517 	handler_id = g_signal_connect (
518 		selection, "changed",
519 		G_CALLBACK (proxy_selector_selection_changed_cb), selector);
520 	selector->priv->selection_changed_handler_id = handler_id;
521 
522 	/* Create and populate the tree model. */
523 
524 	list_store = gtk_list_store_new (2, G_TYPE_STRING, E_TYPE_SOURCE);
525 	gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store));
526 	g_object_unref (list_store);
527 
528 	e_proxy_selector_refresh (E_PROXY_SELECTOR (object));
529 }
530 
531 static void
proxy_selector_update_toolbar_actions(ETreeViewFrame * tree_view_frame)532 proxy_selector_update_toolbar_actions (ETreeViewFrame *tree_view_frame)
533 {
534 	EProxySelector *selector;
535 	ESource *selected;
536 	GtkAction *action;
537 	gboolean sensitive;
538 
539 	selector = E_PROXY_SELECTOR (tree_view_frame);
540 	selected = e_proxy_selector_ref_selected (selector);
541 
542 	action = e_tree_view_frame_lookup_toolbar_action (
543 		tree_view_frame, E_TREE_VIEW_FRAME_ACTION_REMOVE);
544 	sensitive = e_source_get_removable (selected);
545 	gtk_action_set_sensitive (action, sensitive);
546 
547 	g_object_unref (selected);
548 
549 	/* Chain up to parent's update_toolbar_actions() method. */
550 	E_TREE_VIEW_FRAME_CLASS (e_proxy_selector_parent_class)->
551 		update_toolbar_actions (tree_view_frame);
552 }
553 
554 static void
e_proxy_selector_class_init(EProxySelectorClass * class)555 e_proxy_selector_class_init (EProxySelectorClass *class)
556 {
557 	GObjectClass *object_class;
558 	ETreeViewFrameClass *tree_view_frame_class;
559 
560 	g_type_class_add_private (class, sizeof (EProxySelectorPrivate));
561 
562 	object_class = G_OBJECT_CLASS (class);
563 	object_class->set_property = proxy_selector_set_property;
564 	object_class->get_property = proxy_selector_get_property;
565 	object_class->dispose = proxy_selector_dispose;
566 	object_class->constructed = proxy_selector_constructed;
567 
568 	tree_view_frame_class = E_TREE_VIEW_FRAME_CLASS (class);
569 	tree_view_frame_class->update_toolbar_actions =
570 				proxy_selector_update_toolbar_actions;
571 
572 	g_object_class_install_property (
573 		object_class,
574 		PROP_REGISTRY,
575 		g_param_spec_object (
576 			"registry",
577 			"Registry",
578 			"Data source registry",
579 			E_TYPE_SOURCE_REGISTRY,
580 			G_PARAM_READWRITE |
581 			G_PARAM_CONSTRUCT_ONLY |
582 			G_PARAM_STATIC_STRINGS));
583 
584 	g_object_class_install_property (
585 		object_class,
586 		PROP_SELECTED,
587 		g_param_spec_object (
588 			"selected",
589 			"Selected",
590 			"The selected data source",
591 			E_TYPE_SOURCE,
592 			G_PARAM_READWRITE |
593 			G_PARAM_STATIC_STRINGS));
594 }
595 
596 static void
e_proxy_selector_init(EProxySelector * selector)597 e_proxy_selector_init (EProxySelector *selector)
598 {
599 	selector->priv = E_PROXY_SELECTOR_GET_PRIVATE (selector);
600 
601 	/* In this particular case, it's easier to connect handlers
602 	 * to detailed signal names than to override the class method. */
603 
604 	g_signal_connect (
605 		selector,
606 		"toolbar-action-activate::"
607 		E_TREE_VIEW_FRAME_ACTION_ADD,
608 		G_CALLBACK (proxy_selector_action_add_cb), NULL);
609 
610 	g_signal_connect (
611 		selector,
612 		"toolbar-action-activate::"
613 		E_TREE_VIEW_FRAME_ACTION_REMOVE,
614 		G_CALLBACK (proxy_selector_action_remove_cb), NULL);
615 }
616 
617 /**
618  * e_proxy_selector_new:
619  * @registry: an #ESourceRegistry
620  *
621  * Creates a new #EProxySelector widget using #ESource instances in @registry.
622  *
623  * Returns: a new #EProxySelector
624  **/
625 GtkWidget *
e_proxy_selector_new(ESourceRegistry * registry)626 e_proxy_selector_new (ESourceRegistry *registry)
627 {
628 	g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
629 
630 	return g_object_new (
631 		E_TYPE_PROXY_SELECTOR,
632 		"registry", registry, NULL);
633 }
634 
635 /**
636  * e_proxy_selector_refresh:
637  * @selector: an #EProxySelector
638  *
639  * Rebuilds the @selector's list store with an updated list of #ESource
640  * instances that describe a network proxy profile, without disrupting the
641  * previously selected item (if possible).
642  *
643  * This funtion is called automatically in response to #ESourceRegistry
644  * signals which are pertinent to the @selector.
645  **/
646 void
e_proxy_selector_refresh(EProxySelector * selector)647 e_proxy_selector_refresh (EProxySelector *selector)
648 {
649 	ETreeViewFrame *tree_view_frame;
650 	ESourceRegistry *registry;
651 	GtkTreeView *tree_view;
652 	GtkTreeModel *tree_model;
653 	ESource *builtin_source;
654 	ESource *selected;
655 	GList *list, *link;
656 	const gchar *extension_name;
657 
658 	g_return_if_fail (E_IS_PROXY_SELECTOR (selector));
659 
660 	if (selector->priv->refresh_idle_id > 0) {
661 		g_source_remove (selector->priv->refresh_idle_id);
662 		selector->priv->refresh_idle_id = 0;
663 	}
664 
665 	tree_view_frame = E_TREE_VIEW_FRAME (selector);
666 	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
667 	tree_model = gtk_tree_view_get_model (tree_view);
668 
669 	selected = e_proxy_selector_ref_selected (selector);
670 
671 	gtk_list_store_clear (GTK_LIST_STORE (tree_model));
672 
673 	extension_name = E_SOURCE_EXTENSION_PROXY;
674 	registry = e_proxy_selector_get_registry (selector);
675 	list = e_source_registry_list_enabled (registry, extension_name);
676 
677 	builtin_source = e_source_registry_ref_builtin_proxy (registry);
678 	g_warn_if_fail (builtin_source != NULL);
679 
680 	/* Always list the built-in proxy profile first. */
681 	link = g_list_find (list, builtin_source);
682 	if (link != NULL && list != link) {
683 		list = g_list_remove_link (list, link);
684 		list = g_list_concat (link, list);
685 	}
686 
687 	for (link = list; link != NULL; link = g_list_next (link)) {
688 		ESource *source;
689 		GtkTreeIter iter;
690 		const gchar *display_name;
691 
692 		source = E_SOURCE (link->data);
693 		display_name = e_source_get_display_name (source);
694 
695 		gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter);
696 
697 		gtk_list_store_set (
698 			GTK_LIST_STORE (tree_model), &iter,
699 			COLUMN_DISPLAY_NAME, display_name,
700 			COLUMN_SOURCE, source, -1);
701 	}
702 
703 	g_clear_object (&builtin_source);
704 
705 	g_list_free_full (list, (GDestroyNotify) g_object_unref);
706 
707 	/* Try and restore the previous selected source or else pick
708 	 * the built-in proxy profile, which is always listed first. */
709 
710 	e_proxy_selector_set_selected (selector, selected);
711 
712 	g_clear_object (&selected);
713 }
714 
715 /**
716  * e_proxy_selector_get_registry:
717  * @selector: an #EProxySelector
718  *
719  * Returns the #ESourceRegistry passed to e_proxy_selector_get_registry().
720  *
721  * Returns: an #ESourceRegistry
722  **/
723 ESourceRegistry *
e_proxy_selector_get_registry(EProxySelector * selector)724 e_proxy_selector_get_registry (EProxySelector *selector)
725 {
726 	g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), NULL);
727 
728 	return selector->priv->registry;
729 }
730 
731 /**
732  * e_proxy_selector_ref_selected:
733  * @selector: an #EProxySelector
734  *
735  * Returns the selected #ESource in @selector.
736  *
737  * The function tries to ensure a valid #ESource is always returned,
738  * falling back to e_source_registry_ref_builtin_proxy() if necessary.
739  *
740  * The returned #ESource is referenced for thread-safety and must be
741  * unreferenced with g_object_unref() when finished with it.
742  *
743  * Returns: an #ESource
744  **/
745 ESource *
e_proxy_selector_ref_selected(EProxySelector * selector)746 e_proxy_selector_ref_selected (EProxySelector *selector)
747 {
748 	ETreeViewFrame *tree_view_frame;
749 	GtkTreeView *tree_view;
750 	GtkTreeModel *tree_model;
751 	GtkTreeSelection *selection;
752 	GtkTreeIter iter;
753 	ESource *source = NULL;
754 
755 	g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), NULL);
756 
757 	tree_view_frame = E_TREE_VIEW_FRAME (selector);
758 	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
759 	selection = gtk_tree_view_get_selection (tree_view);
760 
761 	if (gtk_tree_selection_get_selected (selection, &tree_model, &iter)) {
762 		gtk_tree_model_get (
763 			tree_model, &iter,
764 			COLUMN_SOURCE, &source, -1);
765 	}
766 
767 	/* The built-in proxy profile is implicitly selected when
768 	 * no proxy profile is actually selected in the tree view. */
769 	if (source == NULL) {
770 		ESourceRegistry *registry;
771 
772 		registry = e_proxy_selector_get_registry (selector);
773 		source = e_source_registry_ref_builtin_proxy (registry);
774 		g_return_val_if_fail (source != NULL, NULL);
775 	}
776 
777 	return source;
778 }
779 
780 /**
781  * e_proxy_selector_set_selected:
782  * @selector: an #EProxySelector
783  * @source: an #ESource, or %NULL for the built-in proxy profile
784  *
785  * Finds the corresponding tree model row for @source, selects the row,
786  * and returns %TRUE.  If no corresponding tree model row for @source is
787  * found, the selection remains unchanged and the function returns %FALSE.
788  *
789  * Returns: whether @source was selected
790  **/
791 gboolean
e_proxy_selector_set_selected(EProxySelector * selector,ESource * source)792 e_proxy_selector_set_selected (EProxySelector *selector,
793                                ESource *source)
794 {
795 	ETreeViewFrame *tree_view_frame;
796 	GtkTreeView *tree_view;
797 	GtkTreeModel *tree_model;
798 	GtkTreeIter iter;
799 	gboolean iter_valid;
800 
801 	g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), FALSE);
802 	g_return_val_if_fail (source == NULL || E_IS_SOURCE (source), FALSE);
803 
804 	if (source == NULL) {
805 		ESourceRegistry *registry;
806 
807 		registry = e_proxy_selector_get_registry (selector);
808 		source = e_source_registry_ref_builtin_proxy (registry);
809 		g_return_val_if_fail (source != NULL, FALSE);
810 	}
811 
812 	tree_view_frame = E_TREE_VIEW_FRAME (selector);
813 	tree_view = e_tree_view_frame_get_tree_view (tree_view_frame);
814 	tree_model = gtk_tree_view_get_model (tree_view);
815 
816 	iter_valid = gtk_tree_model_get_iter_first (tree_model, &iter);
817 
818 	while (iter_valid) {
819 		ESource *candidate = NULL;
820 		gboolean match;
821 
822 		gtk_tree_model_get (
823 			tree_model, &iter,
824 			COLUMN_SOURCE, &candidate, -1);
825 
826 		match = e_source_equal (source, candidate);
827 
828 		g_object_unref (candidate);
829 
830 		if (match)
831 			break;
832 
833 		iter_valid = gtk_tree_model_iter_next (tree_model, &iter);
834 	}
835 
836 	if (iter_valid) {
837 		GtkTreeSelection *selection;
838 
839 		selection = gtk_tree_view_get_selection (tree_view);
840 		gtk_tree_selection_select_iter (selection, &iter);
841 	}
842 
843 	return iter_valid;
844 }
845 
846