]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkappchooserbutton.c
Made GtkBubble/SelectionWindow private at the moment
[~andy/gtk] / gtk / gtkappchooserbutton.c
index f7bb39252908c4b21695866f1a8b43ed92dab46e..235464c58e80cd1d1cffb2033bf44c4f02c91f56 100644 (file)
  * Library General Public License for more details.
  *
  * You should have received a copy of the GNU Library General Public
- * License along with the Gnome Library; see the file COPYING.LIB.  If not,
- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * 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 "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,
-  COLUMN_CALLBACK,
   NUM_COLUMNS,
 };
 
-typedef struct {
-  GtkAppChooserButtonItemFunc func;
-  gpointer user_data;
-} CustomAppComboData;
-
-static gpointer
-custom_app_data_copy (gpointer boxed)
-{
-  CustomAppComboData *retval, *original;
-
-  original = boxed;
-
-  retval = g_slice_new0 (CustomAppComboData);
-  retval->func = original->func;
-  retval->user_data = original->user_data;
-
-  return retval;
-}
-
-static void
-custom_app_data_free (gpointer boxed)
-{
-  g_slice_free (CustomAppComboData, boxed);
-}
-
-#define CUSTOM_COMBO_DATA_TYPE custom_app_combo_data_get_type()
-G_DEFINE_BOXED_TYPE (CustomAppComboData, custom_app_combo_data,
-                     custom_app_data_copy,
-                     custom_app_data_free);
+#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 *label,
-                                    GIcon *icon,
-                                    GtkAppChooserButtonItemFunc func,
-                                    gpointer user_data,
-                                    gboolean custom,
-                                    GtkTreeIter *iter);
+                                     const gchar *name,
+                                     const gchar *label,
+                                     GIcon *icon,
+                                     gboolean custom,
+                                     GtkTreeIter *iter);
 
 static void real_insert_separator (GtkAppChooserButton *self,
-                                  gboolean custom,
-                                  GtkTreeIter *iter);
+                                   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,
@@ -102,7 +109,12 @@ 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
@@ -154,32 +166,36 @@ select_app_data_free (SelectAppData *data)
 
 static gboolean
 select_application_func_cb (GtkTreeModel *model,
-                           GtkTreePath *path,
-                           GtkTreeIter *iter,
-                           gpointer user_data)
+                            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);
+                      COLUMN_APP_INFO, &app,
+                      COLUMN_CUSTOM, &custom,
+                      -1);
 
-  /* cutsom items are always after GAppInfos, so iterating further here
+  /* custom items are always after GAppInfos, so iterating further here
    * is just useless.
    */
   if (custom)
-    return TRUE;
-
-  if (g_app_info_equal (app, app_to_match))
+    result = TRUE;
+  else if (g_app_info_equal (app, app_to_match))
     {
       gtk_combo_box_set_active_iter (GTK_COMBO_BOX (data->self), iter);
-      return TRUE;
+      result = TRUE;
     }
+  else
+    result = FALSE;
 
-  return FALSE;
+  g_object_unref (app);
+
+  return result;
 }
 
 static void
@@ -193,15 +209,15 @@ gtk_app_chooser_button_select_application (GtkAppChooserButton *self,
   data->info = g_object_ref (info);
 
   gtk_tree_model_foreach (GTK_TREE_MODEL (self->priv->store),
-                         select_application_func_cb, data);
+                          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)
+                                      gint response_id,
+                                      gpointer user_data)
 {
   GtkAppChooserButton *self = user_data;
   GAppInfo *info;
@@ -209,15 +225,17 @@ other_application_dialog_response_cb (GtkDialog *dialog,
   if (response_id != GTK_RESPONSE_OK)
     {
       /* reset the active item, otherwise we are stuck on
-       * 'Other application...'
+       * 'Other application'
        */
-      gtk_combo_box_set_active (GTK_COMBO_BOX (self), 0);
+      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);
@@ -226,47 +244,73 @@ other_application_dialog_response_cb (GtkDialog *dialog,
 }
 
 static void
-other_application_item_activated_cb (GtkAppChooserButton *self,
-                                    gpointer _user_data)
+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);
+                                                        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);
+                "show-fallback", TRUE,
+                "show-other", TRUE,
+                NULL);
   gtk_widget_show (dialog);
 
   g_signal_connect (dialog, "response",
-                   G_CALLBACK (other_application_dialog_response_cb), self);
+                    G_CALLBACK (other_application_dialog_response_cb), self);
 }
 
 static void
 gtk_app_chooser_button_ensure_dialog_item (GtkAppChooserButton *self,
                                            GtkTreeIter *prev_iter)
 {
-  GIcon *icon;
-  GtkTreeIter iter;
+  GtkTreeIter iter, iter2;
 
-  if (!self->priv->show_dialog_item)
+  if (!self->priv->show_dialog_item || !self->priv->content_type)
     return;
 
-  icon = g_themed_icon_new ("application-x-executable");
+  if (prev_iter == NULL)
+    gtk_list_store_append (self->priv->store, &iter);
+  else
+    gtk_list_store_insert_after (self->priv->store, &iter, prev_iter);
 
-  gtk_list_store_insert_after (self->priv->store, &iter, prev_iter);
   real_insert_separator (self, FALSE, &iter);
-  *prev_iter = iter;
+  iter2 = iter;
 
-  gtk_list_store_insert_after (self->priv->store, &iter, prev_iter);
-  real_insert_custom_item (self,
-                          _("Other application..."), icon,
-                          other_application_item_activated_cb,
-                          NULL, FALSE, &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);
 }
@@ -275,47 +319,60 @@ static void
 gtk_app_chooser_button_populate (GtkAppChooserButton *self)
 {
   GList *recommended_apps = NULL, *l;
-  GAppInfo *app;
+  GAppInfo *app, *default_app = NULL;
   GtkTreeIter iter, iter2;
-  GIcon *icon;
-  gboolean first;
+  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;
 
-  recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
-  first = TRUE;
+  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;
 
-      icon = g_app_info_get_icon (app);
-
-      if (icon == NULL)
-        icon = g_themed_icon_new ("application-x-executable");
-      else
-        g_object_ref (icon);
+      if (default_app != NULL && g_app_info_equal (app, default_app))
+        continue;
 
-      if (first)
+      if (cycled_recommended)
         {
-          get_first_iter (self->priv->store, &iter);
-          first = FALSE;
+          gtk_list_store_insert_after (self->priv->store, &iter2, &iter);
+          iter = iter2;
         }
       else
         {
-          gtk_list_store_insert_after (self->priv->store, &iter2, &iter);
-          iter = iter2;
+          get_first_iter (self->priv->store, &iter);
+          cycled_recommended = TRUE;
         }
 
-      gtk_list_store_set (self->priv->store, &iter,
-                          COLUMN_APP_INFO, app,
-                          COLUMN_NAME, g_app_info_get_display_name (app),
-                          COLUMN_ICON, icon,
-                          COLUMN_CUSTOM, FALSE,
-                          -1);
-
-      g_object_unref (icon);
+      insert_one_application (self, app, &iter);
     }
 
-  gtk_app_chooser_button_ensure_dialog_item (self, &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);
 }
 
@@ -323,32 +380,33 @@ static void
 gtk_app_chooser_button_build_ui (GtkAppChooserButton *self)
 {
   GtkCellRenderer *cell;
-
-  self->priv->store = gtk_list_store_new (NUM_COLUMNS,
-                                          G_TYPE_APP_INFO,
-                                          G_TYPE_STRING,
-                                          G_TYPE_ICON,
-                                          G_TYPE_BOOLEAN,
-                                          G_TYPE_BOOLEAN,
-                                          CUSTOM_COMBO_DATA_TYPE);
+  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_layout_pack_start (GTK_CELL_LAYOUT (self), cell, FALSE);
+  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_layout_pack_start (GTK_CELL_LAYOUT (self), cell, TRUE);
+  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_NAME,
-                                  "xpad", 6,
+                                  "text", COLUMN_LABEL,
                                   NULL);
 
   gtk_app_chooser_button_populate (self);
@@ -382,17 +440,36 @@ gtk_app_chooser_button_changed (GtkComboBox *object)
 {
   GtkAppChooserButton *self = GTK_APP_CHOOSER_BUTTON (object);
   GtkTreeIter iter;
-  CustomAppComboData *custom_data = NULL;
+  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_CALLBACK, &custom_data,
+                      COLUMN_NAME, &name,
+                      COLUMN_CUSTOM, &custom,
                       -1);
 
-  if (custom_data != NULL && custom_data->func != NULL)
-    custom_data->func (self, custom_data->user_data);
+  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
@@ -429,8 +506,6 @@ gtk_app_chooser_button_constructed (GObject *obj)
   if (G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed != NULL)
     G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed (obj);
 
-  g_assert (self->priv->content_type != NULL);
-
   gtk_app_chooser_button_build_ui (self);
 }
 
@@ -450,6 +525,12 @@ gtk_app_chooser_button_set_property (GObject      *obj,
     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;
@@ -472,6 +553,12 @@ gtk_app_chooser_button_get_property (GObject    *obj,
     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;
@@ -483,7 +570,11 @@ 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);
 }
@@ -514,16 +605,69 @@ gtk_app_chooser_button_class_init (GtkAppChooserButtonClass *klass)
   /**
    * GtkAppChooserButton:show-dialog-item:
    *
-   * The ::show-dialog-item property determines whether the dropdown menu
-   * should show an item that triggers a #GtkAppChooserDialog when clicked.
+   * 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);
+  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));
 }
 
@@ -532,30 +676,79 @@ 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,
-                        GtkAppChooserButtonItemFunc func,
-                        gpointer user_data,
                         gboolean custom,
                         GtkTreeIter *iter)
 {
-  CustomAppComboData *data;
+  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;
+        }
 
-  data = g_slice_new0 (CustomAppComboData);
-  data->func = func;
-  data->user_data = user_data;
+      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, label,
-                     COLUMN_ICON, icon,
-                     COLUMN_CALLBACK, data,
-                     COLUMN_CUSTOM, custom,
-                     COLUMN_SEPARATOR, FALSE,
-                     -1);
+                      COLUMN_NAME, name,
+                      COLUMN_LABEL, label,
+                      COLUMN_ICON, icon,
+                      COLUMN_CUSTOM, custom,
+                      COLUMN_SEPARATOR, FALSE,
+                      -1);
 }
 
 static void
@@ -613,30 +806,65 @@ gtk_app_chooser_button_append_separator (GtkAppChooserButton *self)
 /**
  * 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
- * @func: callback to call if the item is activated
- * @user_data: user data for @func
  *
  * Appends a custom item to the list of applications that is shown
- * in the popup. See also gtk_app_chooser_button_append_separator().
+ * 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                   *label,
-                                           GIcon                         *icon,
-                                           GtkAppChooserButtonItemFunc  func,
-                                           gpointer                       user_data)
+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, label, icon,
-                           func, user_data, TRUE, &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);
 }
 
 /**
@@ -659,7 +887,7 @@ gtk_app_chooser_button_get_show_dialog_item (GtkAppChooserButton *self)
 }
 
 /**
- * gtk_app_chooser_button_get_show_dialog_item:
+ * gtk_app_chooser_button_set_show_dialog_item:
  * @self: a #GtkAppChooserButton
  * @setting: the new value for #GtkAppChooserButton:show-dialog-item
  *
@@ -670,7 +898,7 @@ gtk_app_chooser_button_get_show_dialog_item (GtkAppChooserButton *self)
  */
 void
 gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self,
-                                             gboolean setting)
+                                             gboolean             setting)
 {
   if (self->priv->show_dialog_item != setting)
     {
@@ -681,3 +909,83 @@ gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self,
       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;
+}