summaryrefslogtreecommitdiff
path: root/libcaja-private/caja-mime-application-chooser.c
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-12-01 22:24:23 -0300
committerPerberos <[email protected]>2011-12-01 22:24:23 -0300
commit0e004c696b0e68b2cff37a4c3315b022a35eaf43 (patch)
tree43261e815529cb9518ed7be37af13b846af8b26b /libcaja-private/caja-mime-application-chooser.c
downloadcaja-0e004c696b0e68b2cff37a4c3315b022a35eaf43.tar.bz2
caja-0e004c696b0e68b2cff37a4c3315b022a35eaf43.tar.xz
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'libcaja-private/caja-mime-application-chooser.c')
-rw-r--r--libcaja-private/caja-mime-application-chooser.c746
1 files changed, 746 insertions, 0 deletions
diff --git a/libcaja-private/caja-mime-application-chooser.c b/libcaja-private/caja-mime-application-chooser.c
new file mode 100644
index 00000000..a183f0c5
--- /dev/null
+++ b/libcaja-private/caja-mime-application-chooser.c
@@ -0,0 +1,746 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/*
+ caja-mime-application-chooser.c: an mime-application chooser
+
+ Copyright (C) 2004 Novell, Inc.
+ Copyright (C) 2007 Red Hat, Inc.
+
+ The Mate Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public License as
+ published by the Free Software Foundation; either version 2 of the
+ License, or (at your option) any later version.
+
+ The Mate Library is distributed in the hope that it will be useful,
+ but APPLICATIONOUT ANY WARRANTY; applicationout even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along application the Mate Library; see the file COPYING.LIB. If not,
+ write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+ Authors: Dave Camp <[email protected]>
+ Alexander Larsson <[email protected]>
+*/
+
+#include <config.h>
+#include "caja-mime-application-chooser.h"
+
+#include "caja-open-with-dialog.h"
+#include "caja-signaller.h"
+#include "caja-file.h"
+#include <eel/eel-stock-dialogs.h>
+#include <eel/eel-glib-extensions.h>
+#include <eel/eel-string.h>
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+struct _CajaMimeApplicationChooserDetails
+{
+ char *uri;
+
+ char *content_type;
+ char *extension;
+ char *type_description;
+ char *orig_mime_type;
+
+ guint refresh_timeout;
+
+ GtkWidget *label;
+ GtkWidget *entry;
+ GtkWidget *treeview;
+ GtkWidget *remove_button;
+
+ gboolean for_multiple_files;
+
+ GtkListStore *model;
+ GtkCellRenderer *toggle_renderer;
+};
+
+enum
+{
+ COLUMN_APPINFO,
+ COLUMN_DEFAULT,
+ COLUMN_ICON,
+ COLUMN_NAME,
+ NUM_COLUMNS
+};
+
+G_DEFINE_TYPE (CajaMimeApplicationChooser, caja_mime_application_chooser, GTK_TYPE_VBOX);
+
+static void refresh_model (CajaMimeApplicationChooser *chooser);
+static void refresh_model_soon (CajaMimeApplicationChooser *chooser);
+static void mime_type_data_changed_cb (GObject *signaller,
+ gpointer user_data);
+
+static void
+caja_mime_application_chooser_finalize (GObject *object)
+{
+ CajaMimeApplicationChooser *chooser;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (object);
+
+ if (chooser->details->refresh_timeout)
+ {
+ g_source_remove (chooser->details->refresh_timeout);
+ }
+
+ g_signal_handlers_disconnect_by_func (caja_signaller_get_current (),
+ G_CALLBACK (mime_type_data_changed_cb),
+ chooser);
+
+
+ g_free (chooser->details->uri);
+ g_free (chooser->details->content_type);
+ g_free (chooser->details->extension);
+ g_free (chooser->details->type_description);
+ g_free (chooser->details->orig_mime_type);
+
+ g_free (chooser->details);
+
+ G_OBJECT_CLASS (caja_mime_application_chooser_parent_class)->finalize (object);
+}
+
+static void
+caja_mime_application_chooser_destroy (GtkObject *object)
+{
+ GTK_OBJECT_CLASS (caja_mime_application_chooser_parent_class)->destroy (object);
+}
+
+static void
+caja_mime_application_chooser_class_init (CajaMimeApplicationChooserClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkObjectClass *object_class;
+
+ gobject_class = G_OBJECT_CLASS (class);
+ gobject_class->finalize = caja_mime_application_chooser_finalize;
+
+ object_class = GTK_OBJECT_CLASS (class);
+ object_class->destroy = caja_mime_application_chooser_destroy;
+}
+
+static void
+default_toggled_cb (GtkCellRendererToggle *renderer,
+ const char *path_str,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GError *error;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ path = gtk_tree_path_new_from_string (path_str);
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (chooser->details->model),
+ &iter, path))
+ {
+ gboolean is_default;
+ gboolean success;
+ GAppInfo *info;
+ char *message;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->model),
+ &iter,
+ COLUMN_DEFAULT, &is_default,
+ COLUMN_APPINFO, &info,
+ -1);
+
+ if (!is_default && info != NULL)
+ {
+ error = NULL;
+ if (chooser->details->extension)
+ {
+ success = g_app_info_set_as_default_for_extension (info,
+ chooser->details->extension,
+ &error);
+ }
+ else
+ {
+ success = g_app_info_set_as_default_for_type (info,
+ chooser->details->content_type,
+ &error);
+ }
+
+ if (!success)
+ {
+ message = g_strdup_printf (_("Could not set application as the default: %s"), error->message);
+ eel_show_error_dialog (_("Could not set as default application"),
+ message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (chooser))));
+ g_free (message);
+ g_error_free (error);
+ }
+
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "mime_data_changed");
+ }
+ g_object_unref (info);
+ }
+ gtk_tree_path_free (path);
+}
+
+static GAppInfo *
+get_selected_application (CajaMimeApplicationChooser *chooser)
+{
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+ GAppInfo *info;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->details->treeview));
+
+ info = NULL;
+ if (gtk_tree_selection_get_selected (selection,
+ NULL,
+ &iter))
+ {
+ gtk_tree_model_get (GTK_TREE_MODEL (chooser->details->model),
+ &iter,
+ COLUMN_APPINFO, &info,
+ -1);
+ }
+
+ return info;
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *selection,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+ GAppInfo *info;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ info = get_selected_application (chooser);
+ if (info)
+ {
+ gtk_widget_set_sensitive (chooser->details->remove_button,
+ g_app_info_can_remove_supports_type (info));
+
+ g_object_unref (info);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (chooser->details->remove_button,
+ FALSE);
+ }
+}
+
+static GtkWidget *
+create_tree_view (CajaMimeApplicationChooser *chooser)
+{
+ GtkWidget *treeview;
+ GtkListStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkTreeSelection *selection;
+
+ treeview = gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
+
+ store = gtk_list_store_new (NUM_COLUMNS,
+ G_TYPE_APP_INFO,
+ G_TYPE_BOOLEAN,
+ G_TYPE_ICON,
+ G_TYPE_STRING);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ COLUMN_NAME,
+ GTK_SORT_ASCENDING);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (treeview),
+ GTK_TREE_MODEL (store));
+ chooser->details->model = store;
+
+ renderer = gtk_cell_renderer_toggle_new ();
+ g_signal_connect (renderer, "toggled",
+ G_CALLBACK (default_toggled_cb),
+ chooser);
+ gtk_cell_renderer_toggle_set_radio (GTK_CELL_RENDERER_TOGGLE (renderer),
+ TRUE);
+
+ column = gtk_tree_view_column_new_with_attributes (_("Default"),
+ renderer,
+ "active",
+ COLUMN_DEFAULT,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+ chooser->details->toggle_renderer = renderer;
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ g_object_set (renderer, "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);
+ column = gtk_tree_view_column_new_with_attributes (_("Icon"),
+ renderer,
+ "gicon",
+ COLUMN_ICON,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Name"),
+ renderer,
+ "markup",
+ COLUMN_NAME,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (selection_changed_cb),
+ chooser);
+
+ return treeview;
+}
+
+static void
+add_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+ GtkWidget *dialog;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ if (chooser->details->for_multiple_files)
+ {
+ dialog = caja_add_application_dialog_new_for_multiple_files (chooser->details->extension,
+ chooser->details->orig_mime_type);
+ }
+ else
+ {
+ dialog = caja_add_application_dialog_new (chooser->details->uri,
+ chooser->details->orig_mime_type);
+ }
+ gtk_window_set_screen (GTK_WINDOW (dialog),
+ gtk_widget_get_screen (GTK_WIDGET (chooser)));
+ gtk_widget_show (dialog);
+}
+
+static void
+remove_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+ GError *error;
+ GAppInfo *info;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ info = get_selected_application (chooser);
+
+ if (info)
+ {
+ error = NULL;
+ if (!g_app_info_remove_supports_type (info,
+ chooser->details->content_type,
+ &error))
+ {
+ eel_show_error_dialog (_("Could not remove application"),
+ error->message,
+ GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (chooser))));
+ g_error_free (error);
+
+ }
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "mime_data_changed");
+ g_object_unref (info);
+ }
+}
+
+static void
+reset_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ g_app_info_reset_type_associations (chooser->details->content_type);
+
+ g_signal_emit_by_name (caja_signaller_get_current (),
+ "mime_data_changed");
+}
+
+static void
+mime_type_data_changed_cb (GObject *signaller,
+ gpointer user_data)
+{
+ CajaMimeApplicationChooser *chooser;
+
+ chooser = CAJA_MIME_APPLICATION_CHOOSER (user_data);
+
+ refresh_model_soon (chooser);
+}
+
+static void
+caja_mime_application_chooser_init (CajaMimeApplicationChooser *chooser)
+{
+ GtkWidget *box;
+ GtkWidget *scrolled;
+ GtkWidget *button;
+
+ chooser->details = g_new0 (CajaMimeApplicationChooserDetails, 1);
+
+ chooser->details->for_multiple_files = FALSE;
+ gtk_container_set_border_width (GTK_CONTAINER (chooser), 8);
+ gtk_box_set_spacing (GTK_BOX (chooser), 0);
+ gtk_box_set_homogeneous (GTK_BOX (chooser), FALSE);
+
+ chooser->details->label = gtk_label_new ("");
+ gtk_misc_set_alignment (GTK_MISC (chooser->details->label), 0.0, 0.5);
+ gtk_label_set_line_wrap (GTK_LABEL (chooser->details->label), TRUE);
+ gtk_label_set_line_wrap_mode (GTK_LABEL (chooser->details->label),
+ PANGO_WRAP_WORD_CHAR);
+ gtk_box_pack_start (GTK_BOX (chooser), chooser->details->label,
+ FALSE, FALSE, 0);
+
+ gtk_widget_show (chooser->details->label);
+
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+ GTK_SHADOW_IN);
+
+ gtk_widget_show (scrolled);
+ gtk_box_pack_start (GTK_BOX (chooser), scrolled, TRUE, TRUE, 6);
+
+ chooser->details->treeview = create_tree_view (chooser);
+ gtk_widget_show (chooser->details->treeview);
+
+ gtk_container_add (GTK_CONTAINER (scrolled),
+ chooser->details->treeview);
+
+ box = gtk_hbutton_box_new ();
+ gtk_box_set_spacing (GTK_BOX (box), 6);
+ gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_END);
+ gtk_box_pack_start (GTK_BOX (chooser), box, FALSE, FALSE, 6);
+ gtk_widget_show (box);
+
+ button = gtk_button_new_from_stock (GTK_STOCK_ADD);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (add_clicked_cb),
+ chooser);
+
+ gtk_widget_show (button);
+ gtk_container_add (GTK_CONTAINER (box), button);
+
+ button = gtk_button_new_from_stock (GTK_STOCK_REMOVE);
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (remove_clicked_cb),
+ chooser);
+
+ gtk_widget_show (button);
+ gtk_container_add (GTK_CONTAINER (box), button);
+
+ chooser->details->remove_button = button;
+
+ button = gtk_button_new_with_label (_("Reset"));
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (reset_clicked_cb),
+ chooser);
+
+ gtk_widget_show (button);
+ gtk_container_add (GTK_CONTAINER (box), button);
+
+ g_signal_connect (caja_signaller_get_current (),
+ "mime_data_changed",
+ G_CALLBACK (mime_type_data_changed_cb),
+ chooser);
+}
+
+static char *
+get_extension (const char *basename)
+{
+ char *p;
+
+ p = strrchr (basename, '.');
+
+ if (p && *(p + 1) != '\0')
+ {
+ return g_strdup (p + 1);
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+static gboolean
+refresh_model_timeout (gpointer data)
+{
+ CajaMimeApplicationChooser *chooser = data;
+
+ chooser->details->refresh_timeout = 0;
+
+ refresh_model (chooser);
+
+ return FALSE;
+}
+
+/* This adds a slight delay so that we're sure the mime data is
+ done writing */
+static void
+refresh_model_soon (CajaMimeApplicationChooser *chooser)
+{
+ if (chooser->details->refresh_timeout != 0)
+ return;
+
+ chooser->details->refresh_timeout =
+ g_timeout_add (300,
+ refresh_model_timeout,
+ chooser);
+}
+
+static void
+refresh_model (CajaMimeApplicationChooser *chooser)
+{
+ GList *applications;
+ GAppInfo *default_app;
+ GList *l;
+ GtkTreeSelection *selection;
+ GtkTreeViewColumn *column;
+
+ column = gtk_tree_view_get_column (GTK_TREE_VIEW (chooser->details->treeview), 0);
+ gtk_tree_view_column_set_visible (column, TRUE);
+
+ gtk_list_store_clear (chooser->details->model);
+
+ applications = g_app_info_get_all_for_type (chooser->details->content_type);
+ default_app = g_app_info_get_default_for_type (chooser->details->content_type, FALSE);
+
+ for (l = applications; l != NULL; l = l->next)
+ {
+ GtkTreeIter iter;
+ gboolean is_default;
+ GAppInfo *application;
+ char *escaped;
+ GIcon *icon;
+
+ application = l->data;
+
+ is_default = default_app && g_app_info_equal (default_app, application);
+
+ escaped = g_markup_escape_text (g_app_info_get_display_name (application), -1);
+
+ icon = g_app_info_get_icon (application);
+
+ gtk_list_store_append (chooser->details->model, &iter);
+ gtk_list_store_set (chooser->details->model, &iter,
+ COLUMN_APPINFO, application,
+ COLUMN_DEFAULT, is_default,
+ COLUMN_ICON, icon,
+ COLUMN_NAME, escaped,
+ -1);
+
+ g_free (escaped);
+ }
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (chooser->details->treeview));
+
+ if (applications)
+ {
+ g_object_set (chooser->details->toggle_renderer,
+ "visible", TRUE,
+ NULL);
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+ }
+ else
+ {
+ GtkTreeIter iter;
+ char *name;
+
+ gtk_tree_view_column_set_visible (column, FALSE);
+ gtk_list_store_append (chooser->details->model, &iter);
+ name = g_strdup_printf ("<i>%s</i>", _("No applications selected"));
+ gtk_list_store_set (chooser->details->model, &iter,
+ COLUMN_NAME, name,
+ COLUMN_APPINFO, NULL,
+ -1);
+ g_free (name);
+
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_NONE);
+ }
+
+ if (default_app)
+ {
+ g_object_unref (default_app);
+ }
+
+ eel_g_object_list_free (applications);
+}
+
+static void
+set_extension_and_description (CajaMimeApplicationChooser *chooser,
+ const char *extension,
+ const char *mime_type)
+{
+ if (extension != NULL &&
+ g_content_type_is_unknown (mime_type))
+ {
+ chooser->details->extension = g_strdup (extension);
+ chooser->details->content_type = g_strdup_printf ("application/x-extension-%s", extension);
+ /* the %s here is a file extension */
+ chooser->details->type_description =
+ g_strdup_printf (_("%s document"), extension);
+ }
+ else
+ {
+ char *description;
+
+ chooser->details->content_type = g_strdup (mime_type);
+ description = g_content_type_get_description (mime_type);
+ if (description == NULL)
+ {
+ description = g_strdup (_("Unknown"));
+ }
+
+ chooser->details->type_description = description;
+ }
+}
+
+static gboolean
+set_uri_and_type (CajaMimeApplicationChooser *chooser,
+ const char *uri,
+ const char *mime_type)
+{
+ char *label;
+ char *name;
+ char *emname;
+ char *extension;
+ GFile *file;
+
+ chooser->details->uri = g_strdup (uri);
+
+ file = g_file_new_for_uri (uri);
+ name = g_file_get_basename (file);
+ g_object_unref (file);
+
+ chooser->details->orig_mime_type = g_strdup (mime_type);
+
+ extension = get_extension (name);
+ set_extension_and_description (CAJA_MIME_APPLICATION_CHOOSER (chooser),
+ extension, mime_type);
+ g_free (extension);
+
+ /* first %s is filename, second %s is mime-type description */
+ emname = g_strdup_printf ("<i>%s</i>", name);
+ label = g_strdup_printf (_("Select an application to open %s and other files of type \"%s\""),
+ emname, chooser->details->type_description);
+ g_free (emname);
+
+ gtk_label_set_markup (GTK_LABEL (chooser->details->label), label);
+
+ g_free (label);
+ g_free (name);
+
+ refresh_model (chooser);
+
+ return TRUE;
+}
+
+static char *
+get_extension_from_file (CajaFile *nfile)
+{
+ char *name;
+ char *extension;
+
+ name = caja_file_get_name (nfile);
+ extension = get_extension (name);
+
+ g_free (name);
+
+ return extension;
+}
+
+static gboolean
+set_uri_and_type_for_multiple_files (CajaMimeApplicationChooser *chooser,
+ GList *uris,
+ const char *mime_type)
+{
+ char *label;
+ char *first_extension;
+ gboolean same_extension;
+ GList *iter;
+
+ chooser->details->for_multiple_files = TRUE;
+ chooser->details->uri = NULL;
+ chooser->details->orig_mime_type = g_strdup (mime_type);
+ same_extension = TRUE;
+ first_extension = get_extension_from_file (CAJA_FILE (uris->data));
+ iter = uris->next;
+
+ while (iter != NULL)
+ {
+ char *extension_current;
+
+ extension_current = get_extension_from_file (CAJA_FILE (iter->data));
+ if (eel_strcmp (first_extension, extension_current))
+ {
+ same_extension = FALSE;
+ g_free (extension_current);
+ break;
+ }
+ iter = iter->next;
+
+ g_free (extension_current);
+ }
+ if (!same_extension)
+ {
+ set_extension_and_description (CAJA_MIME_APPLICATION_CHOOSER (chooser),
+ NULL, mime_type);
+ }
+ else
+ {
+ set_extension_and_description (CAJA_MIME_APPLICATION_CHOOSER (chooser),
+ first_extension, mime_type);
+ }
+
+ g_free (first_extension);
+
+ label = g_strdup_printf (_("Open all files of type \"%s\" with:"),
+ chooser->details->type_description);
+ gtk_label_set_markup (GTK_LABEL (chooser->details->label), label);
+
+ g_free (label);
+
+ refresh_model (chooser);
+
+ return TRUE;
+}
+
+GtkWidget *
+caja_mime_application_chooser_new (const char *uri,
+ const char *mime_type)
+{
+ GtkWidget *chooser;
+
+ chooser = gtk_widget_new (CAJA_TYPE_MIME_APPLICATION_CHOOSER, NULL);
+
+ set_uri_and_type (CAJA_MIME_APPLICATION_CHOOSER (chooser), uri, mime_type);
+
+ return chooser;
+}
+
+GtkWidget *
+caja_mime_application_chooser_new_for_multiple_files (GList *uris,
+ const char *mime_type)
+{
+ GtkWidget *chooser;
+
+ chooser = gtk_widget_new (CAJA_TYPE_MIME_APPLICATION_CHOOSER, NULL);
+
+ set_uri_and_type_for_multiple_files (CAJA_MIME_APPLICATION_CHOOSER (chooser),
+ uris, mime_type);
+
+ return chooser;
+}
+