/* * gtkappchooserwidget.c: an app-chooser widget * * Copyright (C) 2004 Novell, Inc. * Copyright (C) 2007, 2010 Red Hat, Inc. * * This 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. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . * * Authors: Dave Camp * Alexander Larsson * Cosimo Cecchi */ #include "config.h" #include "gtkappchooserwidget.h" #include "gtkintl.h" #include "gtkmarshalers.h" #include "gtkappchooserwidget.h" #include "gtkappchooserprivate.h" #include "gtkliststore.h" #include "gtktreeview.h" #include "gtktreeselection.h" #include "gtktreemodelsort.h" #include "gtkorientable.h" #include "gtkscrolledwindow.h" #include "gtklabel.h" #include "gtkgestureclick.h" #include "gtkwidgetprivate.h" #include #include #include /** * GtkAppChooserWidget: * * `GtkAppChooserWidget` is a widget for selecting applications. * * It is the main building block for [class@Gtk.AppChooserDialog]. * Most applications only need to use the latter; but you can use * this widget as part of a larger widget if you have special needs. * * `GtkAppChooserWidget` offers detailed control over what applications * are shown, using the * [property@Gtk.AppChooserWidget:show-default], * [property@Gtk.AppChooserWidget:show-recommended], * [property@Gtk.AppChooserWidget:show-fallback], * [property@Gtk.AppChooserWidget:show-other] and * [property@Gtk.AppChooserWidget:show-all] properties. See the * [iface@Gtk.AppChooser] documentation for more information about these * groups of applications. * * To keep track of the selected application, use the * [signal@Gtk.AppChooserWidget::application-selected] and * [signal@Gtk.AppChooserWidget::application-activated] signals. * * # CSS nodes * * `GtkAppChooserWidget` has a single CSS node with name appchooser. */ typedef struct _GtkAppChooserWidgetClass GtkAppChooserWidgetClass; struct _GtkAppChooserWidget { GtkWidget parent_instance; GAppInfo *selected_app_info; GtkWidget *overlay; char *content_type; char *default_text; guint show_default : 1; guint show_recommended : 1; guint show_fallback : 1; guint show_other : 1; guint show_all : 1; GtkWidget *program_list; GtkListStore *program_list_store; GtkWidget *no_apps_label; GtkWidget *no_apps; GtkTreeViewColumn *column; GtkCellRenderer *padding_renderer; GtkCellRenderer *secondary_padding; GAppInfoMonitor *monitor; GtkWidget *popup_menu; }; struct _GtkAppChooserWidgetClass { GtkWidgetClass parent_class; void (* application_selected) (GtkAppChooserWidget *self, GAppInfo *app_info); void (* application_activated) (GtkAppChooserWidget *self, GAppInfo *app_info); }; enum { COLUMN_APP_INFO, COLUMN_GICON, COLUMN_NAME, COLUMN_DESC, COLUMN_EXEC, COLUMN_DEFAULT, COLUMN_HEADING, COLUMN_HEADING_TEXT, COLUMN_RECOMMENDED, COLUMN_FALLBACK, NUM_COLUMNS }; enum { PROP_CONTENT_TYPE = 1, PROP_GFILE, PROP_SHOW_DEFAULT, PROP_SHOW_RECOMMENDED, PROP_SHOW_FALLBACK, PROP_SHOW_OTHER, PROP_SHOW_ALL, PROP_DEFAULT_TEXT, N_PROPERTIES }; enum { SIGNAL_APPLICATION_SELECTED, SIGNAL_APPLICATION_ACTIVATED, N_SIGNALS }; static guint signals[N_SIGNALS] = { 0, }; static void gtk_app_chooser_widget_iface_init (GtkAppChooserIface *iface); G_DEFINE_TYPE_WITH_CODE (GtkAppChooserWidget, gtk_app_chooser_widget, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER, gtk_app_chooser_widget_iface_init)); static void refresh_and_emit_app_selected (GtkAppChooserWidget *self, GtkTreeSelection *selection) { GtkTreeModel *model; GtkTreeIter iter; GAppInfo *info = NULL; gboolean should_emit = FALSE; if (gtk_tree_selection_get_selected (selection, &model, &iter)) gtk_tree_model_get (model, &iter, COLUMN_APP_INFO, &info, -1); if (info == NULL) return; if (self->selected_app_info) { if (!g_app_info_equal (self->selected_app_info, info)) { should_emit = TRUE; g_set_object (&self->selected_app_info, info); } } else { should_emit = TRUE; g_set_object (&self->selected_app_info, info); } g_object_unref (info); if (should_emit) g_signal_emit (self, signals[SIGNAL_APPLICATION_SELECTED], 0, self->selected_app_info); } static gboolean path_is_heading (GtkTreeView *view, GtkTreePath *path) { GtkTreeIter iter; GtkTreeModel *model; gboolean res; model = gtk_tree_view_get_model (view); gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, COLUMN_HEADING, &res, -1); return res; } static void program_list_selection_activated (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data) { GtkAppChooserWidget *self = user_data; GtkTreeSelection *selection; if (path_is_heading (view, path)) return; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->program_list)); refresh_and_emit_app_selected (self, selection); g_signal_emit (self, signals[SIGNAL_APPLICATION_ACTIVATED], 0, self->selected_app_info); } static gboolean gtk_app_chooser_search_equal_func (GtkTreeModel *model, int column, const char *key, GtkTreeIter *iter, gpointer user_data) { char *name; char *exec_name; gboolean ret; if (key != NULL) { ret = TRUE; gtk_tree_model_get (model, iter, COLUMN_NAME, &name, COLUMN_EXEC, &exec_name, -1); if ((name != NULL && g_str_match_string (key, name, TRUE)) || (exec_name != NULL && g_str_match_string (key, exec_name, FALSE))) ret = FALSE; g_free (name); g_free (exec_name); return ret; } else { return TRUE; } } static int gtk_app_chooser_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) { gboolean a_recommended, b_recommended; gboolean a_fallback, b_fallback; gboolean a_heading, b_heading; gboolean a_default, b_default; char *a_name, *b_name, *a_casefold, *b_casefold; int retval = 0; /* this returns: * - <0 if a should show before b * - =0 if a is the same as b * - >0 if a should show after b */ gtk_tree_model_get (model, a, COLUMN_NAME, &a_name, COLUMN_RECOMMENDED, &a_recommended, COLUMN_FALLBACK, &a_fallback, COLUMN_HEADING, &a_heading, COLUMN_DEFAULT, &a_default, -1); gtk_tree_model_get (model, b, COLUMN_NAME, &b_name, COLUMN_RECOMMENDED, &b_recommended, COLUMN_FALLBACK, &b_fallback, COLUMN_HEADING, &b_heading, COLUMN_DEFAULT, &b_default, -1); /* the default one always wins */ if (a_default && !b_default) { retval = -1; goto out; } if (b_default && !a_default) { retval = 1; goto out; } /* the recommended one always wins */ if (a_recommended && !b_recommended) { retval = -1; goto out; } if (b_recommended && !a_recommended) { retval = 1; goto out; } /* the recommended one always wins */ if (a_fallback && !b_fallback) { retval = -1; goto out; } if (b_fallback && !a_fallback) { retval = 1; goto out; } /* they're both recommended/fallback or not, so if one is a heading, wins */ if (a_heading) { retval = -1; goto out; } if (b_heading) { retval = 1; goto out; } /* don't order by name recommended applications, but use GLib's ordering */ if (!a_recommended) { a_casefold = a_name != NULL ? g_utf8_casefold (a_name, -1) : NULL; b_casefold = b_name != NULL ? g_utf8_casefold (b_name, -1) : NULL; retval = g_strcmp0 (a_casefold, b_casefold); g_free (a_casefold); g_free (b_casefold); } out: g_free (a_name); g_free (b_name); return retval; } static void padding_cell_renderer_func (GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) { gboolean heading; gtk_tree_model_get (model, iter, COLUMN_HEADING, &heading, -1); if (heading) g_object_set (cell, "visible", FALSE, "xpad", 0, "ypad", 0, NULL); else g_object_set (cell, "visible", TRUE, "xpad", 3, "ypad", 3, NULL); } static gboolean gtk_app_chooser_selection_func (GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean path_currently_selected, gpointer user_data) { GtkTreeIter iter; gboolean heading; gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, COLUMN_HEADING, &heading, -1); return !heading; } static int compare_apps_func (gconstpointer a, gconstpointer b) { return !g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b)); } static gboolean gtk_app_chooser_widget_add_section (GtkAppChooserWidget *self, const char *heading_title, gboolean show_headings, gboolean recommended, gboolean fallback, GList *applications, GList *exclude_apps) { gboolean heading_added, unref_icon; GtkTreeIter iter; GAppInfo *app; char *app_string, *bold_string; GIcon *icon; GList *l; gboolean retval; retval = FALSE; heading_added = FALSE; bold_string = g_strdup_printf ("%s", heading_title); for (l = applications; l != NULL; l = l->next) { app = l->data; if (self->content_type != NULL && !g_app_info_supports_uris (app) && !g_app_info_supports_files (app)) continue; if (g_list_find_custom (exclude_apps, app, (GCompareFunc) compare_apps_func)) continue; if (!heading_added && show_headings) { gtk_list_store_append (self->program_list_store, &iter); gtk_list_store_set (self->program_list_store, &iter, COLUMN_HEADING_TEXT, bold_string, COLUMN_HEADING, TRUE, COLUMN_RECOMMENDED, recommended, COLUMN_FALLBACK, fallback, -1); heading_added = TRUE; } app_string = g_markup_printf_escaped ("%s", g_app_info_get_name (app) != NULL ? g_app_info_get_name (app) : ""); icon = g_app_info_get_icon (app); unref_icon = FALSE; if (icon == NULL) { icon = g_themed_icon_new ("application-x-executable"); unref_icon = TRUE; } gtk_list_store_append (self->program_list_store, &iter); gtk_list_store_set (self->program_list_store, &iter, COLUMN_APP_INFO, app, COLUMN_GICON, icon, COLUMN_NAME, g_app_info_get_name (app), COLUMN_DESC, app_string, COLUMN_EXEC, g_app_info_get_executable (app), COLUMN_HEADING, FALSE, COLUMN_RECOMMENDED, recommended, COLUMN_FALLBACK, fallback, -1); retval = TRUE; g_free (app_string); if (unref_icon) g_object_unref (icon); } g_free (bold_string); return retval; } static void gtk_app_chooser_add_default (GtkAppChooserWidget *self, GAppInfo *app) { GtkTreeIter iter; GIcon *icon; char *string; gboolean unref_icon; unref_icon = FALSE; string = g_strdup_printf ("%s", _("Default Application")); gtk_list_store_append (self->program_list_store, &iter); gtk_list_store_set (self->program_list_store, &iter, COLUMN_HEADING_TEXT, string, COLUMN_HEADING, TRUE, COLUMN_DEFAULT, TRUE, -1); g_free (string); string = g_markup_printf_escaped ("%s", g_app_info_get_name (app) != NULL ? g_app_info_get_name (app) : ""); icon = g_app_info_get_icon (app); if (icon == NULL) { icon = g_themed_icon_new ("application-x-executable"); unref_icon = TRUE; } gtk_list_store_append (self->program_list_store, &iter); gtk_list_store_set (self->program_list_store, &iter, COLUMN_APP_INFO, app, COLUMN_GICON, icon, COLUMN_NAME, g_app_info_get_name (app), COLUMN_DESC, string, COLUMN_EXEC, g_app_info_get_executable (app), COLUMN_HEADING, FALSE, COLUMN_DEFAULT, TRUE, -1); g_free (string); if (unref_icon) g_object_unref (icon); } static void update_no_applications_label (GtkAppChooserWidget *self) { char *text = NULL, *desc = NULL; const char *string; if (self->default_text == NULL) { if (self->content_type) desc = g_content_type_get_description (self->content_type); string = text = g_strdup_printf (_("No applications found for ā€œ%sā€."), desc); g_free (desc); } else { string = self->default_text; } gtk_label_set_text (GTK_LABEL (self->no_apps_label), string); g_free (text); } static void gtk_app_chooser_widget_select_first (GtkAppChooserWidget *self) { GtkTreeIter iter; GAppInfo *info = NULL; GtkTreeModel *model; model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->program_list)); if (!gtk_tree_model_get_iter_first (model, &iter)) return; while (info == NULL) { gtk_tree_model_get (model, &iter, COLUMN_APP_INFO, &info, -1); if (info != NULL) break; if (!gtk_tree_model_iter_next (model, &iter)) break; } if (info != NULL) { GtkTreeSelection *selection; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->program_list)); gtk_tree_selection_select_iter (selection, &iter); g_object_unref (info); } } static void gtk_app_chooser_widget_real_add_items (GtkAppChooserWidget *self) { GList *all_applications = NULL; GList *recommended_apps = NULL; GList *fallback_apps = NULL; GList *exclude_apps = NULL; GAppInfo *default_app = NULL; gboolean show_headings; gboolean apps_added; show_headings = TRUE; apps_added = FALSE; if (self->show_all) show_headings = FALSE; if (self->show_default && self->content_type) { default_app = g_app_info_get_default_for_type (self->content_type, FALSE); if (default_app != NULL) { gtk_app_chooser_add_default (self, default_app); apps_added = TRUE; exclude_apps = g_list_prepend (exclude_apps, default_app); } } #ifndef G_OS_WIN32 if ((self->content_type && self->show_recommended) || self->show_all) { if (self->content_type) recommended_apps = g_app_info_get_recommended_for_type (self->content_type); apps_added |= gtk_app_chooser_widget_add_section (self, _("Recommended Applications"), show_headings, !self->show_all, /* mark as recommended */ FALSE, /* mark as fallback */ recommended_apps, exclude_apps); exclude_apps = g_list_concat (exclude_apps, g_list_copy (recommended_apps)); } if ((self->content_type && self->show_fallback) || self->show_all) { if (self->content_type) fallback_apps = g_app_info_get_fallback_for_type (self->content_type); apps_added |= gtk_app_chooser_widget_add_section (self, _("Related Applications"), show_headings, FALSE, /* mark as recommended */ !self->show_all, /* mark as fallback */ fallback_apps, exclude_apps); exclude_apps = g_list_concat (exclude_apps, g_list_copy (fallback_apps)); } #endif if (self->show_other || self->show_all) { all_applications = g_app_info_get_all (); apps_added |= gtk_app_chooser_widget_add_section (self, _("Other Applications"), show_headings, FALSE, FALSE, all_applications, exclude_apps); } if (!apps_added) update_no_applications_label (self); gtk_widget_set_visible (self->no_apps, !apps_added); gtk_app_chooser_widget_select_first (self); if (default_app != NULL) g_object_unref (default_app); g_list_free_full (all_applications, g_object_unref); g_list_free_full (recommended_apps, g_object_unref); g_list_free_full (fallback_apps, g_object_unref); g_list_free (exclude_apps); } static void gtk_app_chooser_widget_initialize_items (GtkAppChooserWidget *self) { /* initial padding */ g_object_set (self->padding_renderer, "xpad", self->show_all ? 0 : 6, NULL); /* populate the widget */ gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } static void app_info_changed (GAppInfoMonitor *monitor, GtkAppChooserWidget *self) { gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } static void gtk_app_chooser_widget_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object); switch (property_id) { case PROP_CONTENT_TYPE: self->content_type = g_value_dup_string (value); break; case PROP_SHOW_DEFAULT: gtk_app_chooser_widget_set_show_default (self, g_value_get_boolean (value)); break; case PROP_SHOW_RECOMMENDED: gtk_app_chooser_widget_set_show_recommended (self, g_value_get_boolean (value)); break; case PROP_SHOW_FALLBACK: gtk_app_chooser_widget_set_show_fallback (self, g_value_get_boolean (value)); break; case PROP_SHOW_OTHER: gtk_app_chooser_widget_set_show_other (self, g_value_get_boolean (value)); break; case PROP_SHOW_ALL: gtk_app_chooser_widget_set_show_all (self, g_value_get_boolean (value)); break; case PROP_DEFAULT_TEXT: gtk_app_chooser_widget_set_default_text (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gtk_app_chooser_widget_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object); switch (property_id) { case PROP_CONTENT_TYPE: g_value_set_string (value, self->content_type); break; case PROP_SHOW_DEFAULT: g_value_set_boolean (value, self->show_default); break; case PROP_SHOW_RECOMMENDED: g_value_set_boolean (value, self->show_recommended); break; case PROP_SHOW_FALLBACK: g_value_set_boolean (value, self->show_fallback); break; case PROP_SHOW_OTHER: g_value_set_boolean (value, self->show_other); break; case PROP_SHOW_ALL: g_value_set_boolean (value, self->show_all); break; case PROP_DEFAULT_TEXT: g_value_set_string (value, self->default_text); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gtk_app_chooser_widget_constructed (GObject *object) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object); if (G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->constructed != NULL) G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->constructed (object); gtk_app_chooser_widget_initialize_items (self); } static void gtk_app_chooser_widget_finalize (GObject *object) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object); g_free (self->content_type); g_free (self->default_text); g_signal_handlers_disconnect_by_func (self->monitor, app_info_changed, self); g_object_unref (self->monitor); G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->finalize (object); } static void gtk_app_chooser_widget_dispose (GObject *object) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object); g_clear_object (&self->selected_app_info); if (self->overlay) { gtk_widget_unparent (self->overlay); self->overlay = NULL; } G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->dispose (object); } static void gtk_app_chooser_widget_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (widget); gtk_widget_measure (self->overlay, orientation, for_size, minimum, natural, minimum_baseline, natural_baseline); } static void gtk_app_chooser_widget_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (widget); gtk_widget_snapshot_child (widget, self->overlay, snapshot); } static void gtk_app_chooser_widget_size_allocate (GtkWidget *widget, int width, int height, int baseline) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (widget); GTK_WIDGET_CLASS (gtk_app_chooser_widget_parent_class)->size_allocate (widget, width, height, baseline); gtk_widget_size_allocate (self->overlay, &(GtkAllocation) { 0, 0, width, height },baseline); } static void gtk_app_chooser_widget_class_init (GtkAppChooserWidgetClass *klass) { GtkWidgetClass *widget_class; GObjectClass *gobject_class; GParamSpec *pspec; gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = gtk_app_chooser_widget_dispose; gobject_class->finalize = gtk_app_chooser_widget_finalize; gobject_class->set_property = gtk_app_chooser_widget_set_property; gobject_class->get_property = gtk_app_chooser_widget_get_property; gobject_class->constructed = gtk_app_chooser_widget_constructed; widget_class = GTK_WIDGET_CLASS (klass); widget_class->measure = gtk_app_chooser_widget_measure; widget_class->size_allocate = gtk_app_chooser_widget_size_allocate; widget_class->snapshot = gtk_app_chooser_widget_snapshot; g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type"); /** * GtkAppChooserWidget:show-default: (attributes org.gtk.Property.get=gtk_app_chooser_widget_get_show_default org.gtk.Property.set=gtk_app_chooser_widget_set_show_default) * * Determines whether the app chooser should show the default * handler for the content type in a separate section. * * If %FALSE, the default handler is listed among the recommended * applications. */ pspec = g_param_spec_boolean ("show-default", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_property (gobject_class, PROP_SHOW_DEFAULT, pspec); /** * GtkAppChooserWidget:show-recommended: (attributes org.gtk.Property.get=gtk_app_chooser_widget_get_show_recommended org.gtk.Property.set=gtk_app_chooser_widget_set_show_recommended) * * Determines whether the app chooser should show a section * for recommended applications. * * If %FALSE, the recommended applications are listed * among the other applications. */ pspec = g_param_spec_boolean ("show-recommended", NULL, NULL, TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_property (gobject_class, PROP_SHOW_RECOMMENDED, pspec); /** * GtkAppChooserWidget:show-fallback: (attributes org.gtk.Property.get=gtk_app_chooser_widget_get_show_fallback org.gtk.Property.set=gtk_app_chooser_widget_set_show_fallback) * * Determines whether the app chooser should show a section * for fallback applications. * * If %FALSE, the fallback applications are listed among the * other applications. */ pspec = g_param_spec_boolean ("show-fallback", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_property (gobject_class, PROP_SHOW_FALLBACK, pspec); /** * GtkAppChooserWidget:show-other: (attributes org.gtk.Property.get=gtk_app_chooser_widget_get_show_other org.gtk.Property.set=gtk_app_chooser_widget_set_show_other) * * Determines whether the app chooser should show a section * for other applications. */ pspec = g_param_spec_boolean ("show-other", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_property (gobject_class, PROP_SHOW_OTHER, pspec); /** * GtkAppChooserWidget:show-all: (attributes org.gtk.Property.get=gtk_app_chooser_widget_get_show_all org.gtk.Property.set=gtk_app_chooser_widget_set_show_all) * * If %TRUE, the app chooser presents all applications * in a single list, without subsections for default, * recommended or related applications. */ pspec = g_param_spec_boolean ("show-all", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_property (gobject_class, PROP_SHOW_ALL, pspec); /** * GtkAppChooserWidget:default-text: (attributes org.gtk.Property.get=gtk_app_chooser_widget_get_default_text org.gtk.Property.set=gtk_app_chooser_widget_set_default_text) * * The text that appears in the widget when there are no applications * for the given content type. */ pspec = g_param_spec_string ("default-text", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_property (gobject_class, PROP_DEFAULT_TEXT, pspec); /** * GtkAppChooserWidget::application-selected: * @self: the object which received the signal * @application: the selected `GAppInfo` * * Emitted when an application item is selected from the widget's list. */ signals[SIGNAL_APPLICATION_SELECTED] = g_signal_new (I_("application-selected"), GTK_TYPE_APP_CHOOSER_WIDGET, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkAppChooserWidgetClass, application_selected), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_APP_INFO); /** * GtkAppChooserWidget::application-activated: * @self: the object which received the signal * @application: the activated `GAppInfo` * * Emitted when an application item is activated from the widget's list. * * This usually happens when the user double clicks an item, or an item * is selected and the user presses one of the keys Space, Shift+Space, * Return or Enter. */ signals[SIGNAL_APPLICATION_ACTIVATED] = g_signal_new (I_("application-activated"), GTK_TYPE_APP_CHOOSER_WIDGET, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GtkAppChooserWidgetClass, application_activated), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_APP_INFO); /* Bind class to template */ gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkappchooserwidget.ui"); gtk_widget_class_bind_template_child (widget_class, GtkAppChooserWidget, program_list); gtk_widget_class_bind_template_child (widget_class, GtkAppChooserWidget, program_list_store); gtk_widget_class_bind_template_child (widget_class, GtkAppChooserWidget, column); gtk_widget_class_bind_template_child (widget_class, GtkAppChooserWidget, padding_renderer); gtk_widget_class_bind_template_child (widget_class, GtkAppChooserWidget, secondary_padding); gtk_widget_class_bind_template_child (widget_class, GtkAppChooserWidget, no_apps_label); gtk_widget_class_bind_template_child (widget_class, GtkAppChooserWidget, no_apps); gtk_widget_class_bind_template_child (widget_class, GtkAppChooserWidget, overlay); gtk_widget_class_bind_template_callback (widget_class, refresh_and_emit_app_selected); gtk_widget_class_bind_template_callback (widget_class, program_list_selection_activated); gtk_widget_class_set_css_name (widget_class, I_("appchooser")); } static void gtk_app_chooser_widget_init (GtkAppChooserWidget *self) { GtkTreeSelection *selection; GtkTreeModel *sort; gtk_widget_init_template (GTK_WIDGET (self)); /* Various parts of the GtkTreeView code need custom code to setup, mostly * because we lack signals to connect to, or properties to set. */ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->program_list)); gtk_tree_selection_set_select_function (selection, gtk_app_chooser_selection_func, self, NULL); sort = gtk_tree_view_get_model (GTK_TREE_VIEW (self->program_list)); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort), COLUMN_NAME, GTK_SORT_ASCENDING); gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sort), COLUMN_NAME, gtk_app_chooser_sort_func, self, NULL); gtk_tree_view_set_search_column (GTK_TREE_VIEW (self->program_list), COLUMN_NAME); gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (self->program_list), gtk_app_chooser_search_equal_func, NULL, NULL); gtk_tree_view_column_set_cell_data_func (self->column, self->secondary_padding, padding_cell_renderer_func, NULL, NULL); self->monitor = g_app_info_monitor_get (); g_signal_connect (self->monitor, "changed", G_CALLBACK (app_info_changed), self); } static GAppInfo * gtk_app_chooser_widget_get_app_info (GtkAppChooser *object) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object); if (self->selected_app_info == NULL) return NULL; return g_object_ref (self->selected_app_info); } static void gtk_app_chooser_widget_refresh (GtkAppChooser *object) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object); if (self->program_list_store != NULL) { gtk_list_store_clear (self->program_list_store); /* don't add additional xpad if we don't have headings */ g_object_set (self->padding_renderer, "visible", !self->show_all, NULL); gtk_app_chooser_widget_real_add_items (self); } } static void gtk_app_chooser_widget_iface_init (GtkAppChooserIface *iface) { iface->get_app_info = gtk_app_chooser_widget_get_app_info; iface->refresh = gtk_app_chooser_widget_refresh; } /** * gtk_app_chooser_widget_new: * @content_type: the content type to show applications for * * Creates a new `GtkAppChooserWidget` for applications * that can handle content of the given type. * * Returns: a newly created `GtkAppChooserWidget` */ GtkWidget * gtk_app_chooser_widget_new (const char *content_type) { return g_object_new (GTK_TYPE_APP_CHOOSER_WIDGET, "content-type", content_type, NULL); } /** * gtk_app_chooser_widget_set_show_default: (attributes org.gtk.Method.set_property=show-default) * @self: a `GtkAppChooserWidget` * @setting: the new value for [property@Gtk.AppChooserWidget:show-default] * * Sets whether the app chooser should show the default handler * for the content type in a separate section. */ void gtk_app_chooser_widget_set_show_default (GtkAppChooserWidget *self, gboolean setting) { g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self)); if (self->show_default != setting) { self->show_default = setting; g_object_notify (G_OBJECT (self), "show-default"); gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } } /** * gtk_app_chooser_widget_get_show_default: (attributes org.gtk.Method.get_property=show-default) * @self: a `GtkAppChooserWidget` * * Gets whether the app chooser should show the default handler * for the content type in a separate section. * * Returns: the value of [property@Gtk.AppChooserWidget:show-default] */ gboolean gtk_app_chooser_widget_get_show_default (GtkAppChooserWidget *self) { g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE); return self->show_default; } /** * gtk_app_chooser_widget_set_show_recommended: (attributes org.gtk.Method.set_property=show-recommended) * @self: a `GtkAppChooserWidget` * @setting: the new value for [property@Gtk.AppChooserWidget:show-recommended] * * Sets whether the app chooser should show recommended applications * for the content type in a separate section. */ void gtk_app_chooser_widget_set_show_recommended (GtkAppChooserWidget *self, gboolean setting) { g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self)); if (self->show_recommended != setting) { self->show_recommended = setting; g_object_notify (G_OBJECT (self), "show-recommended"); gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } } /** * gtk_app_chooser_widget_get_show_recommended: (attributes org.gtk.Method.get_property=show-recommended) * @self: a `GtkAppChooserWidget` * * Gets whether the app chooser should show recommended applications * for the content type in a separate section. * * Returns: the value of [property@Gtk.AppChooserWidget:show-recommended] */ gboolean gtk_app_chooser_widget_get_show_recommended (GtkAppChooserWidget *self) { g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE); return self->show_recommended; } /** * gtk_app_chooser_widget_set_show_fallback: (attributes org.gtk.Method.set_property=show-fallback) * @self: a `GtkAppChooserWidget` * @setting: the new value for [property@Gtk.AppChooserWidget:show-fallback] * * Sets whether the app chooser should show related applications * for the content type in a separate section. */ void gtk_app_chooser_widget_set_show_fallback (GtkAppChooserWidget *self, gboolean setting) { g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self)); if (self->show_fallback != setting) { self->show_fallback = setting; g_object_notify (G_OBJECT (self), "show-fallback"); gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } } /** * gtk_app_chooser_widget_get_show_fallback: (attributes org.gtk.Method.get_property=show-fallback) * @self: a `GtkAppChooserWidget` * * Gets whether the app chooser should show related applications * for the content type in a separate section. * * Returns: the value of [property@Gtk.AppChooserWidget:show-fallback] */ gboolean gtk_app_chooser_widget_get_show_fallback (GtkAppChooserWidget *self) { g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE); return self->show_fallback; } /** * gtk_app_chooser_widget_set_show_other: (attributes org.gtk.Method.set_property=show-other) * @self: a `GtkAppChooserWidget` * @setting: the new value for [property@Gtk.AppChooserWidget:show-other] * * Sets whether the app chooser should show applications * which are unrelated to the content type. */ void gtk_app_chooser_widget_set_show_other (GtkAppChooserWidget *self, gboolean setting) { g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self)); if (self->show_other != setting) { self->show_other = setting; g_object_notify (G_OBJECT (self), "show-other"); gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } } /** * gtk_app_chooser_widget_get_show_other: (attributes org.gtk.Method.get_property=show-other) * @self: a `GtkAppChooserWidget` * * Gets whether the app chooser should show applications * which are unrelated to the content type. * * Returns: the value of [property@Gtk.AppChooserWidget:show-other] */ gboolean gtk_app_chooser_widget_get_show_other (GtkAppChooserWidget *self) { g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE); return self->show_other; } /** * gtk_app_chooser_widget_set_show_all: (attributes org.gtk.Method.set_property=show-all) * @self: a `GtkAppChooserWidget` * @setting: the new value for [property@Gtk.AppChooserWidget:show-all] * * Sets whether the app chooser should show all applications * in a flat list. */ void gtk_app_chooser_widget_set_show_all (GtkAppChooserWidget *self, gboolean setting) { g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self)); if (self->show_all != setting) { self->show_all = setting; g_object_notify (G_OBJECT (self), "show-all"); gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } } /** * gtk_app_chooser_widget_get_show_all: (attributes org.gtk.Method.get_property=show-all) * @self: a `GtkAppChooserWidget` * * Gets whether the app chooser should show all applications * in a flat list. * * Returns: the value of [property@Gtk.AppChooserWidget:show-all] */ gboolean gtk_app_chooser_widget_get_show_all (GtkAppChooserWidget *self) { g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE); return self->show_all; } /** * gtk_app_chooser_widget_set_default_text: (attributes org.gtk.Method.set_property=default-text) * @self: a `GtkAppChooserWidget` * @text: the new value for [property@Gtk.AppChooserWidget:default-text] * * Sets the text that is shown if there are not applications * that can handle the content type. */ void gtk_app_chooser_widget_set_default_text (GtkAppChooserWidget *self, const char *text) { g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self)); if (g_strcmp0 (text, self->default_text) != 0) { g_free (self->default_text); self->default_text = g_strdup (text); g_object_notify (G_OBJECT (self), "default-text"); gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } } /** * gtk_app_chooser_widget_get_default_text: (attributes org.gtk.Method.get_property=default-text) * @self: a `GtkAppChooserWidget` * * Returns the text that is shown if there are not applications * that can handle the content type. * * Returns: (nullable): the value of [property@Gtk.AppChooserWidget:default-text] */ const char * gtk_app_chooser_widget_get_default_text (GtkAppChooserWidget *self) { g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), NULL); return self->default_text; } void _gtk_app_chooser_widget_set_search_entry (GtkAppChooserWidget *self, GtkEditable *entry) { gtk_tree_view_set_search_entry (GTK_TREE_VIEW (self->program_list), entry); g_object_bind_property (self->no_apps, "visible", entry, "sensitive", G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); }