992 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			992 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * gtkappchooserbutton.h: an app-chooser combobox
 | |
|  *
 | |
|  * Copyright (C) 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: Cosimo Cecchi <ccecchi@redhat.com>
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * SECTION:gtkappchooserbutton
 | |
|  * @Title: GtkAppChooserButton
 | |
|  * @Short_description: A button to launch an application chooser dialog
 | |
|  *
 | |
|  * The #GtkAppChooserButton is a widget that lets the user select
 | |
|  * an application. It implements the #GtkAppChooser interface.
 | |
|  *
 | |
|  * Initially, a #GtkAppChooserButton selects the first application
 | |
|  * in its list, which will either be the most-recently used application
 | |
|  * or, if #GtkAppChooserButton:show-default-item is %TRUE, the
 | |
|  * default application.
 | |
|  *
 | |
|  * The list of applications shown in a #GtkAppChooserButton includes
 | |
|  * the recommended applications for the given content type. When
 | |
|  * #GtkAppChooserButton:show-default-item is set, the default application
 | |
|  * is also included. To let the user chooser other applications,
 | |
|  * you can set the #GtkAppChooserButton:show-dialog-item property,
 | |
|  * which allows to open a full #GtkAppChooserDialog.
 | |
|  *
 | |
|  * It is possible to add custom items to the list, using
 | |
|  * gtk_app_chooser_button_append_custom_item(). These items cause
 | |
|  * the #GtkAppChooserButton::custom-item-activated signal to be
 | |
|  * emitted when they are selected.
 | |
|  *
 | |
|  * To track changes in the selected application, use the
 | |
|  * #GtkComboBox::changed signal.
 | |
|  */
 | |
| #include "config.h"
 | |
| 
 | |
| #include "gtkappchooserbutton.h"
 | |
| 
 | |
| #include "gtkappchooser.h"
 | |
| #include "gtkappchooserdialog.h"
 | |
| #include "gtkappchooserprivate.h"
 | |
| #include "gtkcelllayout.h"
 | |
| #include "gtkcellrendererpixbuf.h"
 | |
| #include "gtkcellrenderertext.h"
 | |
| #include "gtkcombobox.h"
 | |
| #include "gtkdialog.h"
 | |
| #include "gtkintl.h"
 | |
| #include "gtkmarshalers.h"
 | |
| 
 | |
| enum {
 | |
|   PROP_CONTENT_TYPE = 1,
 | |
|   PROP_SHOW_DIALOG_ITEM,
 | |
|   PROP_SHOW_DEFAULT_ITEM,
 | |
|   PROP_HEADING
 | |
| };
 | |
| 
 | |
| enum {
 | |
|   SIGNAL_CUSTOM_ITEM_ACTIVATED,
 | |
|   NUM_SIGNALS
 | |
| };
 | |
| 
 | |
| enum {
 | |
|   COLUMN_APP_INFO,
 | |
|   COLUMN_NAME,
 | |
|   COLUMN_LABEL,
 | |
|   COLUMN_ICON,
 | |
|   COLUMN_CUSTOM,
 | |
|   COLUMN_SEPARATOR,
 | |
|   NUM_COLUMNS,
 | |
| };
 | |
| 
 | |
| #define CUSTOM_ITEM_OTHER_APP "gtk-internal-item-other-app"
 | |
| 
 | |
| static void app_chooser_iface_init (GtkAppChooserIface *iface);
 | |
| 
 | |
| static void real_insert_custom_item (GtkAppChooserButton *self,
 | |
|                                      const gchar *name,
 | |
|                                      const gchar *label,
 | |
|                                      GIcon *icon,
 | |
|                                      gboolean custom,
 | |
|                                      GtkTreeIter *iter);
 | |
| 
 | |
| static void real_insert_separator (GtkAppChooserButton *self,
 | |
|                                    gboolean custom,
 | |
|                                    GtkTreeIter *iter);
 | |
| 
 | |
| static guint signals[NUM_SIGNALS] = { 0, };
 | |
| 
 | |
| G_DEFINE_TYPE_WITH_CODE (GtkAppChooserButton, gtk_app_chooser_button, GTK_TYPE_COMBO_BOX,
 | |
|                          G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER,
 | |
|                                                 app_chooser_iface_init));
 | |
| 
 | |
| struct _GtkAppChooserButtonPrivate {
 | |
|   GtkListStore *store;
 | |
| 
 | |
|   gchar *content_type;
 | |
|   gchar *heading;
 | |
|   gint last_active;
 | |
|   gboolean show_dialog_item;
 | |
|   gboolean show_default_item;
 | |
| 
 | |
|   GHashTable *custom_item_names;
 | |
| };
 | |
| 
 | |
| static gboolean
 | |
| row_separator_func (GtkTreeModel *model,
 | |
|                     GtkTreeIter  *iter,
 | |
|                     gpointer      user_data)
 | |
| {
 | |
|   gboolean separator;
 | |
| 
 | |
|   gtk_tree_model_get (model, iter,
 | |
|                       COLUMN_SEPARATOR, &separator,
 | |
|                       -1);
 | |
| 
 | |
|   return separator;
 | |
| }
 | |
| 
 | |
| static void
 | |
| get_first_iter (GtkListStore *store,
 | |
|                 GtkTreeIter  *iter)
 | |
| {
 | |
|   GtkTreeIter iter2;
 | |
| 
 | |
|   if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), iter))
 | |
|     {
 | |
|       /* the model is empty, append */
 | |
|       gtk_list_store_append (store, iter);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       gtk_list_store_insert_before (store, &iter2, iter);
 | |
|       *iter = iter2;
 | |
|     }
 | |
| }
 | |
| 
 | |
| typedef struct {
 | |
|   GtkAppChooserButton *self;
 | |
|   GAppInfo *info;
 | |
|   gint active_index;
 | |
| } SelectAppData;
 | |
| 
 | |
| static void
 | |
| select_app_data_free (SelectAppData *data)
 | |
| {
 | |
|   g_clear_object (&data->self);
 | |
|   g_clear_object (&data->info);
 | |
| 
 | |
|   g_slice_free (SelectAppData, data);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| select_application_func_cb (GtkTreeModel *model,
 | |
|                             GtkTreePath *path,
 | |
|                             GtkTreeIter *iter,
 | |
|                             gpointer user_data)
 | |
| {
 | |
|   SelectAppData *data = user_data;
 | |
|   GAppInfo *app_to_match = data->info, *app = NULL;
 | |
|   gboolean custom;
 | |
|   gboolean result;
 | |
| 
 | |
|   gtk_tree_model_get (model, iter,
 | |
|                       COLUMN_APP_INFO, &app,
 | |
|                       COLUMN_CUSTOM, &custom,
 | |
|                       -1);
 | |
| 
 | |
|   /* custom items are always after GAppInfos, so iterating further here
 | |
|    * is just useless.
 | |
|    */
 | |
|   if (custom)
 | |
|     result = TRUE;
 | |
|   else if (g_app_info_equal (app, app_to_match))
 | |
|     {
 | |
|       gtk_combo_box_set_active_iter (GTK_COMBO_BOX (data->self), iter);
 | |
|       result = TRUE;
 | |
|     }
 | |
|   else
 | |
|     result = FALSE;
 | |
| 
 | |
|   g_object_unref (app);
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_select_application (GtkAppChooserButton *self,
 | |
|                                            GAppInfo *info)
 | |
| {
 | |
|   SelectAppData *data;
 | |
| 
 | |
|   data = g_slice_new0 (SelectAppData);
 | |
|   data->self = g_object_ref (self);
 | |
|   data->info = g_object_ref (info);
 | |
| 
 | |
|   gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->store),
 | |
|                           select_application_func_cb, data);
 | |
| 
 | |
|   select_app_data_free (data);
 | |
| }
 | |
| 
 | |
| static void
 | |
| other_application_dialog_response_cb (GtkDialog *dialog,
 | |
|                                       gint response_id,
 | |
|                                       gpointer user_data)
 | |
| {
 | |
|   GtkAppChooserButton *self = user_data;
 | |
|   GAppInfo *info;
 | |
| 
 | |
|   if (response_id != GTK_RESPONSE_OK)
 | |
|     {
 | |
|       /* reset the active item, otherwise we are stuck on
 | |
|        * 'Other application...'
 | |
|        */
 | |
|       gtk_combo_box_set_active (GTK_COMBO_BOX (self), self->priv->last_active);
 | |
|       gtk_widget_destroy (GTK_WIDGET (dialog));
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog));
 | |
| 
 | |
|   gtk_widget_destroy (GTK_WIDGET (dialog));
 | |
| 
 | |
|   /* refresh the combobox to get the new application */
 | |
|   gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
 | |
|   gtk_app_chooser_button_select_application (self, info);
 | |
| 
 | |
|   g_object_unref (info);
 | |
| }
 | |
| 
 | |
| static void
 | |
| other_application_item_activated_cb (GtkAppChooserButton *self)
 | |
| {
 | |
|   GtkWidget *dialog, *widget;
 | |
|   GtkWindow *toplevel;
 | |
| 
 | |
|   toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)));
 | |
|   dialog = gtk_app_chooser_dialog_new_for_content_type (toplevel, GTK_DIALOG_DESTROY_WITH_PARENT,
 | |
|                                                         self->priv->content_type);
 | |
| 
 | |
|   gtk_window_set_modal (GTK_WINDOW (dialog), gtk_window_get_modal (toplevel));
 | |
|   gtk_app_chooser_dialog_set_heading (GTK_APP_CHOOSER_DIALOG (dialog),
 | |
|                                       self->priv->heading);
 | |
| 
 | |
|   widget = gtk_app_chooser_dialog_get_widget (GTK_APP_CHOOSER_DIALOG (dialog));
 | |
|   g_object_set (widget,
 | |
|                 "show-fallback", TRUE,
 | |
|                 "show-other", TRUE,
 | |
|                 NULL);
 | |
|   gtk_widget_show (dialog);
 | |
| 
 | |
|   g_signal_connect (dialog, "response",
 | |
|                     G_CALLBACK (other_application_dialog_response_cb), self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_ensure_dialog_item (GtkAppChooserButton *self,
 | |
|                                            GtkTreeIter *prev_iter)
 | |
| {
 | |
|   GtkTreeIter iter, iter2;
 | |
| 
 | |
|   if (!self->priv->show_dialog_item || !self->priv->content_type)
 | |
|     return;
 | |
| 
 | |
|   if (prev_iter == NULL)
 | |
|     gtk_list_store_append (self->priv->store, &iter);
 | |
|   else
 | |
|     gtk_list_store_insert_after (self->priv->store, &iter, prev_iter);
 | |
| 
 | |
|   real_insert_separator (self, FALSE, &iter);
 | |
|   iter2 = iter;
 | |
| 
 | |
|   gtk_list_store_insert_after (self->priv->store, &iter, &iter2);
 | |
|   real_insert_custom_item (self, CUSTOM_ITEM_OTHER_APP,
 | |
|                            _("Other application..."), NULL,
 | |
|                            FALSE, &iter);
 | |
| }
 | |
| 
 | |
| static void
 | |
| insert_one_application (GtkAppChooserButton *self,
 | |
|                         GAppInfo            *app,
 | |
|                         GtkTreeIter         *iter)
 | |
| {
 | |
|   GIcon *icon;
 | |
| 
 | |
|   icon = g_app_info_get_icon (app);
 | |
| 
 | |
|   if (icon == NULL)
 | |
|     icon = g_themed_icon_new ("application-x-executable");
 | |
|   else
 | |
|     g_object_ref (icon);
 | |
| 
 | |
|   gtk_list_store_set (self->priv->store, iter,
 | |
|                       COLUMN_APP_INFO, app,
 | |
|                       COLUMN_LABEL, g_app_info_get_name (app),
 | |
|                       COLUMN_ICON, icon,
 | |
|                       COLUMN_CUSTOM, FALSE,
 | |
|                       -1);
 | |
| 
 | |
|   g_object_unref (icon);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_populate (GtkAppChooserButton *self)
 | |
| {
 | |
|   GList *recommended_apps = NULL, *l;
 | |
|   GAppInfo *app, *default_app = NULL;
 | |
|   GtkTreeIter iter, iter2;
 | |
|   gboolean cycled_recommended;
 | |
| 
 | |
| #ifndef G_OS_WIN32
 | |
|   if (self->priv->content_type)
 | |
|     recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
 | |
| #endif
 | |
|   cycled_recommended = FALSE;
 | |
| 
 | |
|   if (self->priv->show_default_item)
 | |
|     {
 | |
|       default_app = g_app_info_get_default_for_type (self->priv->content_type, FALSE);
 | |
| 
 | |
|       if (default_app != NULL)
 | |
|         {
 | |
|           get_first_iter (self->priv->store, &iter);
 | |
|           cycled_recommended = TRUE;
 | |
| 
 | |
|           insert_one_application (self, default_app, &iter);
 | |
| 
 | |
|           g_object_unref (default_app);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   for (l = recommended_apps; l != NULL; l = l->next)
 | |
|     {
 | |
|       app = l->data;
 | |
| 
 | |
|       if (default_app != NULL && g_app_info_equal (app, default_app))
 | |
|         continue;
 | |
| 
 | |
|       if (cycled_recommended)
 | |
|         {
 | |
|           gtk_list_store_insert_after (self->priv->store, &iter2, &iter);
 | |
|           iter = iter2;
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           get_first_iter (self->priv->store, &iter);
 | |
|           cycled_recommended = TRUE;
 | |
|         }
 | |
| 
 | |
|       insert_one_application (self, app, &iter);
 | |
|     }
 | |
| 
 | |
|   if (recommended_apps != NULL)
 | |
|     g_list_free_full (recommended_apps, g_object_unref);
 | |
| 
 | |
|   if (!cycled_recommended)
 | |
|     gtk_app_chooser_button_ensure_dialog_item (self, NULL);
 | |
|   else
 | |
|     gtk_app_chooser_button_ensure_dialog_item (self, &iter);
 | |
| 
 | |
|   gtk_combo_box_set_active (GTK_COMBO_BOX (self), 0);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_build_ui (GtkAppChooserButton *self)
 | |
| {
 | |
|   GtkCellRenderer *cell;
 | |
|   GtkCellArea *area;
 | |
| 
 | |
|   gtk_combo_box_set_model (GTK_COMBO_BOX (self),
 | |
|                            GTK_TREE_MODEL (self->priv->store));
 | |
| 
 | |
|   area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (self));
 | |
| 
 | |
|   gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (self),
 | |
|                                         row_separator_func, NULL, NULL);
 | |
| 
 | |
|   cell = gtk_cell_renderer_pixbuf_new ();
 | |
|   gtk_cell_area_add_with_properties (area, cell,
 | |
|                                      "align", FALSE,
 | |
|                                      "expand", FALSE,
 | |
|                                      "fixed-size", FALSE,
 | |
|                                      NULL);
 | |
|   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self), cell,
 | |
|                                   "gicon", COLUMN_ICON,
 | |
|                                   NULL);
 | |
| 
 | |
|   cell = gtk_cell_renderer_text_new ();
 | |
|   gtk_cell_area_add_with_properties (area, cell,
 | |
|                                      "align", FALSE,
 | |
|                                      "expand", TRUE,
 | |
|                                      NULL);
 | |
|   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self), cell,
 | |
|                                   "text", COLUMN_LABEL,
 | |
|                                   NULL);
 | |
| 
 | |
|   gtk_app_chooser_button_populate (self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_remove_non_custom (GtkAppChooserButton *self)
 | |
| {
 | |
|   GtkTreeModel *model;
 | |
|   GtkTreeIter iter;
 | |
|   gboolean custom, res;
 | |
| 
 | |
|   model = GTK_TREE_MODEL (self->priv->store);
 | |
| 
 | |
|   if (!gtk_tree_model_get_iter_first (model, &iter))
 | |
|     return;
 | |
| 
 | |
|   do {
 | |
|     gtk_tree_model_get (model, &iter,
 | |
|                         COLUMN_CUSTOM, &custom,
 | |
|                         -1);
 | |
|     if (custom)
 | |
|       res = gtk_tree_model_iter_next (model, &iter);
 | |
|     else
 | |
|       res = gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
 | |
|   } while (res);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_changed (GtkComboBox *object)
 | |
| {
 | |
|   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
 | |
|   GtkTreeIter iter;
 | |
|   gchar *name = NULL;
 | |
|   gboolean custom;
 | |
|   GQuark name_quark;
 | |
| 
 | |
|   if (!gtk_combo_box_get_active_iter (object, &iter))
 | |
|     return;
 | |
| 
 | |
|   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter,
 | |
|                       COLUMN_NAME, &name,
 | |
|                       COLUMN_CUSTOM, &custom,
 | |
|                       -1);
 | |
| 
 | |
|   if (name != NULL)
 | |
|     {
 | |
|       if (custom)
 | |
|         {
 | |
|           name_quark = g_quark_from_string (name);
 | |
|           g_signal_emit (self, signals[SIGNAL_CUSTOM_ITEM_ACTIVATED], name_quark, name);
 | |
|           self->priv->last_active = gtk_combo_box_get_active (object);
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           /* trigger the dialog internally */
 | |
|           other_application_item_activated_cb (self);
 | |
|         }
 | |
| 
 | |
|       g_free (name);
 | |
|     }
 | |
|   else
 | |
|     self->priv->last_active = gtk_combo_box_get_active (object);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_refresh (GtkAppChooser *object)
 | |
| {
 | |
|   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
 | |
| 
 | |
|   gtk_app_chooser_button_remove_non_custom (self);
 | |
|   gtk_app_chooser_button_populate (self);
 | |
| }
 | |
| 
 | |
| static GAppInfo *
 | |
| gtk_app_chooser_button_get_app_info (GtkAppChooser *object)
 | |
| {
 | |
|   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
 | |
|   GtkTreeIter iter;
 | |
|   GAppInfo *info;
 | |
| 
 | |
|   if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX (self), &iter))
 | |
|     return NULL;
 | |
| 
 | |
|   gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter,
 | |
|                       COLUMN_APP_INFO, &info,
 | |
|                       -1);
 | |
| 
 | |
|   return info;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_constructed (GObject *obj)
 | |
| {
 | |
|   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
 | |
| 
 | |
|   if (G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed != NULL)
 | |
|     G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed (obj);
 | |
| 
 | |
|   gtk_app_chooser_button_build_ui (self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_set_property (GObject      *obj,
 | |
|                                      guint         property_id,
 | |
|                                      const GValue *value,
 | |
|                                      GParamSpec   *pspec)
 | |
| {
 | |
|   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
 | |
| 
 | |
|   switch (property_id)
 | |
|     {
 | |
|     case PROP_CONTENT_TYPE:
 | |
|       self->priv->content_type = g_value_dup_string (value);
 | |
|       break;
 | |
|     case PROP_SHOW_DIALOG_ITEM:
 | |
|       gtk_app_chooser_button_set_show_dialog_item (self, g_value_get_boolean (value));
 | |
|       break;
 | |
|     case PROP_SHOW_DEFAULT_ITEM:
 | |
|       gtk_app_chooser_button_set_show_default_item (self, g_value_get_boolean (value));
 | |
|       break;
 | |
|     case PROP_HEADING:
 | |
|       gtk_app_chooser_button_set_heading (self, g_value_get_string (value));
 | |
|       break;
 | |
|     default:
 | |
|       G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_get_property (GObject    *obj,
 | |
|                                      guint       property_id,
 | |
|                                      GValue     *value,
 | |
|                                      GParamSpec *pspec)
 | |
| {
 | |
|   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
 | |
| 
 | |
|   switch (property_id)
 | |
|     {
 | |
|     case PROP_CONTENT_TYPE:
 | |
|       g_value_set_string (value, self->priv->content_type);
 | |
|       break;
 | |
|     case PROP_SHOW_DIALOG_ITEM:
 | |
|       g_value_set_boolean (value, self->priv->show_dialog_item);
 | |
|       break;
 | |
|     case PROP_SHOW_DEFAULT_ITEM:
 | |
|       g_value_set_boolean (value, self->priv->show_default_item);
 | |
|       break;
 | |
|     case PROP_HEADING:
 | |
|       g_value_set_string (value, self->priv->heading);
 | |
|       break;
 | |
|     default:
 | |
|       G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_finalize (GObject *obj)
 | |
| {
 | |
|   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (obj);
 | |
| 
 | |
|   g_hash_table_destroy (self->priv->custom_item_names);
 | |
|   g_free (self->priv->content_type);
 | |
|   g_free (self->priv->heading);
 | |
| 
 | |
|   g_object_unref (self->priv->store);
 | |
| 
 | |
|   G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->finalize (obj);
 | |
| }
 | |
| 
 | |
| static void
 | |
| app_chooser_iface_init (GtkAppChooserIface *iface)
 | |
| {
 | |
|   iface->get_app_info = gtk_app_chooser_button_get_app_info;
 | |
|   iface->refresh = gtk_app_chooser_button_refresh;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_class_init (GtkAppChooserButtonClass *klass)
 | |
| {
 | |
|   GObjectClass *oclass = G_OBJECT_CLASS (klass);
 | |
|   GtkComboBoxClass *combo_class = GTK_COMBO_BOX_CLASS (klass);
 | |
|   GParamSpec *pspec;
 | |
| 
 | |
|   oclass->set_property = gtk_app_chooser_button_set_property;
 | |
|   oclass->get_property = gtk_app_chooser_button_get_property;
 | |
|   oclass->finalize = gtk_app_chooser_button_finalize;
 | |
|   oclass->constructed = gtk_app_chooser_button_constructed;
 | |
| 
 | |
|   combo_class->changed = gtk_app_chooser_button_changed;
 | |
| 
 | |
|   g_object_class_override_property (oclass, PROP_CONTENT_TYPE, "content-type");
 | |
| 
 | |
|   /**
 | |
|    * GtkAppChooserButton:show-dialog-item:
 | |
|    *
 | |
|    * The #GtkAppChooserButton:show-dialog-item property determines
 | |
|    * whether the dropdown menu should show an item that triggers
 | |
|    * a #GtkAppChooserDialog when clicked.
 | |
|    */
 | |
|   pspec =
 | |
|     g_param_spec_boolean ("show-dialog-item",
 | |
|                           P_("Include an 'Other...' item"),
 | |
|                           P_("Whether the combobox should include an item that triggers a GtkAppChooserDialog"),
 | |
|                           FALSE,
 | |
|                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 | |
|   g_object_class_install_property (oclass, PROP_SHOW_DIALOG_ITEM, pspec);
 | |
| 
 | |
|   /**
 | |
|    * GtkAppChooserButton:show-default-item:
 | |
|    *
 | |
|    * The #GtkAppChooserButton:show-default-item property determines
 | |
|    * whether the dropdown menu should show the default application
 | |
|    * on top for the provided content type.
 | |
|    *
 | |
|    * Since: 3.2
 | |
|    */
 | |
|   pspec =
 | |
|     g_param_spec_boolean ("show-default-item",
 | |
|                           P_("Show default item"),
 | |
|                           P_("Whether the combobox should show the default application on top"),
 | |
|                           FALSE,
 | |
|                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
 | |
|   g_object_class_install_property (oclass, PROP_SHOW_DEFAULT_ITEM, pspec);
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * GtkAppChooserButton:heading:
 | |
|    *
 | |
|    * The text to show at the top of the dialog that can be
 | |
|    * opened from the button. The string may contain Pango markup.
 | |
|    */
 | |
|   pspec = g_param_spec_string ("heading",
 | |
|                                P_("Heading"),
 | |
|                                P_("The text to show at the top of the dialog"),
 | |
|                                NULL,
 | |
|                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 | |
|   g_object_class_install_property (oclass, PROP_HEADING, pspec);
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * GtkAppChooserButton::custom-item-activated:
 | |
|    * @self: the object which received the signal
 | |
|    * @item_name: the name of the activated item
 | |
|    *
 | |
|    * Emitted when a custom item, previously added with
 | |
|    * gtk_app_chooser_button_append_custom_item(), is activated from the
 | |
|    * dropdown menu.
 | |
|    */
 | |
|   signals[SIGNAL_CUSTOM_ITEM_ACTIVATED] =
 | |
|     g_signal_new ("custom-item-activated",
 | |
|                   GTK_TYPE_APP_CHOOSER_BUTTON,
 | |
|                   G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
 | |
|                   G_STRUCT_OFFSET (GtkAppChooserButtonClass, custom_item_activated),
 | |
|                   NULL, NULL,
 | |
|                   _gtk_marshal_VOID__STRING,
 | |
|                   G_TYPE_NONE,
 | |
|                   1, G_TYPE_STRING);
 | |
| 
 | |
|   g_type_class_add_private (klass, sizeof (GtkAppChooserButtonPrivate));
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_app_chooser_button_init (GtkAppChooserButton *self)
 | |
| {
 | |
|   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_APP_CHOOSER_BUTTON,
 | |
|                                             GtkAppChooserButtonPrivate);
 | |
|   self->priv->custom_item_names =
 | |
|     g_hash_table_new_full (g_str_hash, g_str_equal,
 | |
|                            g_free, NULL);
 | |
| 
 | |
|   self->priv->store = gtk_list_store_new (NUM_COLUMNS,
 | |
|                                           G_TYPE_APP_INFO,
 | |
|                                           G_TYPE_STRING, /* name */
 | |
|                                           G_TYPE_STRING, /* label */
 | |
|                                           G_TYPE_ICON,
 | |
|                                           G_TYPE_BOOLEAN, /* separator */
 | |
|                                           G_TYPE_BOOLEAN); /* custom */
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| app_chooser_button_iter_from_custom_name (GtkAppChooserButton *self,
 | |
|                                           const gchar         *name,
 | |
|                                           GtkTreeIter         *set_me)
 | |
| {
 | |
|   GtkTreeIter iter;
 | |
|   gchar *custom_name = NULL;
 | |
| 
 | |
|   if (!gtk_tree_model_get_iter_first
 | |
|       (GTK_TREE_MODEL (self->priv->store), &iter))
 | |
|     return FALSE;
 | |
| 
 | |
|   do {
 | |
|     gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter,
 | |
|                         COLUMN_NAME, &custom_name,
 | |
|                         -1);
 | |
| 
 | |
|     if (g_strcmp0 (custom_name, name) == 0)
 | |
|       {
 | |
|         g_free (custom_name);
 | |
|         *set_me = iter;
 | |
| 
 | |
|         return TRUE;
 | |
|       }
 | |
| 
 | |
|     g_free (custom_name);
 | |
|   } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->priv->store), &iter));
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static void
 | |
| real_insert_custom_item (GtkAppChooserButton *self,
 | |
|                          const gchar *name,
 | |
| 			 const gchar *label,
 | |
| 			 GIcon *icon,
 | |
| 			 gboolean custom,
 | |
| 			 GtkTreeIter *iter)
 | |
| {
 | |
|   if (custom)
 | |
|     {
 | |
|       if (g_hash_table_lookup (self->priv->custom_item_names,
 | |
|                                name) != NULL)
 | |
|         {
 | |
|           g_warning ("Attempting to add custom item %s to GtkAppChooserButton, "
 | |
|                      "when there's already an item with the same name", name);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|       g_hash_table_insert (self->priv->custom_item_names,
 | |
|                            g_strdup (name), GINT_TO_POINTER (1));
 | |
|     }
 | |
| 
 | |
|   gtk_list_store_set (self->priv->store, iter,
 | |
|                       COLUMN_NAME, name,
 | |
|                       COLUMN_LABEL, label,
 | |
|                       COLUMN_ICON, icon,
 | |
|                       COLUMN_CUSTOM, custom,
 | |
|                       COLUMN_SEPARATOR, FALSE,
 | |
|                       -1);
 | |
| }
 | |
| 
 | |
| static void
 | |
| real_insert_separator (GtkAppChooserButton *self,
 | |
| 		       gboolean custom,
 | |
| 		       GtkTreeIter *iter)
 | |
| {
 | |
|   gtk_list_store_set (self->priv->store, iter,
 | |
| 		      COLUMN_CUSTOM, custom,
 | |
| 		      COLUMN_SEPARATOR, TRUE,
 | |
| 		      -1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_new:
 | |
|  * @content_type: the content type to show applications for
 | |
|  *
 | |
|  * Creates a new #GtkAppChooserButton for applications
 | |
|  * that can handle content of the given type.
 | |
|  *
 | |
|  * Returns: a newly created #GtkAppChooserButton
 | |
|  *
 | |
|  * Since: 3.0
 | |
|  */
 | |
| GtkWidget *
 | |
| gtk_app_chooser_button_new (const gchar *content_type)
 | |
| {
 | |
|   g_return_val_if_fail (content_type != NULL, NULL);
 | |
| 
 | |
|   return g_object_new (GTK_TYPE_APP_CHOOSER_BUTTON,
 | |
|                        "content-type", content_type,
 | |
|                        NULL);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_append_separator:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  *
 | |
|  * Appends a separator to the list of applications that is shown
 | |
|  * in the popup.
 | |
|  *
 | |
|  * Since: 3.0
 | |
|  */
 | |
| void
 | |
| gtk_app_chooser_button_append_separator (GtkAppChooserButton *self)
 | |
| {
 | |
|   GtkTreeIter iter;
 | |
| 
 | |
|   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
 | |
| 
 | |
|   gtk_list_store_append (self->priv->store, &iter);
 | |
|   real_insert_separator (self, TRUE, &iter);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_append_custom_item:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  * @name: the name of the custom item
 | |
|  * @label: the label for the custom item
 | |
|  * @icon: the icon for the custom item
 | |
|  *
 | |
|  * Appends a custom item to the list of applications that is shown
 | |
|  * in the popup; the item name must be unique per-widget.
 | |
|  * Clients can use the provided name as a detail for the
 | |
|  * #GtkAppChooserButton::custom-item-activated signal, to add a
 | |
|  * callback for the activation of a particular custom item in the list.
 | |
|  * See also gtk_app_chooser_button_append_separator().
 | |
|  *
 | |
|  * Since: 3.0
 | |
|  */
 | |
| void
 | |
| gtk_app_chooser_button_append_custom_item (GtkAppChooserButton *self,
 | |
|                                            const gchar         *name,
 | |
|                                            const gchar         *label,
 | |
|                                            GIcon               *icon)
 | |
| {
 | |
|   GtkTreeIter iter;
 | |
| 
 | |
|   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
 | |
|   g_return_if_fail (name != NULL);
 | |
| 
 | |
|   gtk_list_store_append (self->priv->store, &iter);
 | |
|   real_insert_custom_item (self, name, label, icon, TRUE, &iter);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_set_active_custom_item:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  * @name: the name of the custom item
 | |
|  *
 | |
|  * Selects a custom item previously added with
 | |
|  * gtk_app_chooser_button_append_custom_item().
 | |
|  *
 | |
|  * Use gtk_app_chooser_refresh() to bring the selection
 | |
|  * to its initial state.
 | |
|  *
 | |
|  * Since: 3.0
 | |
|  */
 | |
| void
 | |
| gtk_app_chooser_button_set_active_custom_item (GtkAppChooserButton *self,
 | |
|                                                const gchar         *name)
 | |
| {
 | |
|   GtkTreeIter iter;
 | |
| 
 | |
|   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
 | |
|   g_return_if_fail (name != NULL);
 | |
| 
 | |
|   if (g_hash_table_lookup (self->priv->custom_item_names, name) == NULL ||
 | |
|       !app_chooser_button_iter_from_custom_name (self, name, &iter))
 | |
|     {
 | |
|       g_warning ("Can't find the item named %s in the app chooser.",
 | |
|                  name);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self), &iter);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_get_show_dialog_item:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  *
 | |
|  * Returns the current value of the #GtkAppChooserButton:show-dialog-item
 | |
|  * property.
 | |
|  *
 | |
|  * Returns: the value of #GtkAppChooserButton:show-dialog-item
 | |
|  *
 | |
|  * Since: 3.0
 | |
|  */
 | |
| gboolean
 | |
| gtk_app_chooser_button_get_show_dialog_item (GtkAppChooserButton *self)
 | |
| {
 | |
|   g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), FALSE);
 | |
| 
 | |
|   return self->priv->show_dialog_item;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_set_show_dialog_item:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  * @setting: the new value for #GtkAppChooserButton:show-dialog-item
 | |
|  *
 | |
|  * Sets whether the dropdown menu of this button should show an
 | |
|  * entry to trigger a #GtkAppChooserDialog.
 | |
|  *
 | |
|  * Since: 3.0
 | |
|  */
 | |
| void
 | |
| gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self,
 | |
|                                              gboolean             setting)
 | |
| {
 | |
|   if (self->priv->show_dialog_item != setting)
 | |
|     {
 | |
|       self->priv->show_dialog_item = setting;
 | |
| 
 | |
|       g_object_notify (G_OBJECT (self), "show-dialog-item");
 | |
| 
 | |
|       gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_get_show_default_item:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  *
 | |
|  * Returns the current value of the #GtkAppChooserButton:show-default-item
 | |
|  * property.
 | |
|  *
 | |
|  * Returns: the value of #GtkAppChooserButton:show-default-item
 | |
|  *
 | |
|  * Since: 3.2
 | |
|  */
 | |
| gboolean
 | |
| gtk_app_chooser_button_get_show_default_item (GtkAppChooserButton *self)
 | |
| {
 | |
|   g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), FALSE);
 | |
| 
 | |
|   return self->priv->show_default_item;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_set_show_default_item:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  * @setting: the new value for #GtkAppChooserButton:show-default-item
 | |
|  *
 | |
|  * Sets whether the dropdown menu of this button should show the
 | |
|  * default application for the given content type at top.
 | |
|  *
 | |
|  * Since: 3.2
 | |
|  */
 | |
| void
 | |
| gtk_app_chooser_button_set_show_default_item (GtkAppChooserButton *self,
 | |
|                                               gboolean             setting)
 | |
| {
 | |
|   if (self->priv->show_default_item != setting)
 | |
|     {
 | |
|       self->priv->show_default_item = setting;
 | |
| 
 | |
|       g_object_notify (G_OBJECT (self), "show-default-item");
 | |
| 
 | |
|       gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
 | |
|     }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_set_heading:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  * @heading: a string containing Pango markup
 | |
|  *
 | |
|  * Sets the text to display at the top of the dialog.
 | |
|  * If the heading is not set, the dialog displays a default text.
 | |
|  */
 | |
| void
 | |
| gtk_app_chooser_button_set_heading (GtkAppChooserButton *self,
 | |
|                                     const gchar         *heading)
 | |
| {
 | |
|   g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self));
 | |
| 
 | |
|   g_free (self->priv->heading);
 | |
|   self->priv->heading = g_strdup (heading);
 | |
| 
 | |
|   g_object_notify (G_OBJECT (self), "heading");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_app_chooser_button_get_heading:
 | |
|  * @self: a #GtkAppChooserButton
 | |
|  *
 | |
|  * Returns the text to display at the top of the dialog.
 | |
|  *
 | |
|  * Returns: the text to display at the top of the dialog,
 | |
|  *     or %NULL, in which case a default text is displayed
 | |
|  */
 | |
| const gchar *
 | |
| gtk_app_chooser_button_get_heading (GtkAppChooserButton *self)
 | |
| {
 | |
|   g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), NULL);
 | |
| 
 | |
|   return self->priv->heading;
 | |
| }
 | 
