/* * e-proxy-selector.c * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * */ /** * SECTION: e-proxy-selector * @include: e-util/e-util.h * @short_description: Select and manage proxy profiles * * #EProxySelector displays a list of available proxy profiles, with inline * toolbar controls for adding and removing profiles. **/ #include "e-proxy-selector.h" #include #include #define E_PROXY_SELECTOR_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_PROXY_SELECTOR, EProxySelectorPrivate)) typedef struct _AsyncContext AsyncContext; struct _EProxySelectorPrivate { ESourceRegistry *registry; gulong source_added_handler_id; gulong source_changed_handler_id; gulong source_removed_handler_id; GtkTreeSelection *selection; gulong selection_changed_handler_id; guint refresh_idle_id; }; struct _AsyncContext { EProxySelector *selector; ESource *scratch_source; }; enum { PROP_0, PROP_REGISTRY, PROP_SELECTED }; enum { COLUMN_DISPLAY_NAME, COLUMN_SOURCE }; G_DEFINE_TYPE ( EProxySelector, e_proxy_selector, E_TYPE_TREE_VIEW_FRAME) static void async_context_free (AsyncContext *async_context) { g_clear_object (&async_context->selector); g_clear_object (&async_context->scratch_source); g_slice_free (AsyncContext, async_context); } static gchar * proxy_selector_pick_display_name (EProxySelector *selector) { ESourceRegistry *registry; GList *list, *link; const gchar *base_name = _("Custom Proxy"); const gchar *extension_name; gchar *display_name; guint ii = 0; extension_name = E_SOURCE_EXTENSION_PROXY; registry = e_proxy_selector_get_registry (selector); list = e_source_registry_list_sources (registry, extension_name); /* Convert the list of ESources to a list of display names. */ for (link = list; link != NULL; link = g_list_next (link)) { ESource *source = E_SOURCE (link->data); link->data = e_source_dup_display_name (source); g_object_unref (source); } display_name = g_strdup (base_name); try_again: link = g_list_find_custom ( list, display_name, (GCompareFunc) g_utf8_collate); if (link != NULL) { g_free (display_name); display_name = g_strdup_printf ("%s (%u)", base_name, ++ii); goto try_again; } g_list_free_full (list, (GDestroyNotify) g_free); return display_name; } static void proxy_selector_commit_source_cb (GObject *object, GAsyncResult *result, gpointer user_data) { AsyncContext *async_context; GError *local_error = NULL; async_context = (AsyncContext *) user_data; e_source_registry_commit_source_finish ( E_SOURCE_REGISTRY (object), result, &local_error); if (local_error == NULL) { /* Refresh the tree model immediately. */ e_proxy_selector_refresh (async_context->selector); /* Select the newly added proxy data source. Note that * e_proxy_selector_set_selected() uses e_source_equal() * to match the input ESource to a tree model row, so it * should match our scratch ESource to the newly-created * ESource since they both have the same UID. */ e_proxy_selector_set_selected ( async_context->selector, async_context->scratch_source); } else { /* FIXME Hand the error off to an EAlertSink. */ g_warning ("%s: %s", G_STRFUNC, local_error->message); g_error_free (local_error); } gtk_widget_set_sensitive (GTK_WIDGET (async_context->selector), TRUE); async_context_free (async_context); } static void proxy_selector_remove_source_cb (GObject *object, GAsyncResult *result, gpointer user_data) { EProxySelector *selector; GError *local_error = NULL; selector = E_PROXY_SELECTOR (user_data); e_source_remove_finish (E_SOURCE (object), result, &local_error); if (local_error != NULL) { /* FIXME Hand the error off to an EAlertSink. */ g_warning ("%s: %s", G_STRFUNC, local_error->message); g_error_free (local_error); } gtk_widget_set_sensitive (GTK_WIDGET (selector), TRUE); g_object_unref (selector); } static gboolean proxy_selector_action_add_cb (EProxySelector *selector, GtkAction *action) { AsyncContext *async_context; ESourceRegistry *registry; ESourceProxy *extension; ESource *scratch_source; const gchar *extension_name; gchar *display_name; const gchar * const ignore_hosts[] = { "localhost", "127.0.0.0/8", "::1", NULL }; scratch_source = e_source_new (NULL, NULL, NULL); display_name = proxy_selector_pick_display_name (selector); e_source_set_display_name (scratch_source, display_name); g_free (display_name); extension_name = E_SOURCE_EXTENSION_PROXY; extension = e_source_get_extension (scratch_source, extension_name); e_source_proxy_set_ignore_hosts (extension, ignore_hosts); registry = e_proxy_selector_get_registry (selector); /* Disable the selector until the commit operation completes. */ gtk_widget_set_sensitive (GTK_WIDGET (selector), FALSE); async_context = g_slice_new0 (AsyncContext); async_context->selector = g_object_ref (selector); async_context->scratch_source = g_object_ref (scratch_source); e_source_registry_commit_source ( registry, scratch_source, NULL, proxy_selector_commit_source_cb, async_context); g_object_unref (scratch_source); return TRUE; } static gboolean proxy_selector_action_remove_cb (EProxySelector *selector, GtkAction *action) { ESource *selected_source; selected_source = e_proxy_selector_ref_selected (selector); g_return_val_if_fail (selected_source != NULL, FALSE); /* Disable the selector until the remove operation completes. */ gtk_widget_set_sensitive (GTK_WIDGET (selector), FALSE); e_source_remove ( selected_source, NULL, proxy_selector_remove_source_cb, g_object_ref (selector)); g_object_unref (selected_source); return TRUE; } static gboolean proxy_selector_refresh_idle_cb (gpointer user_data) { EProxySelector *selector = user_data; /* The refresh function will clear the idle ID. */ e_proxy_selector_refresh (selector); return FALSE; } static void proxy_selector_schedule_refresh (EProxySelector *selector) { /* Use an idle callback to limit how frequently we refresh * the tree model in case the registry is emitting lots of * signals at once. */ if (selector->priv->refresh_idle_id == 0) { selector->priv->refresh_idle_id = g_idle_add ( proxy_selector_refresh_idle_cb, selector); } } static void proxy_selector_cell_edited_cb (GtkCellRendererText *renderer, const gchar *path_string, const gchar *new_name, EProxySelector *selector) { ETreeViewFrame *tree_view_frame; GtkTreeView *tree_view; GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter; ESource *source; if (new_name == NULL || *new_name == '\0') return; tree_view_frame = E_TREE_VIEW_FRAME (selector); tree_view = e_tree_view_frame_get_tree_view (tree_view_frame); model = gtk_tree_view_get_model (tree_view); path = gtk_tree_path_new_from_string (path_string); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1); gtk_tree_path_free (path); /* EProxyPreferences will detect the change and commit it. */ e_source_set_display_name (source, new_name); e_proxy_selector_refresh (selector); g_object_unref (source); } static void proxy_selector_source_added_cb (ESourceRegistry *registry, ESource *source, EProxySelector *selector) { if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY)) proxy_selector_schedule_refresh (selector); } static void proxy_selector_source_changed_cb (ESourceRegistry *registry, ESource *source, EProxySelector *selector) { if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY)) proxy_selector_schedule_refresh (selector); } static void proxy_selector_source_removed_cb (ESourceRegistry *registry, ESource *source, EProxySelector *selector) { if (e_source_has_extension (source, E_SOURCE_EXTENSION_PROXY)) proxy_selector_schedule_refresh (selector); } static void proxy_selector_selection_changed_cb (GtkTreeSelection *selection, EProxySelector *selector) { g_object_notify (G_OBJECT (selector), "selected"); } static void proxy_selector_set_registry (EProxySelector *selector, ESourceRegistry *registry) { gulong handler_id; g_return_if_fail (E_IS_SOURCE_REGISTRY (registry)); g_return_if_fail (selector->priv->registry == NULL); selector->priv->registry = g_object_ref (registry); handler_id = g_signal_connect ( registry, "source-added", G_CALLBACK (proxy_selector_source_added_cb), selector); selector->priv->source_added_handler_id = handler_id; handler_id = g_signal_connect ( registry, "source-changed", G_CALLBACK (proxy_selector_source_changed_cb), selector); selector->priv->source_changed_handler_id = handler_id; handler_id = g_signal_connect ( registry, "source-removed", G_CALLBACK (proxy_selector_source_removed_cb), selector); selector->priv->source_removed_handler_id = handler_id; } static void proxy_selector_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_REGISTRY: proxy_selector_set_registry ( E_PROXY_SELECTOR (object), g_value_get_object (value)); return; case PROP_SELECTED: e_proxy_selector_set_selected ( E_PROXY_SELECTOR (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void proxy_selector_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_REGISTRY: g_value_set_object ( value, e_proxy_selector_get_registry ( E_PROXY_SELECTOR (object))); return; case PROP_SELECTED: g_value_take_object ( value, e_proxy_selector_ref_selected ( E_PROXY_SELECTOR (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void proxy_selector_dispose (GObject *object) { EProxySelectorPrivate *priv; priv = E_PROXY_SELECTOR_GET_PRIVATE (object); if (priv->source_added_handler_id > 0) { g_signal_handler_disconnect ( priv->registry, priv->source_added_handler_id); priv->source_added_handler_id = 0; } if (priv->source_changed_handler_id > 0) { g_signal_handler_disconnect ( priv->registry, priv->source_changed_handler_id); priv->source_changed_handler_id = 0; } if (priv->source_removed_handler_id > 0) { g_signal_handler_disconnect ( priv->registry, priv->source_removed_handler_id); priv->source_removed_handler_id = 0; } if (priv->selection_changed_handler_id > 0) { g_signal_handler_disconnect ( priv->selection, priv->selection_changed_handler_id); priv->selection_changed_handler_id = 0; } if (priv->refresh_idle_id > 0) { g_source_remove (priv->refresh_idle_id); priv->refresh_idle_id = 0; } g_clear_object (&priv->registry); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_proxy_selector_parent_class)->dispose (object); } static void proxy_selector_constructed (GObject *object) { EProxySelector *selector; ETreeViewFrame *tree_view_frame; GtkTreeView *tree_view; GtkTreeViewColumn *column; GtkCellRenderer *renderer; GtkTreeSelection *selection; GtkListStore *list_store; GtkAction *action; const gchar *tooltip; gulong handler_id; /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_proxy_selector_parent_class)->constructed (object); selector = E_PROXY_SELECTOR (object); tree_view_frame = E_TREE_VIEW_FRAME (object); tree_view = e_tree_view_frame_get_tree_view (tree_view_frame); gtk_tree_view_set_reorderable (tree_view, FALSE); gtk_tree_view_set_headers_visible (tree_view, FALSE); /* Configure the toolbar actions. */ action = e_tree_view_frame_lookup_toolbar_action ( tree_view_frame, E_TREE_VIEW_FRAME_ACTION_ADD); tooltip = _("Create a new proxy profile"); gtk_action_set_tooltip (action, tooltip); action = e_tree_view_frame_lookup_toolbar_action ( tree_view_frame, E_TREE_VIEW_FRAME_ACTION_REMOVE); tooltip = _("Delete the selected proxy profile"); gtk_action_set_tooltip (action, tooltip); /* Configure the tree view column. */ column = gtk_tree_view_column_new (); renderer = gtk_cell_renderer_text_new (); g_object_set ( G_OBJECT (renderer), "editable", TRUE, "ellipsize", PANGO_ELLIPSIZE_END, NULL); g_signal_connect ( renderer, "edited", G_CALLBACK (proxy_selector_cell_edited_cb), selector); gtk_tree_view_column_pack_start (column, renderer, TRUE); gtk_tree_view_column_add_attribute ( column, renderer, "text", COLUMN_DISPLAY_NAME); gtk_tree_view_append_column (tree_view, column); /* Listen for tree view selection changes. */ selection = gtk_tree_view_get_selection (tree_view); selector->priv->selection = g_object_ref (selection); handler_id = g_signal_connect ( selection, "changed", G_CALLBACK (proxy_selector_selection_changed_cb), selector); selector->priv->selection_changed_handler_id = handler_id; /* Create and populate the tree model. */ list_store = gtk_list_store_new (2, G_TYPE_STRING, E_TYPE_SOURCE); gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (list_store)); g_object_unref (list_store); e_proxy_selector_refresh (E_PROXY_SELECTOR (object)); } static void proxy_selector_update_toolbar_actions (ETreeViewFrame *tree_view_frame) { EProxySelector *selector; ESource *selected; GtkAction *action; gboolean sensitive; selector = E_PROXY_SELECTOR (tree_view_frame); selected = e_proxy_selector_ref_selected (selector); action = e_tree_view_frame_lookup_toolbar_action ( tree_view_frame, E_TREE_VIEW_FRAME_ACTION_REMOVE); sensitive = e_source_get_removable (selected); gtk_action_set_sensitive (action, sensitive); g_object_unref (selected); /* Chain up to parent's update_toolbar_actions() method. */ E_TREE_VIEW_FRAME_CLASS (e_proxy_selector_parent_class)-> update_toolbar_actions (tree_view_frame); } static void e_proxy_selector_class_init (EProxySelectorClass *class) { GObjectClass *object_class; ETreeViewFrameClass *tree_view_frame_class; g_type_class_add_private (class, sizeof (EProxySelectorPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = proxy_selector_set_property; object_class->get_property = proxy_selector_get_property; object_class->dispose = proxy_selector_dispose; object_class->constructed = proxy_selector_constructed; tree_view_frame_class = E_TREE_VIEW_FRAME_CLASS (class); tree_view_frame_class->update_toolbar_actions = proxy_selector_update_toolbar_actions; g_object_class_install_property ( object_class, PROP_REGISTRY, g_param_spec_object ( "registry", "Registry", "Data source registry", E_TYPE_SOURCE_REGISTRY, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_SELECTED, g_param_spec_object ( "selected", "Selected", "The selected data source", E_TYPE_SOURCE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void e_proxy_selector_init (EProxySelector *selector) { selector->priv = E_PROXY_SELECTOR_GET_PRIVATE (selector); /* In this particular case, it's easier to connect handlers * to detailed signal names than to override the class method. */ g_signal_connect ( selector, "toolbar-action-activate::" E_TREE_VIEW_FRAME_ACTION_ADD, G_CALLBACK (proxy_selector_action_add_cb), NULL); g_signal_connect ( selector, "toolbar-action-activate::" E_TREE_VIEW_FRAME_ACTION_REMOVE, G_CALLBACK (proxy_selector_action_remove_cb), NULL); } /** * e_proxy_selector_new: * @registry: an #ESourceRegistry * * Creates a new #EProxySelector widget using #ESource instances in @registry. * * Returns: a new #EProxySelector **/ GtkWidget * e_proxy_selector_new (ESourceRegistry *registry) { g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL); return g_object_new ( E_TYPE_PROXY_SELECTOR, "registry", registry, NULL); } /** * e_proxy_selector_refresh: * @selector: an #EProxySelector * * Rebuilds the @selector's list store with an updated list of #ESource * instances that describe a network proxy profile, without disrupting the * previously selected item (if possible). * * This funtion is called automatically in response to #ESourceRegistry * signals which are pertinent to the @selector. **/ void e_proxy_selector_refresh (EProxySelector *selector) { ETreeViewFrame *tree_view_frame; ESourceRegistry *registry; GtkTreeView *tree_view; GtkTreeModel *tree_model; ESource *builtin_source; ESource *selected; GList *list, *link; const gchar *extension_name; g_return_if_fail (E_IS_PROXY_SELECTOR (selector)); if (selector->priv->refresh_idle_id > 0) { g_source_remove (selector->priv->refresh_idle_id); selector->priv->refresh_idle_id = 0; } tree_view_frame = E_TREE_VIEW_FRAME (selector); tree_view = e_tree_view_frame_get_tree_view (tree_view_frame); tree_model = gtk_tree_view_get_model (tree_view); selected = e_proxy_selector_ref_selected (selector); gtk_list_store_clear (GTK_LIST_STORE (tree_model)); extension_name = E_SOURCE_EXTENSION_PROXY; registry = e_proxy_selector_get_registry (selector); list = e_source_registry_list_enabled (registry, extension_name); builtin_source = e_source_registry_ref_builtin_proxy (registry); g_warn_if_fail (builtin_source != NULL); /* Always list the built-in proxy profile first. */ link = g_list_find (list, builtin_source); if (link != NULL && list != link) { list = g_list_remove_link (list, link); list = g_list_concat (link, list); } for (link = list; link != NULL; link = g_list_next (link)) { ESource *source; GtkTreeIter iter; const gchar *display_name; source = E_SOURCE (link->data); display_name = e_source_get_display_name (source); gtk_list_store_append (GTK_LIST_STORE (tree_model), &iter); gtk_list_store_set ( GTK_LIST_STORE (tree_model), &iter, COLUMN_DISPLAY_NAME, display_name, COLUMN_SOURCE, source, -1); } g_clear_object (&builtin_source); g_list_free_full (list, (GDestroyNotify) g_object_unref); /* Try and restore the previous selected source or else pick * the built-in proxy profile, which is always listed first. */ e_proxy_selector_set_selected (selector, selected); g_clear_object (&selected); } /** * e_proxy_selector_get_registry: * @selector: an #EProxySelector * * Returns the #ESourceRegistry passed to e_proxy_selector_get_registry(). * * Returns: an #ESourceRegistry **/ ESourceRegistry * e_proxy_selector_get_registry (EProxySelector *selector) { g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), NULL); return selector->priv->registry; } /** * e_proxy_selector_ref_selected: * @selector: an #EProxySelector * * Returns the selected #ESource in @selector. * * The function tries to ensure a valid #ESource is always returned, * falling back to e_source_registry_ref_builtin_proxy() if necessary. * * The returned #ESource is referenced for thread-safety and must be * unreferenced with g_object_unref() when finished with it. * * Returns: an #ESource **/ ESource * e_proxy_selector_ref_selected (EProxySelector *selector) { ETreeViewFrame *tree_view_frame; GtkTreeView *tree_view; GtkTreeModel *tree_model; GtkTreeSelection *selection; GtkTreeIter iter; ESource *source = NULL; g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), NULL); tree_view_frame = E_TREE_VIEW_FRAME (selector); tree_view = e_tree_view_frame_get_tree_view (tree_view_frame); selection = gtk_tree_view_get_selection (tree_view); if (gtk_tree_selection_get_selected (selection, &tree_model, &iter)) { gtk_tree_model_get ( tree_model, &iter, COLUMN_SOURCE, &source, -1); } /* The built-in proxy profile is implicitly selected when * no proxy profile is actually selected in the tree view. */ if (source == NULL) { ESourceRegistry *registry; registry = e_proxy_selector_get_registry (selector); source = e_source_registry_ref_builtin_proxy (registry); g_return_val_if_fail (source != NULL, NULL); } return source; } /** * e_proxy_selector_set_selected: * @selector: an #EProxySelector * @source: an #ESource, or %NULL for the built-in proxy profile * * Finds the corresponding tree model row for @source, selects the row, * and returns %TRUE. If no corresponding tree model row for @source is * found, the selection remains unchanged and the function returns %FALSE. * * Returns: whether @source was selected **/ gboolean e_proxy_selector_set_selected (EProxySelector *selector, ESource *source) { ETreeViewFrame *tree_view_frame; GtkTreeView *tree_view; GtkTreeModel *tree_model; GtkTreeIter iter; gboolean iter_valid; g_return_val_if_fail (E_IS_PROXY_SELECTOR (selector), FALSE); g_return_val_if_fail (source == NULL || E_IS_SOURCE (source), FALSE); if (source == NULL) { ESourceRegistry *registry; registry = e_proxy_selector_get_registry (selector); source = e_source_registry_ref_builtin_proxy (registry); g_return_val_if_fail (source != NULL, FALSE); } tree_view_frame = E_TREE_VIEW_FRAME (selector); tree_view = e_tree_view_frame_get_tree_view (tree_view_frame); tree_model = gtk_tree_view_get_model (tree_view); iter_valid = gtk_tree_model_get_iter_first (tree_model, &iter); while (iter_valid) { ESource *candidate = NULL; gboolean match; gtk_tree_model_get ( tree_model, &iter, COLUMN_SOURCE, &candidate, -1); match = e_source_equal (source, candidate); g_object_unref (candidate); if (match) break; iter_valid = gtk_tree_model_iter_next (tree_model, &iter); } if (iter_valid) { GtkTreeSelection *selection; selection = gtk_tree_view_get_selection (tree_view); gtk_tree_selection_select_iter (selection, &iter); } return iter_valid; }