gtk2/gtk/gtkappchooserwidget.c
Florian Müllner 5ad9ecaff0 appchooserwidget: Don't limit application list unconditionally
As documented, GtkAppChooser is "typically [used] for the purpose of
opening a file". However given that applications that support neither
opening files nor URLs are filtered out, the chooser is not actual
useful for any other (atypical) usage. Change that by only applying
the filtering if a content-type was set, and use the full unfiltered
list otherwise.

https://bugzilla.gnome.org/show_bug.cgi?id=789327
2017-10-26 06:18:32 -04:00

1494 lines
45 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors: Dave Camp <dave@novell.com>
* Alexander Larsson <alexl@redhat.com>
* Cosimo Cecchi <ccecchi@redhat.com>
*/
#include "config.h"
#include "gtkappchooserwidget.h"
#include "gtkintl.h"
#include "gtkmarshalers.h"
#include "gtkappchooserwidget.h"
#include "gtkappchooserprivate.h"
#include "gtkliststore.h"
#include "gtkcellrenderertext.h"
#include "gtkcellrendererpixbuf.h"
#include "gtktreeview.h"
#include "gtktreeselection.h"
#include "gtktreemodelsort.h"
#include "gtkorientable.h"
#include "gtkscrolledwindow.h"
#include "gtklabel.h"
#include <string.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
/**
* SECTION:gtkappchooserwidget
* @Title: GtkAppChooserWidget
* @Short_description: Application chooser widget that can be embedded in other widgets
*
* #GtkAppChooserWidget is a widget for selecting applications.
* It is the main building block for #GtkAppChooserDialog. 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
* #GtkAppChooserWidget:show-default,
* #GtkAppChooserWidget:show-recommended,
* #GtkAppChooserWidget:show-fallback,
* #GtkAppChooserWidget:show-other and
* #GtkAppChooserWidget:show-all
* properties. See the #GtkAppChooser documentation for more information
* about these groups of applications.
*
* To keep track of the selected application, use the
* #GtkAppChooserWidget::application-selected and #GtkAppChooserWidget::application-activated signals.
*
* # CSS nodes
*
* GtkAppChooserWidget has a single CSS node with name appchooser.
*/
struct _GtkAppChooserWidgetPrivate {
GAppInfo *selected_app_info;
gchar *content_type;
gchar *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;
};
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,
SIGNAL_POPULATE_POPUP,
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_BOX,
G_ADD_PRIVATE (GtkAppChooserWidget)
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->priv->selected_app_info)
{
if (!g_app_info_equal (self->priv->selected_app_info, info))
{
should_emit = TRUE;
g_set_object (&self->priv->selected_app_info, info);
}
}
else
{
should_emit = TRUE;
g_set_object (&self->priv->selected_app_info, info);
}
g_object_unref (info);
if (should_emit)
g_signal_emit (self, signals[SIGNAL_APPLICATION_SELECTED], 0,
self->priv->selected_app_info);
}
static GAppInfo *
get_app_info_for_event (GtkAppChooserWidget *self,
GdkEventButton *event)
{
GtkTreePath *path = NULL;
GtkTreeIter iter;
GtkTreeModel *model;
GAppInfo *info;
gboolean recommended;
if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (self->priv->program_list),
event->x, event->y,
&path,
NULL, NULL, NULL))
return NULL;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->program_list));
if (!gtk_tree_model_get_iter (model, &iter, path))
{
gtk_tree_path_free (path);
return NULL;
}
/* we only allow interaction with recommended applications */
gtk_tree_model_get (model, &iter,
COLUMN_APP_INFO, &info,
COLUMN_RECOMMENDED, &recommended,
-1);
if (!recommended)
g_clear_object (&info);
return info;
}
static void
popup_menu_detach (GtkWidget *attach_widget,
GtkMenu *menu)
{
GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (attach_widget);
self->priv->popup_menu = NULL;
}
static gboolean
widget_button_press_event_cb (GtkWidget *widget,
GdkEventButton *event,
gpointer user_data)
{
GtkAppChooserWidget *self = user_data;
if (event->button == GDK_BUTTON_SECONDARY && event->type == GDK_BUTTON_PRESS)
{
GAppInfo *info;
GtkWidget *menu;
GList *children;
gint n_children;
info = get_app_info_for_event (self, event);
if (info == NULL)
return FALSE;
if (self->priv->popup_menu)
gtk_widget_destroy (self->priv->popup_menu);
self->priv->popup_menu = menu = gtk_menu_new ();
gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (self), popup_menu_detach);
g_signal_emit (self, signals[SIGNAL_POPULATE_POPUP], 0, menu, info);
g_object_unref (info);
/* see if clients added menu items to this container */
children = gtk_container_get_children (GTK_CONTAINER (menu));
n_children = g_list_length (children);
if (n_children > 0)
/* actually popup the menu */
gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *) event);
g_list_free (children);
}
return FALSE;
}
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->priv->program_list));
refresh_and_emit_app_selected (self, selection);
g_signal_emit (self, signals[SIGNAL_APPLICATION_ACTIVATED], 0,
self->priv->selected_app_info);
}
static gboolean
gtk_app_chooser_search_equal_func (GtkTreeModel *model,
gint column,
const gchar *key,
GtkTreeIter *iter,
gpointer user_data)
{
gchar *normalized_key;
gchar *name, *normalized_name;
gchar *path, *normalized_path;
gchar *basename, *normalized_basename;
gboolean ret;
if (key != NULL)
{
normalized_key = g_utf8_casefold (key, -1);
g_assert (normalized_key != NULL);
ret = TRUE;
gtk_tree_model_get (model, iter,
COLUMN_NAME, &name,
COLUMN_EXEC, &path,
-1);
if (name != NULL)
{
normalized_name = g_utf8_casefold (name, -1);
g_assert (normalized_name != NULL);
if (strncmp (normalized_name, normalized_key, strlen (normalized_key)) == 0)
ret = FALSE;
g_free (normalized_name);
}
if (ret && path != NULL)
{
normalized_path = g_utf8_casefold (path, -1);
g_assert (normalized_path != NULL);
basename = g_path_get_basename (path);
g_assert (basename != NULL);
normalized_basename = g_utf8_casefold (basename, -1);
g_assert (normalized_basename != NULL);
if (strncmp (normalized_path, normalized_key, strlen (normalized_key)) == 0 ||
strncmp (normalized_basename, normalized_key, strlen (normalized_key)) == 0)
ret = FALSE;
g_free (basename);
g_free (normalized_basename);
g_free (normalized_path);
}
g_free (name);
g_free (path);
g_free (normalized_key);
return ret;
}
else
{
return TRUE;
}
}
static gint
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;
gchar *a_name, *b_name, *a_casefold, *b_casefold;
gint 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 gint
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 gchar *heading_title,
gboolean show_headings,
gboolean recommended,
gboolean fallback,
GList *applications,
GList *exclude_apps)
{
gboolean heading_added, unref_icon;
GtkTreeIter iter;
GAppInfo *app;
gchar *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)
{
app = l->data;
if (self->priv->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->priv->program_list_store, &iter);
gtk_list_store_set (self->priv->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->priv->program_list_store, &iter);
gtk_list_store_set (self->priv->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;
gchar *string;
gboolean unref_icon;
unref_icon = FALSE;
string = g_strdup_printf ("<b>%s</b>", _("Default Application"));
gtk_list_store_append (self->priv->program_list_store, &iter);
gtk_list_store_set (self->priv->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->priv->program_list_store, &iter);
gtk_list_store_set (self->priv->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)
{
gchar *text = NULL, *desc = NULL;
const gchar *string;
if (self->priv->default_text == NULL)
{
if (self->priv->content_type)
desc = g_content_type_get_description (self->priv->content_type);
string = text = g_strdup_printf (_("No applications found for “%s”."), desc);
g_free (desc);
}
else
{
string = self->priv->default_text;
}
gtk_label_set_text (GTK_LABEL (self->priv->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->priv->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->priv->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->priv->show_all)
show_headings = FALSE;
if (self->priv->show_default && self->priv->content_type)
{
default_app = g_app_info_get_default_for_type (self->priv->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->priv->content_type && self->priv->show_recommended) || self->priv->show_all)
{
if (self->priv->content_type)
recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
apps_added |= gtk_app_chooser_widget_add_section (self, _("Recommended Applications"),
show_headings,
!self->priv->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->priv->content_type && self->priv->show_fallback) || self->priv->show_all)
{
if (self->priv->content_type)
fallback_apps = g_app_info_get_fallback_for_type (self->priv->content_type);
apps_added |= gtk_app_chooser_widget_add_section (self, _("Related Applications"),
show_headings,
FALSE, /* mark as recommended */
!self->priv->show_all, /* mark as fallback */
fallback_apps, exclude_apps);
exclude_apps = g_list_concat (exclude_apps,
g_list_copy (fallback_apps));
}
#endif
if (self->priv->show_other || self->priv->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->priv->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->priv->padding_renderer,
"xpad", self->priv->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->priv->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->priv->content_type);
break;
case PROP_SHOW_DEFAULT:
g_value_set_boolean (value, self->priv->show_default);
break;
case PROP_SHOW_RECOMMENDED:
g_value_set_boolean (value, self->priv->show_recommended);
break;
case PROP_SHOW_FALLBACK:
g_value_set_boolean (value, self->priv->show_fallback);
break;
case PROP_SHOW_OTHER:
g_value_set_boolean (value, self->priv->show_other);
break;
case PROP_SHOW_ALL:
g_value_set_boolean (value, self->priv->show_all);
break;
case PROP_DEFAULT_TEXT:
g_value_set_string (value, self->priv->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->priv->content_type);
g_free (self->priv->default_text);
g_signal_handlers_disconnect_by_func (self->priv->monitor, app_info_changed, self);
g_object_unref (self->priv->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->priv->selected_app_info);
G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->dispose (object);
}
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;
g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");
/**
* GtkAppChooserWidget:show-default:
*
* The ::show-default property 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",
P_("Show default app"),
P_("Whether the widget should show the default application"),
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:
*
* The #GtkAppChooserWidget:show-recommended property 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",
P_("Show recommended apps"),
P_("Whether the widget should show recommended applications"),
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:
*
* The #GtkAppChooserWidget:show-fallback property 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",
P_("Show fallback apps"),
P_("Whether the widget should show fallback applications"),
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:
*
* The #GtkAppChooserWidget:show-other property determines whether
* the app chooser should show a section for other applications.
*/
pspec = g_param_spec_boolean ("show-other",
P_("Show other apps"),
P_("Whether the widget should show other applications"),
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:
*
* If the #GtkAppChooserWidget:show-all property is %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",
P_("Show all apps"),
P_("Whether the widget should show all applications"),
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:
*
* The #GtkAppChooserWidget:default-text property determines the text
* that appears in the widget when there are no applications for the
* given content type.
* See also gtk_app_chooser_widget_set_default_text().
*/
pspec = g_param_spec_string ("default-text",
P_("Widget's default text"),
P_("The default text appearing when there are no applications"),
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);
/**
* GtkAppChooserWidget::populate-popup:
* @self: the object which received the signal
* @menu: the #GtkMenu to populate
* @application: the current #GAppInfo
*
* Emitted when a context menu is about to popup over an application item.
* Clients can insert menu items into the provided #GtkMenu object in the
* callback of this signal; the context menu will be shown over the item
* if at least one item has been added to the menu.
*/
signals[SIGNAL_POPULATE_POPUP] =
g_signal_new (I_("populate-popup"),
GTK_TYPE_APP_CHOOSER_WIDGET,
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GtkAppChooserWidgetClass, populate_popup),
NULL, NULL,
_gtk_marshal_VOID__OBJECT_OBJECT,
G_TYPE_NONE,
2, GTK_TYPE_MENU, G_TYPE_APP_INFO);
/* Bind class to template
*/
widget_class = GTK_WIDGET_CLASS (klass);
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gtk/libgtk/ui/gtkappchooserwidget.ui");
gtk_widget_class_bind_template_child_private (widget_class, GtkAppChooserWidget, program_list);
gtk_widget_class_bind_template_child_private (widget_class, GtkAppChooserWidget, program_list_store);
gtk_widget_class_bind_template_child_private (widget_class, GtkAppChooserWidget, column);
gtk_widget_class_bind_template_child_private (widget_class, GtkAppChooserWidget, padding_renderer);
gtk_widget_class_bind_template_child_private (widget_class, GtkAppChooserWidget, secondary_padding);
gtk_widget_class_bind_template_child_private (widget_class, GtkAppChooserWidget, no_apps_label);
gtk_widget_class_bind_template_child_private (widget_class, GtkAppChooserWidget, no_apps);
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_bind_template_callback (widget_class, widget_button_press_event_cb);
gtk_widget_class_set_css_name (widget_class, "appchooser");
}
static void
gtk_app_chooser_widget_init (GtkAppChooserWidget *self)
{
GtkTreeSelection *selection;
GtkTreeModel *sort;
self->priv = gtk_app_chooser_widget_get_instance_private (self);
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->priv->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->priv->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->priv->program_list), COLUMN_NAME);
gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (self->priv->program_list),
gtk_app_chooser_search_equal_func,
NULL, NULL);
gtk_tree_view_column_set_cell_data_func (self->priv->column,
self->priv->secondary_padding,
padding_cell_renderer_func,
NULL, NULL);
self->priv->monitor = g_app_info_monitor_get ();
g_signal_connect (self->priv->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->priv->selected_app_info == NULL)
return NULL;
return g_object_ref (self->priv->selected_app_info);
}
static void
gtk_app_chooser_widget_refresh (GtkAppChooser *object)
{
GtkAppChooserWidget *self = GTK_APP_CHOOSER_WIDGET (object);
if (self->priv->program_list_store != NULL)
{
gtk_list_store_clear (self->priv->program_list_store);
/* don't add additional xpad if we don't have headings */
g_object_set (self->priv->padding_renderer,
"visible", !self->priv->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
*
* Since: 3.0
*/
GtkWidget *
gtk_app_chooser_widget_new (const gchar *content_type)
{
return g_object_new (GTK_TYPE_APP_CHOOSER_WIDGET,
"content-type", content_type,
NULL);
}
/**
* gtk_app_chooser_widget_set_show_default:
* @self: a #GtkAppChooserWidget
* @setting: the new value for #GtkAppChooserWidget:show-default
*
* Sets whether the app chooser should show the default handler
* for the content type in a separate section.
*
* Since: 3.0
*/
void
gtk_app_chooser_widget_set_show_default (GtkAppChooserWidget *self,
gboolean setting)
{
g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
if (self->priv->show_default != setting)
{
self->priv->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:
* @self: a #GtkAppChooserWidget
*
* Returns the current value of the #GtkAppChooserWidget:show-default
* property.
*
* Returns: the value of #GtkAppChooserWidget:show-default
*
* Since: 3.0
*/
gboolean
gtk_app_chooser_widget_get_show_default (GtkAppChooserWidget *self)
{
g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
return self->priv->show_default;
}
/**
* gtk_app_chooser_widget_set_show_recommended:
* @self: a #GtkAppChooserWidget
* @setting: the new value for #GtkAppChooserWidget:show-recommended
*
* Sets whether the app chooser should show recommended applications
* for the content type in a separate section.
*
* Since: 3.0
*/
void
gtk_app_chooser_widget_set_show_recommended (GtkAppChooserWidget *self,
gboolean setting)
{
g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
if (self->priv->show_recommended != setting)
{
self->priv->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:
* @self: a #GtkAppChooserWidget
*
* Returns the current value of the #GtkAppChooserWidget:show-recommended
* property.
*
* Returns: the value of #GtkAppChooserWidget:show-recommended
*
* Since: 3.0
*/
gboolean
gtk_app_chooser_widget_get_show_recommended (GtkAppChooserWidget *self)
{
g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
return self->priv->show_recommended;
}
/**
* gtk_app_chooser_widget_set_show_fallback:
* @self: a #GtkAppChooserWidget
* @setting: the new value for #GtkAppChooserWidget:show-fallback
*
* Sets whether the app chooser should show related applications
* for the content type in a separate section.
*
* Since: 3.0
*/
void
gtk_app_chooser_widget_set_show_fallback (GtkAppChooserWidget *self,
gboolean setting)
{
g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
if (self->priv->show_fallback != setting)
{
self->priv->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:
* @self: a #GtkAppChooserWidget
*
* Returns the current value of the #GtkAppChooserWidget:show-fallback
* property.
*
* Returns: the value of #GtkAppChooserWidget:show-fallback
*
* Since: 3.0
*/
gboolean
gtk_app_chooser_widget_get_show_fallback (GtkAppChooserWidget *self)
{
g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
return self->priv->show_fallback;
}
/**
* gtk_app_chooser_widget_set_show_other:
* @self: a #GtkAppChooserWidget
* @setting: the new value for #GtkAppChooserWidget:show-other
*
* Sets whether the app chooser should show applications
* which are unrelated to the content type.
*
* Since: 3.0
*/
void
gtk_app_chooser_widget_set_show_other (GtkAppChooserWidget *self,
gboolean setting)
{
g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
if (self->priv->show_other != setting)
{
self->priv->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:
* @self: a #GtkAppChooserWidget
*
* Returns the current value of the #GtkAppChooserWidget:show-other
* property.
*
* Returns: the value of #GtkAppChooserWidget:show-other
*
* Since: 3.0
*/
gboolean
gtk_app_chooser_widget_get_show_other (GtkAppChooserWidget *self)
{
g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
return self->priv->show_other;
}
/**
* gtk_app_chooser_widget_set_show_all:
* @self: a #GtkAppChooserWidget
* @setting: the new value for #GtkAppChooserWidget:show-all
*
* Sets whether the app chooser should show all applications
* in a flat list.
*
* Since: 3.0
*/
void
gtk_app_chooser_widget_set_show_all (GtkAppChooserWidget *self,
gboolean setting)
{
g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
if (self->priv->show_all != setting)
{
self->priv->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:
* @self: a #GtkAppChooserWidget
*
* Returns the current value of the #GtkAppChooserWidget:show-all
* property.
*
* Returns: the value of #GtkAppChooserWidget:show-all
*
* Since: 3.0
*/
gboolean
gtk_app_chooser_widget_get_show_all (GtkAppChooserWidget *self)
{
g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), FALSE);
return self->priv->show_all;
}
/**
* gtk_app_chooser_widget_set_default_text:
* @self: a #GtkAppChooserWidget
* @text: the new value for #GtkAppChooserWidget: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 gchar *text)
{
g_return_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self));
if (g_strcmp0 (text, self->priv->default_text) != 0)
{
g_free (self->priv->default_text);
self->priv->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:
* @self: a #GtkAppChooserWidget
*
* Returns the text that is shown if there are not applications
* that can handle the content type.
*
* Returns: the value of #GtkAppChooserWidget:default-text
*
* Since: 3.0
*/
const gchar *
gtk_app_chooser_widget_get_default_text (GtkAppChooserWidget *self)
{
g_return_val_if_fail (GTK_IS_APP_CHOOSER_WIDGET (self), NULL);
return self->priv->default_text;
}
void
_gtk_app_chooser_widget_set_search_entry (GtkAppChooserWidget *self,
GtkEntry *entry)
{
gtk_tree_view_set_search_entry (GTK_TREE_VIEW (self->priv->program_list), entry);
g_object_bind_property (self->priv->no_apps, "visible",
entry, "sensitive",
G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
}