gtkappchooserwidget: Port to GtkListView
This drops the GtkTreeView dependency and the original reason for deprecation. Attempted to keep the behavior consistent with the original implementation: Sorting - When show_all is FALSE only the Other category is alphabetically sorted, the other categories keep app info sorting. When show_all is TRUE, the list is sorted. One difference is that in the original implementation, the default app was left as the first item. For simplicity (since this was not documented anywhere) the default app will also be alphabetically sorted, but still selected by default. Searching - Type ahead search is maintaining. It is now "invisible" (no popover). Icons - Icons are smaller than in TreeView Selectability - Now enforces that an item is always selected. Under certain circumstances the TreeView version would have no item selected, arguably a bug All the benefits of list views, proper headers (not fake rows), etc
This commit is contained in:
@@ -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 ("<b>%s</b>", 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 ("<b>%s</b>", _("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",
|
||||
|
||||
@@ -1,22 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface domain="gtk40">
|
||||
<object class="GtkListStore" id="program_list_store">
|
||||
<columns>
|
||||
<column type="GAppInfo"/>
|
||||
<column type="GIcon"/>
|
||||
<column type="gchararray"/>
|
||||
<column type="gchararray"/>
|
||||
<column type="gchararray"/>
|
||||
<column type="gboolean"/>
|
||||
<column type="gboolean"/>
|
||||
<column type="gchararray"/>
|
||||
<column type="gboolean"/>
|
||||
<column type="gboolean"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkTreeModelSort" id="program_list_sort">
|
||||
<property name="model">program_list_store</property>
|
||||
</object>
|
||||
<template class="GtkAppChooserWidget" parent="GtkWidget">
|
||||
<child>
|
||||
<object class="GtkOverlay" id="overlay">
|
||||
@@ -27,51 +10,42 @@
|
||||
<property name="hscrollbar-policy">2</property>
|
||||
<property name="has-frame">1</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="program_list">
|
||||
<property name="model">program_list_sort</property>
|
||||
<property name="headers-visible">0</property>
|
||||
<signal name="row-activated" handler="program_list_selection_activated" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection" id="treeview-selection2">
|
||||
<property name="mode">2</property>
|
||||
<signal name="changed" handler="refresh_and_emit_app_selected" object="GtkAppChooserWidget" swapped="yes"/>
|
||||
<object class="GtkListView" id="program_list">
|
||||
<property name="factory">
|
||||
<object class="GtkBuilderListItemFactory">
|
||||
<property name="bytes"><![CDATA[
|
||||
<interface>
|
||||
<template class="GtkListItem">
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<binding name="gicon">
|
||||
<closure type="GIcon" function="get_icon">
|
||||
<lookup name="item">GtkListItem</lookup>
|
||||
</closure>
|
||||
</binding>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="xalign">0</property>
|
||||
<binding name="label">
|
||||
<closure type="gchararray" function="get_description">
|
||||
<lookup name="item">GtkListItem</lookup>
|
||||
</closure>
|
||||
</binding>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
||||
]]></property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="column">
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="padding_renderer"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="heading">
|
||||
<property name="ypad">6</property>
|
||||
<property name="wrap-mode">0</property>
|
||||
<property name="wrap-width">350</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="visible">6</attribute>
|
||||
<attribute name="markup">7</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="secondary_padding"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererPixbuf" id="app_icon"/>
|
||||
<attributes>
|
||||
<attribute name="gicon">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="app_name">
|
||||
<property name="ellipsize">3</property>
|
||||
</object>
|
||||
<attributes>
|
||||
<attribute name="markup">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</property>
|
||||
<signal name="activate" handler="program_list_selection_activated" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
Reference in New Issue
Block a user