diff --git a/gtk/gtkappchooserwidget.c b/gtk/gtkappchooserwidget.c index 4c62b59272..20161cefde 100644 --- a/gtk/gtkappchooserwidget.c +++ b/gtk/gtkappchooserwidget.c @@ -31,10 +31,14 @@ G_GNUC_BEGIN_IGNORE_DEPRECATIONS #include "gtkmarshalers.h" #include "gtkappchooserwidget.h" #include "deprecated/gtkappchooserprivate.h" -#include "deprecated/gtkliststore.h" -#include "deprecated/gtktreeview.h" -#include "deprecated/gtktreeselection.h" -#include "deprecated/gtktreemodelsort.h" +#include "gtkeventcontrollerkey.h" +#include "gtkflattenlistmodel.h" +#include "gtklistheader.h" +#include "gtklistview.h" +#include "gtksignallistitemfactory.h" +#include "gtksingleselection.h" +#include "gtksortlistmodel.h" +#include "gtkstringsorter.h" #include "gtkorientable.h" #include "gtkscrolledwindow.h" #include "gtklabel.h" @@ -80,8 +84,6 @@ typedef struct _GtkAppChooserWidgetClass GtkAppChooserWidgetClass; struct _GtkAppChooserWidget { GtkWidget parent_instance; - GAppInfo *selected_app_info; - GtkWidget *overlay; char *content_type; @@ -93,14 +95,22 @@ struct _GtkAppChooserWidget { guint show_other : 1; guint show_all : 1; + GListModel *program_list_model; + GListModel *sorted_program_list_model; + GListStore *default_app; + GListStore *recommended_apps; + GListStore *related_apps; + GListStore *other_apps; + GtkSelectionModel *selection_model; + GtkWidget *program_list; - GtkListStore *program_list_store; + GtkListItemFactory *header_factory; GtkWidget *no_apps_label; GtkWidget *no_apps; - GtkTreeViewColumn *column; - GtkCellRenderer *padding_renderer; - GtkCellRenderer *secondary_padding; + GString *search_string; + guint search_timeout; + gboolean custom_search_entry; GAppInfoMonitor *monitor; @@ -117,21 +127,6 @@ struct _GtkAppChooserWidgetClass { 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, @@ -158,264 +153,63 @@ G_DEFINE_TYPE_WITH_CODE (GtkAppChooserWidget, gtk_app_chooser_widget, GTK_TYPE_W G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER, gtk_app_chooser_widget_iface_init)); -static void -refresh_and_emit_app_selected (GtkAppChooserWidget *self, - GtkTreeSelection *selection) +static char * +get_description (GtkListItem *list_item, + GAppInfo *app_info) { - GtkTreeModel *model; - GtkTreeIter iter; - GAppInfo *info = NULL; - gboolean should_emit = FALSE; + if (!app_info) + return NULL; - if (gtk_tree_selection_get_selected (selection, &model, &iter)) - gtk_tree_model_get (model, &iter, COLUMN_APP_INFO, &info, -1); + return g_markup_printf_escaped ("%s", + g_app_info_get_name (app_info) != NULL ? + g_app_info_get_name (app_info) : ""); +} - if (info == NULL) - return; +static char * +get_description_closure (GAppInfo *app_info) +{ + return get_description (NULL, app_info); +} - 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); - } - } +static GIcon * +get_icon (GtkListItem *list_item, + GAppInfo *app_info) +{ + GIcon *icon; + + if (!app_info) + return NULL; + + icon = g_app_info_get_icon (app_info); + if (icon == NULL) + return g_themed_icon_new ("application-x-executable"); 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; + return g_object_ref (icon); } static void -program_list_selection_activated (GtkTreeView *view, - GtkTreePath *path, - GtkTreeViewColumn *column, - gpointer user_data) +selection_changed (GtkSelectionModel *model, + guint position, + guint n_items, + GtkAppChooserWidget *self) +{ + g_signal_emit (self, signals[SIGNAL_APPLICATION_SELECTED], 0, + gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (model))); +} + +static void +program_list_selection_activated (GtkListView *view, + guint pos, + gpointer user_data) { GtkAppChooserWidget *self = user_data; - GtkTreeSelection *selection; + GAppInfo *selection; - if (path_is_heading (view, path)) - return; + selection = g_list_model_get_item (G_LIST_MODEL (self->selection_model), pos); - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->program_list)); + g_signal_emit (self, signals[SIGNAL_APPLICATION_ACTIVATED], 0, selection); - 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; + g_object_unref (selection); } static int @@ -425,26 +219,14 @@ compare_apps_func (gconstpointer a, return !g_app_info_equal (G_APP_INFO (a), G_APP_INFO (b)); } -static gboolean +static void gtk_app_chooser_widget_add_section (GtkAppChooserWidget *self, - const char *heading_title, - gboolean show_headings, - gboolean recommended, - gboolean fallback, + GListStore *store, 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) { @@ -459,103 +241,8 @@ gtk_app_chooser_widget_add_section (GtkAppChooserWidget *self, (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_list_store_append (store, app); } - - 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 App")); - - 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 @@ -582,41 +269,6 @@ update_no_applications_label (GtkAppChooserWidget *self) 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) { @@ -625,23 +277,17 @@ gtk_app_chooser_widget_real_add_items (GtkAppChooserWidget *self) 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; + g_list_store_append (self->default_app, default_app); exclude_apps = g_list_prepend (exclude_apps, default_app); } } @@ -652,11 +298,9 @@ gtk_app_chooser_widget_real_add_items (GtkAppChooserWidget *self) 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 Apps"), - show_headings, - !self->show_all, /* mark as recommended */ - FALSE, /* mark as fallback */ - recommended_apps, exclude_apps); + gtk_app_chooser_widget_add_section (self, + self->recommended_apps, + recommended_apps, exclude_apps); exclude_apps = g_list_concat (exclude_apps, g_list_copy (recommended_apps)); @@ -667,11 +311,9 @@ gtk_app_chooser_widget_real_add_items (GtkAppChooserWidget *self) 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 Apps"), - show_headings, - FALSE, /* mark as recommended */ - !self->show_all, /* mark as fallback */ - fallback_apps, exclude_apps); + gtk_app_chooser_widget_add_section (self, + self->related_apps, + fallback_apps, exclude_apps); exclude_apps = g_list_concat (exclude_apps, g_list_copy (fallback_apps)); } @@ -681,20 +323,22 @@ gtk_app_chooser_widget_real_add_items (GtkAppChooserWidget *self) { all_applications = g_app_info_get_all (); - apps_added |= gtk_app_chooser_widget_add_section (self, _("Other Apps"), - show_headings, - FALSE, - FALSE, - all_applications, exclude_apps); + gtk_app_chooser_widget_add_section (self, + self->other_apps, + all_applications, exclude_apps); } + apps_added = g_list_model_get_n_items (G_LIST_MODEL (self->selection_model)) > 0; + if (!apps_added) update_no_applications_label (self); + else + gtk_list_view_scroll_to (GTK_LIST_VIEW (self->program_list), 0, + GTK_LIST_SCROLL_SELECT | GTK_LIST_SCROLL_FOCUS, + NULL); 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); @@ -704,14 +348,98 @@ gtk_app_chooser_widget_real_add_items (GtkAppChooserWidget *self) g_list_free (exclude_apps); } +static void +setup_header_factory (GtkListItemFactory *factory, + GtkListHeader *list_header, + GtkAppChooserWidget *self) +{ + GtkWidget *label; + + label = gtk_label_new (""); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_list_header_set_child (list_header, label); +} + +static void +bind_header_factory (GtkListItemFactory *factory, + GtkListHeader *list_header, + GtkAppChooserWidget *self) +{ + GListModel *model; + GtkWidget *label = gtk_list_header_get_child (list_header); + guint pos = gtk_list_header_get_start (list_header); + + model = gtk_flatten_list_model_get_model_for_item (GTK_FLATTEN_LIST_MODEL (self->program_list_model), + pos); + if (GTK_IS_SORT_LIST_MODEL (model)) + model = gtk_sort_list_model_get_model (GTK_SORT_LIST_MODEL (model)); + + if (model == G_LIST_MODEL (self->default_app)) + gtk_label_set_label (GTK_LABEL (label), _("Default App")); + else if (model == G_LIST_MODEL (self->recommended_apps)) + gtk_label_set_label (GTK_LABEL (label), _("Recommended Apps")); + else if (model == G_LIST_MODEL (self->related_apps)) + gtk_label_set_label (GTK_LABEL (label), _("Related Apps")); + else if (model == G_LIST_MODEL (self->other_apps)) + gtk_label_set_label (GTK_LABEL (label), _("Other Apps")); +} + +static void +clear_search (gpointer user_data) +{ + GtkAppChooserWidget *self = user_data; + + g_string_erase (self->search_string, 0, -1); + self->search_timeout = 0; +} + +static gboolean +on_key_pressed (GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + GtkAppChooserWidget *self) +{ + guint32 character = gdk_keyval_to_unicode (keyval); + guint i; + gboolean match_found = FALSE; + + if (character == 0 || self->custom_search_entry) + return FALSE; + + g_string_append_c (self->search_string, character); + + g_clear_handle_id (&self->search_timeout, g_source_remove); + self->search_timeout = g_timeout_add_once (2000, clear_search, self); + + for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (self->selection_model)); i++) + { + GAppInfo *app_info = g_list_model_get_item (G_LIST_MODEL (self->selection_model), i); + const char *name = g_app_info_get_name (app_info); + const char *exec_name = g_app_info_get_executable (app_info); + + if ((name != NULL && g_str_match_string (self->search_string->str, name, TRUE)) || + (exec_name != NULL && g_str_match_string (self->search_string->str, exec_name, FALSE))) + { + match_found = TRUE; + g_object_unref (app_info); + break; + } + + g_object_unref (app_info); + } + + if (match_found) + gtk_list_view_scroll_to (GTK_LIST_VIEW (self->program_list), i, + GTK_LIST_SCROLL_SELECT | GTK_LIST_SCROLL_FOCUS, + NULL); + + return TRUE; +} + 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)); } @@ -817,6 +545,19 @@ gtk_app_chooser_widget_finalize (GObject *object) g_free (self->default_text); g_signal_handlers_disconnect_by_func (self->monitor, app_info_changed, self); g_object_unref (self->monitor); + g_clear_object (&self->header_factory); + + g_clear_handle_id (&self->search_timeout, g_source_remove); + g_string_free (self->search_string, TRUE); + self->search_string = NULL; + + g_clear_object (&self->program_list_model); + g_clear_object (&self->sorted_program_list_model); + g_clear_object (&self->selection_model); + g_clear_object (&self->default_app); + g_clear_object (&self->recommended_apps); + g_clear_object (&self->related_apps); + g_clear_object (&self->other_apps); G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->finalize (object); } @@ -826,8 +567,6 @@ 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); @@ -1019,14 +758,11 @@ gtk_app_chooser_widget_class_init (GtkAppChooserWidgetClass *klass) 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, get_description); + gtk_widget_class_bind_template_callback (widget_class, get_icon); gtk_widget_class_bind_template_callback (widget_class, program_list_selection_activated); gtk_widget_class_set_css_name (widget_class, I_("appchooser")); @@ -1035,36 +771,47 @@ gtk_app_chooser_widget_class_init (GtkAppChooserWidgetClass *klass) static void gtk_app_chooser_widget_init (GtkAppChooserWidget *self) { - GtkTreeSelection *selection; - GtkTreeModel *sort; + GListStore *store = g_list_store_new (G_TYPE_LIST_MODEL); + GtkExpression *sorter_expression; + GtkSortListModel *other_apps_sorted; + GtkSorter *sorter; + GtkEventController *controller; 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); + self->default_app = g_list_store_new (G_TYPE_APP_INFO); + self->recommended_apps = g_list_store_new (G_TYPE_APP_INFO); + self->related_apps = g_list_store_new (G_TYPE_APP_INFO); + self->other_apps = g_list_store_new (G_TYPE_APP_INFO); + g_list_store_append (store, self->default_app); + g_list_store_append (store, self->recommended_apps); + g_list_store_append (store, self->related_apps); - 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); + sorter_expression = gtk_cclosure_expression_new (G_TYPE_STRING, + NULL, 0, NULL, + G_CALLBACK (get_description_closure), + NULL, NULL); + sorter = GTK_SORTER (gtk_string_sorter_new (sorter_expression)); + other_apps_sorted = gtk_sort_list_model_new (G_LIST_MODEL (g_object_ref (self->other_apps)), + g_object_ref (sorter)); + g_list_store_append (store, other_apps_sorted); + g_object_unref (other_apps_sorted); - 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); + self->program_list_model = G_LIST_MODEL (gtk_flatten_list_model_new (G_LIST_MODEL (store))); + self->sorted_program_list_model = G_LIST_MODEL (gtk_sort_list_model_new (g_object_ref (self->program_list_model), sorter)); + self->selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (g_object_ref (self->program_list_model))); + gtk_list_view_set_model (GTK_LIST_VIEW (self->program_list), self->selection_model); + g_signal_connect (self->selection_model, "selection-changed", G_CALLBACK (selection_changed), self); - gtk_tree_view_column_set_cell_data_func (self->column, - self->secondary_padding, - padding_cell_renderer_func, - NULL, NULL); + self->header_factory = gtk_signal_list_item_factory_new (); + g_signal_connect (self->header_factory, "setup", G_CALLBACK (setup_header_factory), self); + g_signal_connect (self->header_factory, "bind", G_CALLBACK (bind_header_factory), self); + gtk_list_view_set_header_factory (GTK_LIST_VIEW (self->program_list), self->header_factory); + + self->search_string = g_string_new (""); + controller = gtk_event_controller_key_new (); + g_signal_connect (controller, "key-pressed", G_CALLBACK (on_key_pressed), self); + gtk_widget_add_controller (self->program_list, controller); self->monitor = g_app_info_monitor_get (); g_signal_connect (self->monitor, "changed", @@ -1075,11 +822,13 @@ static GAppInfo * gtk_app_chooser_widget_get_app_info (GtkAppChooser *object) { GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object); + GAppInfo *app_info; - if (self->selected_app_info == NULL) + app_info = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (self->selection_model)); + if (!app_info) return NULL; - return g_object_ref (self->selected_app_info); + return g_object_ref (app_info); } static void @@ -1087,17 +836,12 @@ 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); + g_list_store_remove_all (self->default_app); + g_list_store_remove_all (self->recommended_apps); + g_list_store_remove_all (self->related_apps); + g_list_store_remove_all (self->other_apps); - /* 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); - } + gtk_app_chooser_widget_real_add_items (self); } static void @@ -1304,11 +1048,18 @@ gtk_app_chooser_widget_set_show_all (GtkAppChooserWidget *self, if (self->show_all != setting) { + GtkListItemFactory *header_factory = setting ? NULL : self->header_factory; + GListModel *model = setting ? self->sorted_program_list_model : self->program_list_model; + self->show_all = setting; g_object_notify (G_OBJECT (self), "show-all"); gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); + + /* Don't show sections when show_all==TRUE. In which case all items should be sorted. */ + gtk_list_view_set_header_factory (GTK_LIST_VIEW (self->program_list), header_factory); + gtk_single_selection_set_model (GTK_SINGLE_SELECTION (self->selection_model), model); } } @@ -1375,7 +1126,7 @@ 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); + self->custom_search_entry = TRUE; g_object_bind_property (self->no_apps, "visible", entry, "sensitive", diff --git a/gtk/ui/gtkappchooserwidget.ui b/gtk/ui/gtkappchooserwidget.ui index dd88524e97..a048d9741e 100644 --- a/gtk/ui/gtkappchooserwidget.ui +++ b/gtk/ui/gtkappchooserwidget.ui @@ -1,22 +1,5 @@ - - - - - - - - - - - - - - - - program_list_store -