summaryrefslogtreecommitdiff
path: root/gtk
diff options
context:
space:
mode:
authorTristan Van Berkom <tristan.van.berkom@gmail.com>2010-12-02 13:58:02 +0900
committerTristan Van Berkom <tristan.van.berkom@gmail.com>2010-12-02 13:58:02 +0900
commit03a72118add37e6df51c12dba6c62298e810d41a (patch)
tree200a88ad09d55a5e0a207d0f7275de146e8b337c /gtk
parenta9fd00a4358f0ff73280bfeb51164872aea50e9b (diff)
parentdd678ac323b81e67bb1dea488f2123efee7e3500 (diff)
downloadgtk+-03a72118add37e6df51c12dba6c62298e810d41a.tar.gz
Merge branch 'master' into treeview-refactor
Conflicts: tests/Makefile.am
Diffstat (limited to 'gtk')
-rw-r--r--gtk/Makefile.am37
-rwxr-xr-xgtk/abicheck.sh2
-rw-r--r--gtk/gtk.h5
-rw-r--r--gtk/gtk.symbols51
-rw-r--r--gtk/gtkappchooser.c109
-rw-r--r--gtk/gtkappchooser.h51
-rw-r--r--gtk/gtkappchooserbutton.c784
-rw-r--r--gtk/gtkappchooserbutton.h78
-rw-r--r--gtk/gtkappchooserdialog.c787
-rw-r--r--gtk/gtkappchooserdialog.h73
-rw-r--r--gtk/gtkappchoosermodule.c59
-rw-r--r--gtk/gtkappchoosermodule.h35
-rw-r--r--gtk/gtkappchooseronline.c106
-rw-r--r--gtk/gtkappchooseronline.h73
-rw-r--r--gtk/gtkappchooseronlinepk.c263
-rw-r--r--gtk/gtkappchooseronlinepk.h53
-rw-r--r--gtk/gtkappchooserprivate.h45
-rw-r--r--gtk/gtkappchooserwidget.c1503
-rw-r--r--gtk/gtkappchooserwidget.h101
-rw-r--r--gtk/gtkassistant.c166
-rw-r--r--gtk/gtkbuilder.c2
-rw-r--r--gtk/gtkcellrenderer.c2
-rw-r--r--gtk/gtkcellrenderertext.c4
-rw-r--r--gtk/gtkcombobox.c212
-rw-r--r--gtk/gtkcombobox.h6
-rw-r--r--gtk/gtkcomboboxtext.c101
-rw-r--r--gtk/gtkcomboboxtext.h10
-rw-r--r--gtk/gtkenums.h1
-rw-r--r--gtk/gtkiconview.c45
-rw-r--r--gtk/gtkrange.c20
-rw-r--r--gtk/gtkscale.c5
-rw-r--r--gtk/gtkselection.h6
-rw-r--r--gtk/gtkspinner.c203
-rw-r--r--gtk/gtkswitch.c997
-rw-r--r--gtk/gtkswitch.h94
-rw-r--r--gtk/gtktoolbar.c8
36 files changed, 5813 insertions, 284 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 6d3edd8a7a..b3d6e61198 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -156,7 +156,11 @@ gtk_public_h_sources = \
gtkactivatable.h \
gtkadjustment.h \
gtkalignment.h \
- gtkapplication.h \
+ gtkappchooser.h \
+ gtkappchooserdialog.h \
+ gtkappchooserbutton.h \
+ gtkappchooserwidget.h \
+ gtkapplication.h \
gtkarrow.h \
gtkaspectframe.h \
gtkassistant.h \
@@ -292,6 +296,7 @@ gtk_public_h_sources = \
gtkstatusicon.h \
gtkstock.h \
gtkstyle.h \
+ gtkswitch.h \
gtktable.h \
gtktearoffmenuitem.h \
gtktestutils.h \
@@ -350,6 +355,18 @@ endif
gtk_semi_private_h_sources = \
gtktextlayout.h
+if ENABLE_PACKAGEKIT
+gtk_appchooser_impl_h_sources = \
+ gtkappchooseronlinepk.h \
+ $(NULL)
+endif
+
+if ENABLE_PACKAGEKIT
+gtk_appchooser_impl_c_sources = \
+ gtkappchooseronlinepk.c \
+ $(NULL)
+endif
+
# GTK+ header files that don't get installed
gtk_private_h_sources = \
gtkbuttonprivate.h \
@@ -374,6 +391,9 @@ gtk_private_h_sources = \
gtkmenuprivate.h \
gtkmnemonichash.h \
gtkmountoperationprivate.h \
+ gtkappchooserprivate.h \
+ gtkappchoosermodule.h \
+ gtkappchooseronline.h \
gtkpango.h \
gtkpathbar.h \
gtkplugprivate.h \
@@ -401,7 +421,8 @@ gtk_private_h_sources = \
gtktreeprivate.h \
gtkwindow-decorate.h \
gtkwidgetprivate.h \
- $(gtk_clipboard_dnd_h_sources)
+ $(gtk_clipboard_dnd_h_sources) \
+ $(gtk_appchooser_impl_h_sources)
# GTK+ C sources to build the library from
gtk_base_c_sources = \
@@ -419,7 +440,13 @@ gtk_base_c_sources = \
gtkactivatable.c \
gtkadjustment.c \
gtkalignment.c \
- gtkapplication.c \
+ gtkappchooser.c \
+ gtkappchooserwidget.c \
+ gtkappchooserbutton.c \
+ gtkappchooserdialog.c \
+ gtkappchoosermodule.c \
+ gtkappchooseronline.c \
+ gtkapplication.c \
gtkarrow.c \
gtkaspectframe.c \
gtkassistant.c \
@@ -569,6 +596,7 @@ gtk_base_c_sources = \
gtkstatusicon.c \
gtkstock.c \
gtkstyle.c \
+ gtkswitch.c \
gtktable.c \
gtktearoffmenuitem.c \
gtktestutils.c \
@@ -622,7 +650,8 @@ gtk_base_c_sources = \
gtkwidget.c \
gtkwindow-decorate.c \
gtkwindow.c \
- $(gtk_clipboard_dnd_c_sources)
+ $(gtk_clipboard_dnd_c_sources) \
+ $(gtk_appchooser_impl_c_sources)
gtk_c_sources = $(gtk_base_c_sources)
gtk_all_c_sources = $(gtk_base_c_sources)
diff --git a/gtk/abicheck.sh b/gtk/abicheck.sh
index a48597c178..e818a64fe4 100755
--- a/gtk/abicheck.sh
+++ b/gtk/abicheck.sh
@@ -1,5 +1,5 @@
#! /bin/sh
-cpp -P -DG_OS_UNIX -DGTK_WINDOWING_X11 ${srcdir:-.}/gtk.symbols | sed -e '/^$/d' -e 's/ G_GNUC.*$//' -e 's/ PRIVATE//' | sort > expected-abi
+cpp -P -DG_OS_UNIX -DGDK_WINDOWING_X11 ${srcdir:-.}/gtk.symbols | sed -e '/^$/d' -e 's/ G_GNUC.*$//' -e 's/ PRIVATE//' | sort > expected-abi
nm -D -g --defined-only .libs/libgtk-x11-3.0.so | cut -d ' ' -f 3 | egrep -v '^(__bss_start|_edata|_end)' | sort > actual-abi
diff -u expected-abi actual-abi && rm -f expected-abi actual-abi
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 4646fa5deb..38889aef1b 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -40,6 +40,10 @@
#include <gtk/gtkactivatable.h>
#include <gtk/gtkadjustment.h>
#include <gtk/gtkalignment.h>
+#include <gtk/gtkappchooser.h>
+#include <gtk/gtkappchooserdialog.h>
+#include <gtk/gtkappchooserwidget.h>
+#include <gtk/gtkappchooserbutton.h>
#include <gtk/gtkapplication.h>
#include <gtk/gtkarrow.h>
#include <gtk/gtkaspectframe.h>
@@ -174,6 +178,7 @@
#include <gtk/gtkstatusicon.h>
#include <gtk/gtkstock.h>
#include <gtk/gtkstyle.h>
+#include <gtk/gtkswitch.h>
#include <gtk/gtktable.h>
#include <gtk/gtktearoffmenuitem.h>
#include <gtk/gtktextbuffer.h>
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index 8188e30e02..7762a0e0a3 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -178,6 +178,35 @@ gtk_alignment_new
gtk_alignment_set
gtk_alignment_set_padding
gtk_alternative_dialog_button_order
+gtk_app_chooser_get_app_info
+gtk_app_chooser_get_content_type
+gtk_app_chooser_get_type G_GNUC_CONST
+gtk_app_chooser_refresh
+gtk_app_chooser_button_append_custom_item
+gtk_app_chooser_button_append_separator
+gtk_app_chooser_button_get_show_dialog_item
+gtk_app_chooser_button_get_type G_GNUC_CONST;
+gtk_app_chooser_button_new
+gtk_app_chooser_button_set_active_custom_item
+gtk_app_chooser_button_set_show_dialog_item
+gtk_app_chooser_dialog_new
+gtk_app_chooser_dialog_new_for_content_type
+gtk_app_chooser_dialog_get_type G_GNUC_CONST
+gtk_app_chooser_dialog_get_widget
+gtk_app_chooser_widget_new
+gtk_app_chooser_widget_get_show_all
+gtk_app_chooser_widget_get_default_text
+gtk_app_chooser_widget_get_show_default
+gtk_app_chooser_widget_get_show_fallback
+gtk_app_chooser_widget_get_show_other
+gtk_app_chooser_widget_get_show_recommended
+gtk_app_chooser_widget_get_type G_GNUC_CONST
+gtk_app_chooser_widget_set_default_text
+gtk_app_chooser_widget_set_show_all
+gtk_app_chooser_widget_set_show_default
+gtk_app_chooser_widget_set_show_fallback
+gtk_app_chooser_widget_set_show_other
+gtk_app_chooser_widget_set_show_recommended
gtk_application_add_window
gtk_application_get_type G_GNUC_CONST
gtk_application_get_windows
@@ -490,6 +519,7 @@ gtk_color_selection_set_previous_alpha
gtk_color_selection_set_previous_color
gtk_color_selection_set_previous_rgba
gtk_combo_box_get_active
+gtk_combo_box_get_active_id
gtk_combo_box_get_active_iter
gtk_combo_box_get_add_tearoffs
gtk_combo_box_get_button_sensitivity
@@ -497,6 +527,7 @@ gtk_combo_box_get_column_span_column
gtk_combo_box_get_entry_text_column
gtk_combo_box_get_focus_on_click
gtk_combo_box_get_has_entry
+gtk_combo_box_get_id_column
gtk_combo_box_get_model
gtk_combo_box_get_popup_accessible
gtk_combo_box_get_popup_fixed_width
@@ -513,24 +544,29 @@ gtk_combo_box_popdown
gtk_combo_box_popup
gtk_combo_box_popup_for_device
gtk_combo_box_set_active
+gtk_combo_box_set_active_id
gtk_combo_box_set_active_iter
gtk_combo_box_set_add_tearoffs
gtk_combo_box_set_button_sensitivity
gtk_combo_box_set_column_span_column
gtk_combo_box_set_entry_text_column
gtk_combo_box_set_focus_on_click
+gtk_combo_box_set_id_column
gtk_combo_box_set_model
gtk_combo_box_set_popup_fixed_width
gtk_combo_box_set_row_separator_func
gtk_combo_box_set_row_span_column
gtk_combo_box_set_title
gtk_combo_box_set_wrap_width
+gtk_combo_box_text_append
gtk_combo_box_text_append_text
gtk_combo_box_text_get_active_text
gtk_combo_box_text_get_type G_GNUC_CONST
+gtk_combo_box_text_insert
gtk_combo_box_text_insert_text
gtk_combo_box_text_new
gtk_combo_box_text_new_with_entry
+gtk_combo_box_text_prepend
gtk_combo_box_text_prepend_text
gtk_combo_box_text_remove
gtk_combo_box_text_remove_all
@@ -1200,9 +1236,11 @@ gtk_info_bar_set_default_response
gtk_info_bar_set_message_type
gtk_info_bar_set_response_sensitive
gtk_init
-gtk_init_abi_check
gtk_init_check
+#ifdef GDK_WINDOWING_WIN32
+gtk_init_abi_check
gtk_init_check_abi_check
+#endif
gtk_init_with_args
gtk_invisible_get_screen
gtk_invisible_get_type G_GNUC_CONST
@@ -1386,7 +1424,6 @@ gtk_message_dialog_new_with_markup G_GNUC_PRINTF(5,6)
gtk_message_dialog_set_image
gtk_message_dialog_set_markup
gtk_message_type_get_type G_GNUC_CONST
-gtk_metric_type_get_type G_GNUC_CONST
gtk_misc_get_alignment
gtk_misc_get_padding
gtk_misc_get_type G_GNUC_CONST
@@ -1699,8 +1736,6 @@ gtk_print_operation_set_use_full_page
gtk_print_pages_get_type G_GNUC_CONST
gtk_print_quality_get_type G_GNUC_CONST
gtk_print_run_page_setup_dialog
-gtk_print_run_page_setup_dialog
-gtk_print_run_page_setup_dialog_async
gtk_print_run_page_setup_dialog_async
gtk_print_settings_copy
gtk_print_settings_foreach
@@ -1797,10 +1832,12 @@ gtk_print_unix_dialog_set_manual_capabilities
gtk_print_unix_dialog_set_page_setup
gtk_print_unix_dialog_set_settings
gtk_print_unix_dialog_set_support_selection
+#ifdef GDK_WINDOWING_WIN32
gtk_print_win32_devnames_free
gtk_print_win32_devnames_from_printer_name
gtk_print_win32_devnames_from_win32
gtk_print_win32_devnames_to_win32
+#endif
gtk_progress_bar_get_ellipsize
gtk_progress_bar_get_fraction
gtk_progress_bar_get_inverted
@@ -2236,6 +2273,10 @@ gtk_stock_item_free
gtk_stock_list_ids
gtk_stock_lookup
gtk_stock_set_translate_func
+gtk_switch_get_active
+gtk_switch_get_type G_GNUC_CONST
+gtk_switch_new
+gtk_switch_set_active
gtk_style_apply_default_background
gtk_style_attach
gtk_style_copy
@@ -3313,7 +3354,9 @@ gtk_widget_trigger_tooltip_query
gtk_widget_unmap
gtk_widget_unparent
gtk_widget_unrealize
+#ifdef GDK_WINDOWING_WIN32
gtk_win32_embed_widget_get_type G_GNUC_CONST
+#endif
gtk_window_activate_default
gtk_window_activate_focus
gtk_window_activate_key
diff --git a/gtk/gtkappchooser.c b/gtk/gtkappchooser.c
new file mode 100644
index 0000000000..cb630c332b
--- /dev/null
+++ b/gtk/gtkappchooser.c
@@ -0,0 +1,109 @@
+/*
+ * gtkappchooser.c: app-chooser interface
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#include "config.h"
+
+#include "gtkappchooser.h"
+
+#include "gtkintl.h"
+#include "gtkappchooserprivate.h"
+#include "gtkwidget.h"
+
+#include <glib.h>
+
+G_DEFINE_INTERFACE (GtkAppChooser, gtk_app_chooser, GTK_TYPE_WIDGET);
+
+static void
+gtk_app_chooser_default_init (GtkAppChooserIface *iface)
+{
+ GParamSpec *pspec;
+
+ /**
+ * GtkAppChooser:content-type:
+ *
+ * The content type of the #GtkAppChooser object.
+ */
+ pspec = g_param_spec_string ("content-type",
+ P_("Content type"),
+ P_("The content type used by the open with object"),
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_interface_install_property (iface, pspec);
+}
+
+
+/**
+ * gtk_app_chooser_get_content_type:
+ * @self: a #GtkAppChooser
+ *
+ * Returns the current value of the #GtkAppChooser:content-type property.
+ *
+ * Returns: the content type of @self. Free with g_free()
+ *
+ * Since: 3.0
+ */
+gchar *
+gtk_app_chooser_get_content_type (GtkAppChooser *self)
+{
+ gchar *retval = NULL;
+
+ g_return_val_if_fail (GTK_IS_APP_CHOOSER (self), NULL);
+
+ g_object_get (self,
+ "content-type", &retval,
+ NULL);
+
+ return retval;
+}
+
+/**
+ * gtk_app_chooser_get_app_info:
+ * @self: a #GtkAppChooser
+ *
+ * Returns the currently selected application.
+ *
+ * Returns: (transfer full): a #GAppInfo for the currently selected
+ * application, or %NULL if none is selected. Free with g_object_unref()
+ *
+ * Since: 3.0
+ */
+GAppInfo *
+gtk_app_chooser_get_app_info (GtkAppChooser *self)
+{
+ return GTK_APP_CHOOSER_GET_IFACE (self)->get_app_info (self);
+}
+
+/**
+ * gtk_app_chooser_refresh:
+ * @self: a #GtkAppChooser
+ *
+ * Reloads the list of applications.
+ *
+ * Since: 3.0
+ */
+void
+gtk_app_chooser_refresh (GtkAppChooser *self)
+{
+ GTK_APP_CHOOSER_GET_IFACE (self)->refresh (self);
+}
diff --git a/gtk/gtkappchooser.h b/gtk/gtkappchooser.h
new file mode 100644
index 0000000000..35de8770c2
--- /dev/null
+++ b/gtk/gtkappchooser.h
@@ -0,0 +1,51 @@
+/*
+ * gtkappchooser.h: app-chooser interface
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_APP_CHOOSER_H__
+#define __GTK_APP_CHOOSER_H__
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_APP_CHOOSER (gtk_app_chooser_get_type ())
+#define GTK_APP_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_APP_CHOOSER, GtkAppChooser))
+#define GTK_IS_APP_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_APP_CHOOSER))
+
+typedef struct _GtkAppChooser GtkAppChooser;
+
+GType gtk_app_chooser_get_type (void) G_GNUC_CONST;
+
+GAppInfo * gtk_app_chooser_get_app_info (GtkAppChooser *self);
+gchar * gtk_app_chooser_get_content_type (GtkAppChooser *self);
+void gtk_app_chooser_refresh (GtkAppChooser *self);
+
+G_END_DECLS
+
+#endif /* __GTK_APP_CHOOSER_H__ */
+
diff --git a/gtk/gtkappchooserbutton.c b/gtk/gtkappchooserbutton.c
new file mode 100644
index 0000000000..a956a7953c
--- /dev/null
+++ b/gtk/gtkappchooserbutton.c
@@ -0,0 +1,784 @@
+/*
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#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,
+};
+
+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;
+ gboolean show_dialog_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;
+
+ gtk_tree_model_get (model, iter,
+ COLUMN_APP_INFO, &app,
+ COLUMN_CUSTOM, &custom,
+ -1);
+
+ /* cutsom 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))
+ {
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX (data->self), iter);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+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), 0);
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+ return;
+ }
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (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);
+ 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)
+{
+ GIcon *icon;
+ GtkTreeIter iter, iter2;
+
+ if (!self->priv->show_dialog_item)
+ 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);
+
+ 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..."), icon,
+ FALSE, &iter);
+
+ g_object_unref (icon);
+}
+
+static void
+gtk_app_chooser_button_populate (GtkAppChooserButton *self)
+{
+ GList *recommended_apps = NULL, *l;
+ GAppInfo *app;
+ GtkTreeIter iter, iter2;
+ GIcon *icon;
+ gboolean cycled_recommended;
+
+ recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
+ cycled_recommended = FALSE;
+
+ 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 (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;
+ }
+
+ gtk_list_store_set (self->priv->store, &iter,
+ COLUMN_APP_INFO, app,
+ COLUMN_LABEL, g_app_info_get_display_name (app),
+ COLUMN_ICON, icon,
+ COLUMN_CUSTOM, FALSE,
+ -1);
+
+ g_object_unref (icon);
+ }
+
+ 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;
+
+ 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 */
+
+ gtk_combo_box_set_model (GTK_COMBO_BOX (self),
+ GTK_TREE_MODEL (self->priv->store));
+
+ 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_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_layout_set_attributes (GTK_CELL_LAYOUT (self), cell,
+ "text", COLUMN_LABEL,
+ NULL);
+ g_object_set (cell,
+ "xpad", 6,
+ 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);
+ }
+ else
+ {
+ /* trigger the dialog internally */
+ other_application_item_activated_cb (self);
+ }
+
+ g_free (name);
+ }
+}
+
+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);
+
+ g_assert (self->priv->content_type != NULL);
+
+ 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;
+ 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;
+ 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_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::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);
+}
+
+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 ::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_select_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));
+ }
+}
diff --git a/gtk/gtkappchooserbutton.h b/gtk/gtkappchooserbutton.h
new file mode 100644
index 0000000000..67fc5de5d5
--- /dev/null
+++ b/gtk/gtkappchooserbutton.h
@@ -0,0 +1,78 @@
+/*
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_APP_CHOOSER_BUTTON_H__
+#define __GTK_APP_CHOOSER_BUTTON_H__
+
+#include <gtk/gtkcombobox.h>
+#include <gio/gio.h>
+
+#define GTK_TYPE_APP_CHOOSER_BUTTON (gtk_app_chooser_button_get_type ())
+#define GTK_APP_CHOOSER_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_APP_CHOOSER_BUTTON, GtkAppChooserButton))
+#define GTK_APP_CHOOSER_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_APP_CHOOSER_BUTTON, GtkAppChooserButtonClass))
+#define GTK_IS_APP_CHOOSER_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_APP_CHOOSER_BUTTON))
+#define GTK_IS_APP_CHOOSER_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_APP_CHOOSER_BUTTON))
+#define GTK_APP_CHOOSER_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_APP_CHOOSER_BUTTON, GtkAppChooserButtonClass))
+
+typedef struct _GtkAppChooserButton GtkAppChooserButton;
+typedef struct _GtkAppChooserButtonClass GtkAppChooserButtonClass;
+typedef struct _GtkAppChooserButtonPrivate GtkAppChooserButtonPrivate;
+
+struct _GtkAppChooserButton {
+ GtkComboBox parent;
+
+ /*< private >*/
+ GtkAppChooserButtonPrivate *priv;
+};
+
+struct _GtkAppChooserButtonClass {
+ GtkComboBoxClass parent_class;
+
+ void (* custom_item_activated) (GtkAppChooserButton *self,
+ const gchar *item_name);
+
+ /* padding for future class expansion */
+ gpointer padding[16];
+};
+
+GType gtk_app_chooser_button_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gtk_app_chooser_button_new (const gchar *content_type);
+
+void gtk_app_chooser_button_append_separator (GtkAppChooserButton *self);
+void gtk_app_chooser_button_append_custom_item (GtkAppChooserButton *self,
+ const gchar *name,
+ const gchar *label,
+ GIcon *icon);
+void gtk_app_chooser_button_set_active_custom_item (GtkAppChooserButton *self,
+ const gchar *name);
+
+void gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self,
+ gboolean setting);
+gboolean gtk_app_chooser_button_get_show_dialog_item (GtkAppChooserButton *self);
+
+#endif /* __GTK_APP_CHOOSER_BUTTON_H__ */
diff --git a/gtk/gtkappchooserdialog.c b/gtk/gtkappchooserdialog.c
new file mode 100644
index 0000000000..83cf7e00e4
--- /dev/null
+++ b/gtk/gtkappchooserdialog.c
@@ -0,0 +1,787 @@
+/*
+ * gtkappchooserdialog.c: an app-chooser dialog
+ *
+ * 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 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.
+ *
+ * Authors: Dave Camp <dave@novell.com>
+ * Alexander Larsson <alexl@redhat.com>
+ * Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#include "config.h"
+
+#include "gtkappchooserdialog.h"
+
+#include "gtkintl.h"
+#include "gtkappchooser.h"
+#include "gtkappchooseronline.h"
+#include "gtkappchooserprivate.h"
+#include "gtkappchooserprivate.h"
+
+#include "gtkmessagedialog.h"
+#include "gtklabel.h"
+#include "gtkbbox.h"
+#include "gtkbutton.h"
+#include "gtkmenuitem.h"
+#include "gtkstock.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+#define sure_string(s) ((const char *) ((s) != NULL ? (s) : ""))
+
+struct _GtkAppChooserDialogPrivate {
+ char *content_type;
+ GFile *gfile;
+
+ GtkWidget *label;
+ GtkWidget *button;
+ GtkWidget *online_button;
+
+ GtkWidget *open_label;
+
+ GtkWidget *app_chooser_widget;
+ GtkWidget *show_more_button;
+
+ GtkAppChooserOnline *online;
+
+ gboolean show_more_clicked;
+};
+
+enum {
+ PROP_GFILE = 1,
+ PROP_CONTENT_TYPE,
+ N_PROPERTIES
+};
+
+static void gtk_app_chooser_dialog_iface_init (GtkAppChooserIface *iface);
+G_DEFINE_TYPE_WITH_CODE (GtkAppChooserDialog, gtk_app_chooser_dialog, GTK_TYPE_DIALOG,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER,
+ gtk_app_chooser_dialog_iface_init));
+
+static void
+show_error_dialog (const gchar *primary,
+ const gchar *secondary,
+ GtkWindow *parent)
+{
+ GtkWidget *message_dialog;
+
+ message_dialog = gtk_message_dialog_new (parent, 0,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ NULL);
+ g_object_set (message_dialog,
+ "text", primary,
+ "secondary-text", secondary,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (message_dialog), GTK_RESPONSE_OK);
+
+ gtk_widget_show (message_dialog);
+
+ g_signal_connect (message_dialog, "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+}
+
+static void
+search_for_mimetype_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkAppChooserOnline *online = GTK_APP_CHOOSER_ONLINE (source);
+ GtkAppChooserDialog *self = user_data;
+ GError *error = NULL;
+
+ gtk_app_chooser_online_search_for_mimetype_finish (online, res, &error);
+
+ if (error != NULL)
+ {
+ show_error_dialog (_("Failed to look for applications online"),
+ error->message, GTK_WINDOW (self));
+ g_error_free (error);
+ }
+ else
+ {
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (self->priv->app_chooser_widget));
+ }
+}
+
+static void
+online_button_clicked_cb (GtkButton *b,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = user_data;
+
+ gtk_app_chooser_online_search_for_mimetype_async (self->priv->online,
+ self->priv->content_type,
+ GTK_WINDOW (self),
+ search_for_mimetype_ready_cb,
+ self);
+}
+
+static void
+app_chooser_online_get_default_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = user_data;
+
+ self->priv->online = gtk_app_chooser_online_get_default_finish (source, res);
+
+ if (self->priv->online != NULL)
+ {
+ GtkWidget *action_area;
+
+ action_area = gtk_dialog_get_action_area (GTK_DIALOG (self));
+ self->priv->online_button = gtk_button_new_with_label (_("Find applications online"));
+ gtk_box_pack_start (GTK_BOX (action_area), self->priv->online_button,
+ FALSE, FALSE, 0);
+ gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (action_area), self->priv->online_button,
+ TRUE);
+ g_signal_connect (self->priv->online_button, "clicked",
+ G_CALLBACK (online_button_clicked_cb), self);
+
+ gtk_widget_show (self->priv->online_button);
+ }
+}
+
+static void
+ensure_online_button (GtkAppChooserDialog *self)
+{
+ gtk_app_chooser_online_get_default_async (app_chooser_online_get_default_ready_cb, self);
+}
+
+/* An application is valid if:
+ *
+ * 1) The file exists
+ * 2) The user has permissions to run the file
+ */
+static gboolean
+check_application (GtkAppChooserDialog *self,
+ GAppInfo **app_out)
+{
+ const char *command;
+ char *path = NULL;
+ char **argv = NULL;
+ int argc;
+ GError *error = NULL;
+ gint retval = TRUE;
+ GAppInfo *info;
+
+ command = NULL;
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self->priv->app_chooser_widget));
+ command = g_app_info_get_executable (info);
+
+ g_shell_parse_argv (command, &argc, &argv, &error);
+
+ if (error)
+ {
+ show_error_dialog (_("Could not run application"),
+ error->message,
+ GTK_WINDOW (self));
+ g_error_free (error);
+ retval = FALSE;
+ goto cleanup;
+ }
+
+ path = g_find_program_in_path (argv[0]);
+ if (!path)
+ {
+ char *error_message;
+
+ error_message = g_strdup_printf (_("Could not find '%s'"),
+ argv[0]);
+
+ show_error_dialog (_("Could not find application"),
+ error_message,
+ GTK_WINDOW (self));
+ g_free (error_message);
+ retval = FALSE;
+ goto cleanup;
+ }
+
+ *app_out = info;
+
+ cleanup:
+ g_strfreev (argv);
+ g_free (path);
+
+ return retval;
+}
+
+static void
+add_or_find_application (GtkAppChooserDialog *self)
+{
+ GAppInfo *app;
+
+ app = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self));
+
+ /* we don't care about reporting errors here */
+ g_app_info_add_supports_type (app,
+ self->priv->content_type,
+ NULL);
+
+ g_object_unref (app);
+}
+
+static void
+gtk_app_chooser_dialog_response (GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (dialog);
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_OK:
+ add_or_find_application (self);
+ break;
+ default :
+ break;
+ }
+}
+
+static void
+widget_application_selected_cb (GtkAppChooserWidget *widget,
+ GAppInfo *app_info,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = user_data;
+
+ gtk_widget_set_sensitive (self->priv->button, TRUE);
+}
+
+static void
+widget_application_activated_cb (GtkAppChooserWidget *widget,
+ GAppInfo *app_info,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = user_data;
+
+ gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_OK);
+}
+
+static char *
+get_extension (const char *basename)
+{
+ char *p;
+
+ p = strrchr (basename, '.');
+
+ if (p && *(p + 1) != '\0')
+ return g_strdup (p + 1);
+
+ return NULL;
+}
+
+static void
+set_dialog_properties (GtkAppChooserDialog *self)
+{
+ gchar *label;
+ gchar *name;
+ gchar *extension;
+ gchar *description;
+ gchar *default_text;
+ gchar *string;
+ PangoFontDescription *font_desc;
+
+ name = NULL;
+ extension = NULL;
+ label = NULL;
+ description = NULL;
+
+ if (self->priv->gfile != NULL)
+ {
+ name = g_file_get_basename (self->priv->gfile);
+ extension = get_extension (name);
+ }
+
+ description = g_content_type_get_description (self->priv->content_type);
+ gtk_window_set_title (GTK_WINDOW (self), "");
+
+ if (name != NULL)
+ {
+ /* Translators: %s is a filename */
+ label = g_strdup_printf (_("Select an application to open \"%s\""), name);
+ string = g_strdup_printf (_("No applications available to open \"%s\""),
+ name);
+ }
+ else
+ {
+ /* Translators: %s is a file type description */
+ label = g_strdup_printf (_("Select an application for \"%s\" files"),
+ g_content_type_is_unknown (self->priv->content_type) ?
+ self->priv->content_type : description);
+ string = g_strdup_printf (_("No applications available to open \"%s\" files"),
+ g_content_type_is_unknown (self->priv->content_type) ?
+ self->priv->content_type : description);
+ }
+
+ font_desc = pango_font_description_new ();
+ pango_font_description_set_weight (font_desc, PANGO_WEIGHT_BOLD);
+ gtk_widget_modify_font (self->priv->label, font_desc);
+ pango_font_description_free (font_desc);
+
+ gtk_label_set_markup (GTK_LABEL (self->priv->label), label);
+
+ default_text = g_strdup_printf ("<big><b>%s</b></big>\n%s",
+ string,
+ _("Click \"Show other applications\", for more options, or "
+ "\"Find applications online\" to install a new application"));
+
+ gtk_app_chooser_widget_set_default_text (GTK_APP_CHOOSER_WIDGET (self->priv->app_chooser_widget),
+ default_text);
+
+ g_free (label);
+ g_free (name);
+ g_free (extension);
+ g_free (description);
+ g_free (string);
+ g_free (default_text);
+}
+
+static void
+show_more_button_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = user_data;
+
+ g_object_set (self->priv->app_chooser_widget,
+ "show-recommended", TRUE,
+ "show-fallback", TRUE,
+ "show-other", TRUE,
+ NULL);
+
+ gtk_widget_hide (self->priv->show_more_button);
+ self->priv->show_more_clicked = TRUE;
+}
+
+static void
+widget_notify_for_button_cb (GObject *source,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = user_data;
+ GtkAppChooserWidget *widget = GTK_APP_CHOOSER_WIDGET (source);
+ gboolean should_hide;
+
+ should_hide = gtk_app_chooser_widget_get_show_all (widget) ||
+ self->priv->show_more_clicked;
+
+ if (should_hide)
+ gtk_widget_hide (self->priv->show_more_button);
+}
+
+static void
+forget_menu_item_activate_cb (GtkMenuItem *item,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = user_data;
+ GAppInfo *info;
+
+ info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (self));
+
+ if (info != NULL)
+ {
+ g_app_info_remove_supports_type (info, self->priv->content_type, NULL);
+
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (self));
+
+ g_object_unref (info);
+ }
+}
+
+static GtkWidget *
+build_forget_menu_item (GtkAppChooserDialog *self)
+{
+ GtkWidget *retval;
+
+ retval = gtk_menu_item_new_with_label (_("Forget association"));
+ gtk_widget_show (retval);
+
+ g_signal_connect (retval, "activate",
+ G_CALLBACK (forget_menu_item_activate_cb), self);
+
+ return retval;
+}
+
+static void
+widget_populate_popup_cb (GtkAppChooserWidget *widget,
+ GtkMenu *menu,
+ GAppInfo *info,
+ gpointer user_data)
+{
+ GtkAppChooserDialog *self = user_data;
+ GtkWidget *menu_item;
+
+ if (g_app_info_can_remove_supports_type (info))
+ {
+ menu_item = build_forget_menu_item (self);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), menu_item);
+ }
+}
+
+static void
+build_dialog_ui (GtkAppChooserDialog *self)
+{
+ GtkWidget *vbox;
+ GtkWidget *vbox2;
+ GtkWidget *label;
+ GtkWidget *button, *w;
+
+ gtk_container_set_border_width (GTK_CONTAINER (self), 5);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (self))), vbox, TRUE, TRUE, 0);
+ gtk_widget_show (vbox);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0);
+ gtk_widget_show (vbox2);
+
+ self->priv->label = gtk_label_new ("");
+ gtk_misc_set_alignment (GTK_MISC (self->priv->label), 0, 0.5);
+ gtk_label_set_line_wrap (GTK_LABEL (self->priv->label), TRUE);
+ gtk_box_pack_start (GTK_BOX (vbox2), self->priv->label,
+ FALSE, FALSE, 0);
+ gtk_widget_show (self->priv->label);
+
+ self->priv->app_chooser_widget =
+ gtk_app_chooser_widget_new (self->priv->content_type);
+ gtk_box_pack_start (GTK_BOX (vbox2), self->priv->app_chooser_widget, TRUE, TRUE, 0);
+ gtk_widget_show (self->priv->app_chooser_widget);
+
+ g_signal_connect (self->priv->app_chooser_widget, "application-selected",
+ G_CALLBACK (widget_application_selected_cb), self);
+ g_signal_connect (self->priv->app_chooser_widget, "application-activated",
+ G_CALLBACK (widget_application_activated_cb), self);
+ g_signal_connect (self->priv->app_chooser_widget, "notify::show-all",
+ G_CALLBACK (widget_notify_for_button_cb), self);
+ g_signal_connect (self->priv->app_chooser_widget, "populate-popup",
+ G_CALLBACK (widget_populate_popup_cb), self);
+
+ button = gtk_button_new_with_label (_("Show other applications"));
+ self->priv->show_more_button = button;
+ w = gtk_image_new_from_stock (GTK_STOCK_ADD,
+ GTK_ICON_SIZE_BUTTON);
+ gtk_button_set_image (GTK_BUTTON (button), w);
+ gtk_box_pack_start (GTK_BOX (self->priv->app_chooser_widget), button, FALSE, FALSE, 6);
+ gtk_widget_show_all (button);
+
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (show_more_button_clicked_cb), self);
+
+ gtk_dialog_add_button (GTK_DIALOG (self),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+
+ /* Create a custom stock icon */
+ self->priv->button = gtk_button_new ();
+
+ label = gtk_label_new_with_mnemonic (_("_Open"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), GTK_WIDGET (self->priv->button));
+ gtk_widget_set_halign (label, GTK_ALIGN_CENTER);
+ gtk_widget_show (label);
+ self->priv->open_label = label;
+
+ gtk_container_add (GTK_CONTAINER (self->priv->button),
+ self->priv->open_label);
+
+ gtk_widget_show (self->priv->button);
+ gtk_widget_set_can_default (self->priv->button, TRUE);
+
+ gtk_dialog_add_action_widget (GTK_DIALOG (self),
+ self->priv->button, GTK_RESPONSE_OK);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (self),
+ GTK_RESPONSE_OK);
+}
+
+static void
+set_gfile_and_content_type (GtkAppChooserDialog *self,
+ GFile *file)
+{
+ GFileInfo *info;
+
+ if (file == NULL)
+ return;
+
+ self->priv->gfile = g_object_ref (file);
+
+ info = g_file_query_info (self->priv->gfile,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ 0, NULL, NULL);
+ self->priv->content_type = g_strdup (g_file_info_get_content_type (info));
+
+ g_object_unref (info);
+}
+
+static GAppInfo *
+gtk_app_chooser_dialog_get_app_info (GtkAppChooser *object)
+{
+ GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
+ GAppInfo *app = NULL;
+
+ if (!check_application (self, &app))
+ return NULL;
+
+ return app;
+}
+
+static void
+gtk_app_chooser_dialog_refresh (GtkAppChooser *object)
+{
+ GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
+
+ gtk_app_chooser_refresh (GTK_APP_CHOOSER (self->priv->app_chooser_widget));
+}
+
+static void
+gtk_app_chooser_dialog_constructed (GObject *object)
+{
+ GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
+
+ g_assert (self->priv->content_type != NULL ||
+ self->priv->gfile != NULL);
+
+ if (G_OBJECT_CLASS (gtk_app_chooser_dialog_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (gtk_app_chooser_dialog_parent_class)->constructed (object);
+
+ build_dialog_ui (self);
+ set_dialog_properties (self);
+ ensure_online_button (self);
+}
+
+static void
+gtk_app_chooser_dialog_dispose (GObject *object)
+{
+ GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
+
+ g_clear_object (&self->priv->gfile);
+ g_clear_object (&self->priv->online);
+
+ G_OBJECT_CLASS (gtk_app_chooser_dialog_parent_class)->dispose (object);
+}
+
+static void
+gtk_app_chooser_dialog_finalize (GObject *object)
+{
+ GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
+
+ g_free (self->priv->content_type);
+
+ G_OBJECT_CLASS (gtk_app_chooser_dialog_parent_class)->finalize (object);
+}
+
+static void
+gtk_app_chooser_dialog_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
+
+ switch (property_id)
+ {
+ case PROP_GFILE:
+ set_gfile_and_content_type (self, g_value_get_object (value));
+ break;
+ case PROP_CONTENT_TYPE:
+ /* don't try to override a value previously set with the GFile */
+ if (self->priv->content_type == NULL)
+ self->priv->content_type = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_app_chooser_dialog_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkAppChooserDialog *self = GTK_APP_CHOOSER_DIALOG (object);
+
+ switch (property_id)
+ {
+ case PROP_GFILE:
+ if (self->priv->gfile != NULL)
+ g_value_set_object (value, self->priv->gfile);
+ break;
+ case PROP_CONTENT_TYPE:
+ g_value_set_string (value, self->priv->content_type);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_app_chooser_dialog_iface_init (GtkAppChooserIface *iface)
+{
+ iface->get_app_info = gtk_app_chooser_dialog_get_app_info;
+ iface->refresh = gtk_app_chooser_dialog_refresh;
+}
+
+static void
+gtk_app_chooser_dialog_class_init (GtkAppChooserDialogClass *klass)
+{
+ GObjectClass *gobject_class;
+ GParamSpec *pspec;
+
+ gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->dispose = gtk_app_chooser_dialog_dispose;
+ gobject_class->finalize = gtk_app_chooser_dialog_finalize;
+ gobject_class->set_property = gtk_app_chooser_dialog_set_property;
+ gobject_class->get_property = gtk_app_chooser_dialog_get_property;
+ gobject_class->constructed = gtk_app_chooser_dialog_constructed;
+
+ g_object_class_override_property (gobject_class, PROP_CONTENT_TYPE, "content-type");
+
+ /**
+ * GtkAppChooserDialog:gfile:
+ *
+ * The GFile used by the #GtkAppChooserDialog.
+ * The dialog's #GtkAppChooserWidget content type will be guessed from the
+ * file, if present.
+ */
+ pspec = g_param_spec_object ("gfile",
+ P_("GFile"),
+ P_("The GFile used by the app chooser dialog"),
+ G_TYPE_FILE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_GFILE, pspec);
+
+ g_type_class_add_private (klass, sizeof (GtkAppChooserDialogPrivate));
+}
+
+static void
+gtk_app_chooser_dialog_init (GtkAppChooserDialog *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_APP_CHOOSER_DIALOG,
+ GtkAppChooserDialogPrivate);
+
+ /* we can't override the class signal handler here, as it's a RUN_LAST;
+ * we want our signal handler instead to be executed before any user code.
+ */
+ g_signal_connect (self, "response",
+ G_CALLBACK (gtk_app_chooser_dialog_response), NULL);
+}
+
+static void
+set_parent_and_flags (GtkWidget *dialog,
+ GtkWindow *parent,
+ GtkDialogFlags flags)
+{
+ if (parent != NULL)
+ gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+
+ if (flags & GTK_DIALOG_MODAL)
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ if (flags & GTK_DIALOG_DESTROY_WITH_PARENT)
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+}
+
+/**
+ * gtk_app_chooser_dialog_new:
+ * @parent: (allow-none): a #GtkWindow, or %NULL
+ * @flags: flags for this dialog
+ * @file: a #GFile
+ *
+ * Creates a new #GtkAppChooserDialog for the provided #GFile,
+ * to allow the user to select an application for it.
+ *
+ * Returns: a newly created #GtkAppChooserDialog
+ *
+ * Since: 3.0
+ **/
+GtkWidget *
+gtk_app_chooser_dialog_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ GFile *file)
+{
+ GtkWidget *retval;
+
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+ retval = g_object_new (GTK_TYPE_APP_CHOOSER_DIALOG,
+ "gfile", file,
+ NULL);
+
+ set_parent_and_flags (retval, parent, flags);
+
+ return retval;
+}
+
+/**
+ * gtk_app_chooser_dialog_new_for_content_type:
+ * @parent: (allow-none): a #GtkWindow, or %NULL
+ * @flags: flags for this dialog
+ * @content_type: a content type string
+ *
+ * Creates a new #GtkAppChooserDialog for the provided content type,
+ * to allow the user to select an application for it.
+ *
+ * Returns: a newly created #GtkAppChooserDialog
+ *
+ * Since: 3.0
+ **/
+GtkWidget *
+gtk_app_chooser_dialog_new_for_content_type (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const gchar *content_type)
+{
+ GtkWidget *retval;
+
+ g_return_val_if_fail (content_type != NULL, NULL);
+
+ retval = g_object_new (GTK_TYPE_APP_CHOOSER_DIALOG,
+ "content-type", content_type,
+ NULL);
+
+ set_parent_and_flags (retval, parent, flags);
+
+ return retval;
+}
+
+/**
+ * gtk_app_chooser_dialog_get_widget:
+ * @self: a #GtkAppChooserDialog
+ *
+ * Returns the #GtkAppChooserWidget of this dialog.
+ *
+ * Returns: (transfer none): the #GtkAppChooserWidget of @self
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+gtk_app_chooser_dialog_get_widget (GtkAppChooserDialog *self)
+{
+ g_return_val_if_fail (GTK_IS_APP_CHOOSER_DIALOG (self), NULL);
+
+ return self->priv->app_chooser_widget;
+}
diff --git a/gtk/gtkappchooserdialog.h b/gtk/gtkappchooserdialog.h
new file mode 100644
index 0000000000..bcb3dd97d4
--- /dev/null
+++ b/gtk/gtkappchooserdialog.h
@@ -0,0 +1,73 @@
+/*
+ * gtkappchooserdialog.h: an app-chooser dialog
+ *
+ * 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 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.
+ *
+ * Authors: Dave Camp <dave@novell.com>
+ * Alexander Larsson <alexl@redhat.com>
+ * Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_APP_CHOOSER_DIALOG_H__
+#define __GTK_APP_CHOOSER_DIALOG_H__
+
+#include <gtk/gtkdialog.h>
+#include <gio/gio.h>
+
+#define GTK_TYPE_APP_CHOOSER_DIALOG (gtk_app_chooser_dialog_get_type ())
+#define GTK_APP_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_APP_CHOOSER_DIALOG, GtkAppChooserDialog))
+#define GTK_APP_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_APP_CHOOSER_DIALOG, GtkAppChooserDialogClass))
+#define GTK_IS_APP_CHOOSER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_APP_CHOOSER_DIALOG))
+#define GTK_IS_APP_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_APP_CHOOSER_DIALOG))
+#define GTK_APP_CHOOSER_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_APP_CHOOSER_DIALOG, GtkAppChooserDialogClass))
+
+typedef struct _GtkAppChooserDialog GtkAppChooserDialog;
+typedef struct _GtkAppChooserDialogClass GtkAppChooserDialogClass;
+typedef struct _GtkAppChooserDialogPrivate GtkAppChooserDialogPrivate;
+
+struct _GtkAppChooserDialog {
+ GtkDialog parent;
+
+ /*< private >*/
+ GtkAppChooserDialogPrivate *priv;
+};
+
+struct _GtkAppChooserDialogClass {
+ GtkDialogClass parent_class;
+
+ /* padding for future class expansion */
+ gpointer padding[16];
+};
+
+GType gtk_app_chooser_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gtk_app_chooser_dialog_new (GtkWindow *parent,
+ GtkDialogFlags flags,
+ GFile *file);
+GtkWidget * gtk_app_chooser_dialog_new_for_content_type (GtkWindow *parent,
+ GtkDialogFlags flags,
+ const gchar *content_type);
+
+GtkWidget * gtk_app_chooser_dialog_get_widget (GtkAppChooserDialog *self);
+
+#endif /* __GTK_APP_CHOOSER_DIALOG_H__ */
diff --git a/gtk/gtkappchoosermodule.c b/gtk/gtkappchoosermodule.c
new file mode 100644
index 0000000000..afcf3de966
--- /dev/null
+++ b/gtk/gtkappchoosermodule.c
@@ -0,0 +1,59 @@
+/*
+ * gtkappchoosermodule.c: an extension point for online integration
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#include "config.h"
+
+#include "gtkappchoosermodule.h"
+
+#include <gio/gio.h>
+
+#include "gtkappchooseronline.h"
+
+#ifdef ENABLE_PACKAGEKIT
+#include "gtkappchooseronlinepk.h"
+#endif
+
+G_LOCK_DEFINE_STATIC (registered_ep);
+
+void
+_gtk_app_chooser_module_ensure (void)
+{
+ static gboolean registered_ep = FALSE;
+ GIOExtensionPoint *ep;
+
+ G_LOCK (registered_ep);
+
+ if (!registered_ep)
+ {
+ registered_ep = TRUE;
+
+ ep = g_io_extension_point_register ("gtkappchooser-online");
+ g_io_extension_point_set_required_type (ep, GTK_TYPE_APP_CHOOSER_ONLINE);
+
+#ifdef ENABLE_PACKAGEKIT
+ _gtk_app_chooser_online_pk_get_type ();
+#endif
+ }
+
+ G_UNLOCK (registered_ep);
+}
diff --git a/gtk/gtkappchoosermodule.h b/gtk/gtkappchoosermodule.h
new file mode 100644
index 0000000000..cb66c95ff4
--- /dev/null
+++ b/gtk/gtkappchoosermodule.h
@@ -0,0 +1,35 @@
+/*
+ * gtkappchoosermodule.h: an extension point for online integration
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#ifndef __GTK_APP_CHOOSER_MODULE_H__
+#define __GTK_APP_CHOOSER_MODULE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+void _gtk_app_chooser_module_ensure (void);
+
+G_END_DECLS
+
+#endif /* __GTK_APP_CHOOSER_MODULE_H__ */
diff --git a/gtk/gtkappchooseronline.c b/gtk/gtkappchooseronline.c
new file mode 100644
index 0000000000..71267ca7f5
--- /dev/null
+++ b/gtk/gtkappchooseronline.c
@@ -0,0 +1,106 @@
+/*
+ * gtkappchooseronline.h: an extension point for online integration
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#include "config.h"
+
+#include "gtkappchooseronline.h"
+
+#include "gtkappchoosermodule.h"
+#include "gtkintl.h"
+
+#include <gio/gio.h>
+
+#define gtk_app_chooser_online_get_type _gtk_app_chooser_online_get_type
+static void gtk_app_chooser_online_default_init (GtkAppChooserOnlineInterface *iface);
+G_DEFINE_INTERFACE_WITH_CODE (GtkAppChooserOnline, gtk_app_chooser_online, G_TYPE_OBJECT,
+ g_type_interface_add_prerequisite (g_define_type_id, G_TYPE_ASYNC_INITABLE);)
+
+static void
+gtk_app_chooser_online_default_init (GtkAppChooserOnlineInterface *iface)
+{
+ /* do nothing */
+}
+
+GtkAppChooserOnline *
+gtk_app_chooser_online_get_default_finish (GObject *source,
+ GAsyncResult *result)
+{
+ GtkAppChooserOnline *retval;
+
+ retval = GTK_APP_CHOOSER_ONLINE (g_async_initable_new_finish (G_ASYNC_INITABLE (source),
+ result, NULL));
+
+ return retval;
+}
+
+void
+gtk_app_chooser_online_get_default_async (GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GIOExtensionPoint *ep;
+ GIOExtension *extension;
+ GList *extensions;
+
+ _gtk_app_chooser_module_ensure ();
+
+ ep = g_io_extension_point_lookup ("gtkappchooser-online");
+ extensions = g_io_extension_point_get_extensions (ep);
+
+ if (extensions != NULL)
+ {
+ /* pick the first */
+ extension = extensions->data;
+ g_async_initable_new_async (g_io_extension_get_type (extension), G_PRIORITY_DEFAULT,
+ NULL, callback, user_data, NULL);
+ }
+}
+
+void
+gtk_app_chooser_online_search_for_mimetype_async (GtkAppChooserOnline *self,
+ const gchar *content_type,
+ GtkWindow *parent,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GtkAppChooserOnlineInterface *iface;
+
+ g_return_if_fail (GTK_IS_APP_CHOOSER_ONLINE (self));
+
+ iface = GTK_APP_CHOOSER_ONLINE_GET_IFACE (self);
+
+ (* iface->search_for_mimetype_async) (self, content_type, parent, callback, user_data);
+}
+
+gboolean
+gtk_app_chooser_online_search_for_mimetype_finish (GtkAppChooserOnline *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GtkAppChooserOnlineInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_APP_CHOOSER_ONLINE (self), FALSE);
+
+ iface = GTK_APP_CHOOSER_ONLINE_GET_IFACE (self);
+
+ return ((* iface->search_for_mimetype_finish) (self, res, error));
+}
diff --git a/gtk/gtkappchooseronline.h b/gtk/gtkappchooseronline.h
new file mode 100644
index 0000000000..d0eca3b9e0
--- /dev/null
+++ b/gtk/gtkappchooseronline.h
@@ -0,0 +1,73 @@
+/*
+ * gtkappchooseronline.h: an extension point for online integration
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#ifndef __GTK_APP_CHOOSER_ONLINE_H__
+#define __GTK_APP_CHOOSER_ONLINE_H__
+
+#include <glib.h>
+
+#include <gtk/gtkwindow.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_APP_CHOOSER_ONLINE (_gtk_app_chooser_online_get_type ())
+#define GTK_APP_CHOOSER_ONLINE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_APP_CHOOSER_ONLINE, GtkAppChooserOnline))
+#define GTK_IS_APP_CHOOSER_ONLINE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_APP_CHOOSER_ONLINE))
+#define GTK_APP_CHOOSER_ONLINE_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GTK_TYPE_APP_CHOOSER_ONLINE, GtkAppChooserOnlineInterface))
+
+typedef struct _GtkAppChooserOnline GtkAppChooserOnline;
+typedef struct _GtkAppChooserOnlineInterface GtkAppChooserOnlineInterface;
+
+struct _GtkAppChooserOnlineInterface {
+ GTypeInterface g_iface;
+
+ /* Methods */
+ void (*search_for_mimetype_async) (GtkAppChooserOnline *self,
+ const gchar *content_type,
+ GtkWindow *parent,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+ gboolean (*search_for_mimetype_finish) (GtkAppChooserOnline *self,
+ GAsyncResult *res,
+ GError **error);
+};
+
+GType _gtk_app_chooser_online_get_type (void) G_GNUC_CONST;
+
+void gtk_app_chooser_online_get_default_async (GAsyncReadyCallback callback,
+ gpointer user_data);
+GtkAppChooserOnline * gtk_app_chooser_online_get_default_finish (GObject *source,
+ GAsyncResult *result);
+
+void gtk_app_chooser_online_search_for_mimetype_async (GtkAppChooserOnline *self,
+ const gchar *content_type,
+ GtkWindow *parent,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean gtk_app_chooser_online_search_for_mimetype_finish (GtkAppChooserOnline *self,
+ GAsyncResult *res,
+ GError **error);
+
+#endif /* __GTK_APP_CHOOSER_ONLINE_H__ */
diff --git a/gtk/gtkappchooseronlinepk.c b/gtk/gtkappchooseronlinepk.c
new file mode 100644
index 0000000000..c498546890
--- /dev/null
+++ b/gtk/gtkappchooseronlinepk.c
@@ -0,0 +1,263 @@
+/*
+ * gtkappchooseronlinepk.c: packagekit module for app-chooser
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#include "config.h"
+
+#include "gtkappchooseronlinepk.h"
+
+#include "gtkappchooseronline.h"
+#ifdef GDK_WINDOWING_X11
+#include "x11/gdkx.h"
+#endif
+
+#include <gio/gio.h>
+
+#define gtk_app_chooser_online_pk_get_type _gtk_app_chooser_online_pk_get_type
+static void app_chooser_online_iface_init (GtkAppChooserOnlineInterface *iface);
+static void app_chooser_online_pk_async_initable_init (GAsyncInitableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkAppChooserOnlinePk, gtk_app_chooser_online_pk,
+ G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+ app_chooser_online_pk_async_initable_init)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_APP_CHOOSER_ONLINE,
+ app_chooser_online_iface_init)
+ g_io_extension_point_implement ("gtkappchooser-online",
+ g_define_type_id,
+ "packagekit", 10));
+
+struct _GtkAppChooserOnlinePkPrivate {
+ GSimpleAsyncResult *init_result;
+ guint watch_id;
+
+ GDBusProxy *proxy;
+ GSimpleAsyncResult *result;
+ GtkWindow *parent;
+};
+
+static void
+gtk_app_chooser_online_pk_dispose (GObject *obj)
+{
+ GtkAppChooserOnlinePk *self = GTK_APP_CHOOSER_ONLINE_PK (obj);
+
+ g_clear_object (&self->priv->result);
+ g_clear_object (&self->priv->proxy);
+
+ G_OBJECT_CLASS (gtk_app_chooser_online_pk_parent_class)->dispose (obj);
+}
+
+static void
+gtk_app_chooser_online_pk_class_init (GtkAppChooserOnlinePkClass *klass)
+{
+ GObjectClass *oclass = G_OBJECT_CLASS (klass);
+
+ oclass->dispose = gtk_app_chooser_online_pk_dispose;
+
+ g_type_class_add_private (klass, sizeof (GtkAppChooserOnlinePkPrivate));
+}
+
+static void
+gtk_app_chooser_online_pk_init (GtkAppChooserOnlinePk *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_APP_CHOOSER_ONLINE_PK,
+ GtkAppChooserOnlinePkPrivate);
+}
+
+static gboolean
+pk_search_mime_finish (GtkAppChooserOnline *obj,
+ GAsyncResult *res,
+ GError **error)
+{
+ GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+static void
+install_mime_types_ready_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GtkAppChooserOnlinePk *self = user_data;
+ GDBusProxy *proxy = G_DBUS_PROXY (source);
+ GError *error = NULL;
+ GVariant *variant;
+
+ variant = g_dbus_proxy_call_finish (proxy, res, &error);
+
+ if (variant == NULL)
+ {
+ /* don't show errors if the user cancelled the installation explicitely
+ * or if PK wasn't able to find any apps
+ */
+ if (g_strcmp0 (g_dbus_error_get_remote_error (error), "org.freedesktop.PackageKit.Modify.Cancelled") != 0 &&
+ g_strcmp0 (g_dbus_error_get_remote_error (error), "org.freedesktop.PackageKit.Modify.NoPackagesFound") != 0)
+ g_simple_async_result_set_from_error (self->priv->result, error);
+
+ g_error_free (error);
+ }
+
+ g_simple_async_result_complete (self->priv->result);
+}
+
+static void
+pk_search_mime_async (GtkAppChooserOnline *obj,
+ const gchar *content_type,
+ GtkWindow *parent,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GtkAppChooserOnlinePk *self = GTK_APP_CHOOSER_ONLINE_PK (obj);
+ guint xid = 0;
+ GdkWindow *window;
+ const gchar *mime_types[2];
+
+ self->priv->result = g_simple_async_result_new (G_OBJECT (self),
+ callback, user_data,
+ gtk_app_chooser_online_search_for_mimetype_async);
+
+#ifdef GDK_WINDOWING_X11
+ window = gtk_widget_get_window (GTK_WIDGET (parent));
+ xid = GDK_WINDOW_XID (window);
+#endif
+
+ mime_types[0] = content_type;
+ mime_types[1] = NULL;
+
+ g_dbus_proxy_call (self->priv->proxy,
+ "InstallMimeTypes",
+ g_variant_new ("(u^ass)",
+ xid,
+ mime_types,
+ "hide-confirm-search"),
+ G_DBUS_CALL_FLAGS_NONE,
+ G_MAXINT, /* no timeout */
+ NULL,
+ install_mime_types_ready_cb,
+ self);
+}
+
+static void
+app_chooser_online_iface_init (GtkAppChooserOnlineInterface *iface)
+{
+ iface->search_for_mimetype_async = pk_search_mime_async;
+ iface->search_for_mimetype_finish = pk_search_mime_finish;
+}
+
+static void
+pk_proxy_created_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtkAppChooserOnlinePk *self = user_data;
+ GDBusProxy *proxy;
+
+ proxy = g_dbus_proxy_new_finish (result, NULL);
+
+ if (proxy == NULL)
+ {
+ g_simple_async_result_set_op_res_gboolean (self->priv->init_result, FALSE);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gboolean (self->priv->init_result, TRUE);
+ self->priv->proxy = proxy;
+ }
+
+ g_simple_async_result_complete (self->priv->init_result);
+ g_clear_object (&self->priv->init_result);
+}
+
+static void
+pk_appeared_cb (GDBusConnection *conn,
+ const gchar *name,
+ const gchar *owner,
+ gpointer user_data)
+{
+ GtkAppChooserOnlinePk *self = user_data;
+
+ /* create the proxy */
+ g_dbus_proxy_new (conn, 0, NULL,
+ "org.freedesktop.PackageKit",
+ "/org/freedesktop/PackageKit",
+ "org.freedesktop.PackageKit.Modify",
+ NULL,
+ pk_proxy_created_cb,
+ self);
+
+ g_bus_unwatch_name (self->priv->watch_id);
+}
+
+static void
+pk_vanished_cb (GDBusConnection *conn,
+ const gchar *name,
+ gpointer user_data)
+{
+ GtkAppChooserOnlinePk *self = user_data;
+
+ /* just return */
+ g_simple_async_result_set_op_res_gboolean (self->priv->init_result, FALSE);
+ g_simple_async_result_complete (self->priv->init_result);
+
+ g_bus_unwatch_name (self->priv->watch_id);
+
+ g_clear_object (&self->priv->init_result);
+}
+
+static gboolean
+app_chooser_online_pk_init_finish (GAsyncInitable *init,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (res));
+}
+
+static void
+app_chooser_online_pk_init_async (GAsyncInitable *init,
+ int io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GtkAppChooserOnlinePk *self = GTK_APP_CHOOSER_ONLINE_PK (init);
+
+ self->priv->init_result = g_simple_async_result_new (G_OBJECT (self),
+ callback, user_data,
+ gtk_app_chooser_online_get_default_async);
+
+ self->priv->watch_id =
+ g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.freedesktop.PackageKit",
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
+ pk_appeared_cb,
+ pk_vanished_cb,
+ self,
+ NULL);
+}
+
+static void
+app_chooser_online_pk_async_initable_init (GAsyncInitableIface *iface)
+{
+ iface->init_async = app_chooser_online_pk_init_async;
+ iface->init_finish = app_chooser_online_pk_init_finish;
+}
diff --git a/gtk/gtkappchooseronlinepk.h b/gtk/gtkappchooseronlinepk.h
new file mode 100644
index 0000000000..7d4264234f
--- /dev/null
+++ b/gtk/gtkappchooseronlinepk.h
@@ -0,0 +1,53 @@
+/*
+ * gtkappchooseronlinepk.h: an extension point for online integration
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#ifndef __GTK_APP_CHOOSER_ONLINE_PK_H__
+#define __GTK_APP_CHOOSER_ONLINE_PK_H__
+
+#include <gtk/gtkappchooseronline.h>
+#include <glib.h>
+
+#define GTK_TYPE_APP_CHOOSER_ONLINE_PK (_gtk_app_chooser_online_pk_get_type ())
+#define GTK_APP_CHOOSER_ONLINE_PK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_APP_CHOOSER_ONLINE_PK, GtkAppChooserOnlinePk))
+#define GTK_APP_CHOOSER_ONLINE_PK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_APP_CHOOSER_ONLINE_PK, GtkAppChooserOnlinePkClass))
+#define GTK_IS_APP_CHOOSER_ONLINE_PK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_APP_CHOOSER_ONLINE_PK))
+#define GTK_IS_APP_CHOOSER_ONLINE_PK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_APP_CHOOSER_ONLINE_PK))
+#define GTK_APP_CHOOSER_ONLINE_PK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_APP_CHOOSER_ONLINE_PK, GtkAppChooserOnlinePkClass))
+
+typedef struct _GtkAppChooserOnlinePk GtkAppChooserOnlinePk;
+typedef struct _GtkAppChooserOnlinePkClass GtkAppChooserOnlinePkClass;
+typedef struct _GtkAppChooserOnlinePkPrivate GtkAppChooserOnlinePkPrivate;
+
+struct _GtkAppChooserOnlinePk {
+ GObject parent;
+
+ GtkAppChooserOnlinePkPrivate *priv;
+};
+
+struct _GtkAppChooserOnlinePkClass {
+ GObjectClass parent_class;
+};
+
+GType _gtk_app_chooser_online_pk_get_type (void);
+
+#endif /* __GTK_APP_CHOOSER_ONLINE_PK_H__ */
diff --git a/gtk/gtkappchooserprivate.h b/gtk/gtkappchooserprivate.h
new file mode 100644
index 0000000000..67e0a7c1dd
--- /dev/null
+++ b/gtk/gtkappchooserprivate.h
@@ -0,0 +1,45 @@
+/*
+ * gtkappchooserprivate.h: app-chooser interface
+ *
+ * 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 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.
+ *
+ * Authors: Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#ifndef __GTK_APP_CHOOSER_PRIVATE_H__
+#define __GTK_APP_CHOOSER_PRIVATE_H__
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "gtkappchooser.h"
+#include "gtkappchooserwidget.h"
+
+typedef struct _GtkAppChooserIface GtkAppChooserIface;
+typedef GtkAppChooserIface GtkAppChooserInterface;
+
+#define GTK_APP_CHOOSER_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GTK_TYPE_APP_CHOOSER, GtkAppChooserIface))
+
+struct _GtkAppChooserIface {
+ GTypeInterface base_iface;
+
+ GAppInfo * (* get_app_info) (GtkAppChooser *object);
+ void (* refresh) (GtkAppChooser *object);
+};
+
+#endif /* __GTK_APP_CHOOSER_PRIVATE_H__ */
diff --git a/gtk/gtkappchooserwidget.c b/gtk/gtkappchooserwidget.c
new file mode 100644
index 0000000000..95510e9a35
--- /dev/null
+++ b/gtk/gtkappchooserwidget.c
@@ -0,0 +1,1503 @@
+/*
+ * 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 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.
+ *
+ * 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 <string.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+
+struct _GtkAppChooserWidgetPrivate {
+ GAppInfo *selected_app_info;
+
+ gchar *content_type;
+ gchar *default_text;
+ gboolean show_default;
+ gboolean show_recommended;
+ gboolean show_fallback;
+ gboolean show_other;
+ gboolean show_all;
+
+ GtkWidget *program_list;
+ GtkListStore *program_list_store;
+
+ GtkCellRenderer *padding_renderer;
+};
+
+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_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_object_unref (self->priv->selected_app_info);
+
+ self->priv->selected_app_info = info;
+ }
+ }
+ else
+ {
+ should_emit = TRUE;
+ self->priv->selected_app_info = 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 gboolean
+widget_button_press_event_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ GtkAppChooserWidget *self = user_data;
+
+ if (event->button == 3 && 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;
+
+ menu = gtk_menu_new ();
+
+ 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_attach_to_widget (GTK_MENU (menu), self->priv->program_list, NULL);
+ gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+ event->button, event->time);
+ }
+
+ 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/falback or not, so if one is a heading, wins */
+ if (a_heading)
+ {
+ return -1;
+ goto out;
+ }
+
+ if (b_heading)
+ {
+ return 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 (!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 ("<b>%s</b>\n%s",
+ g_app_info_get_display_name (app) != NULL ?
+ g_app_info_get_display_name (app) : "",
+ g_app_info_get_description (app) != NULL ?
+ g_app_info_get_description (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_display_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 ("<b>%s</b>\n%s",
+ g_app_info_get_display_name (app) != NULL ?
+ g_app_info_get_display_name (app) : "",
+ g_app_info_get_description (app) != NULL ?
+ g_app_info_get_description (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_display_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
+add_no_applications_label (GtkAppChooserWidget *self)
+{
+ gchar *text = NULL, *desc;
+ const gchar *string;
+ GtkTreeIter iter;
+
+ if (self->priv->default_text == NULL)
+ {
+ desc = g_content_type_get_description (self->priv->content_type);
+ string = text = g_strdup_printf (_("No applications available to open \"%s\""),
+ desc);
+ g_free (desc);
+ }
+ else
+ {
+ string = self->priv->default_text;
+ }
+
+ 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,
+ -1);
+
+ 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));
+ gtk_tree_model_get_iter_first (model, &iter);
+
+ 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)
+ {
+ 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);
+ }
+ }
+
+ if (self->priv->show_recommended || self->priv->show_all)
+ {
+ 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->show_fallback || self->priv->show_all)
+ {
+ 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));
+ }
+
+ 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)
+ add_no_applications_label (self);
+
+ gtk_app_chooser_widget_select_first (self);
+
+ if (default_app != NULL)
+ g_object_unref (default_app);
+
+ if (all_applications != NULL)
+ g_list_free_full (all_applications, g_object_unref);
+
+ if (recommended_apps != NULL)
+ g_list_free_full (recommended_apps, g_object_unref);
+
+ if (fallback_apps != NULL)
+ g_list_free_full (fallback_apps, g_object_unref);
+
+ if (exclude_apps != NULL)
+ g_list_free (exclude_apps);
+}
+
+static void
+gtk_app_chooser_widget_add_items (GtkAppChooserWidget *self)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkTreeModel *sort;
+
+ /* create list store */
+ self->priv->program_list_store = gtk_list_store_new (NUM_COLUMNS,
+ G_TYPE_APP_INFO,
+ G_TYPE_ICON,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ G_TYPE_BOOLEAN);
+ sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (self->priv->program_list_store));
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (self->priv->program_list),
+ GTK_TREE_MODEL (sort));
+ 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);
+
+ column = gtk_tree_view_column_new ();
+
+ /* initial padding */
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ g_object_set (renderer,
+ "xpad", self->priv->show_all ? 0 : 6,
+ NULL);
+ self->priv->padding_renderer = renderer;
+
+ /* heading text renderer */
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "markup", COLUMN_HEADING_TEXT,
+ "visible", COLUMN_HEADING,
+ NULL);
+ g_object_set (renderer,
+ "ypad", 6,
+ "xpad", 0,
+ "wrap-width", 350,
+ "wrap-mode", PANGO_WRAP_WORD,
+ NULL);
+
+ /* padding renderer for non-heading cells */
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_cell_data_func (column, renderer,
+ padding_cell_renderer_func,
+ NULL, NULL);
+
+ /* app icon renderer */
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "gicon", COLUMN_GICON,
+ NULL);
+ g_object_set (renderer,
+ "stock-size", GTK_ICON_SIZE_DIALOG,
+ NULL);
+
+ /* app name renderer */
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (column, renderer,
+ "markup", COLUMN_DESC,
+ NULL);
+ g_object_set (renderer,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "ellipsize-set", TRUE,
+ NULL);
+
+ gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (self->priv->program_list), column);
+
+ /* populate the widget */
+ gtk_app_chooser_widget_real_add_items (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);
+
+ g_assert (self->priv->content_type != NULL);
+
+ 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_add_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_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);
+
+ if (self->priv->selected_app_info != NULL)
+ {
+ g_object_unref (self->priv->selected_app_info);
+ self->priv->selected_app_info = NULL;
+ }
+
+ G_OBJECT_CLASS (gtk_app_chooser_widget_parent_class)->dispose (object);
+}
+
+static void
+gtk_app_chooser_widget_class_init (GtkAppChooserWidgetClass *klass)
+{
+ 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_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_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 related applications. If %FALSE, the
+ * related 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_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_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_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_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 ("application-selected",
+ GTK_TYPE_APP_CHOOSER_WIDGET,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GtkAppChooserWidgetClass, application_selected),
+ NULL, NULL,
+ _gtk_marshal_VOID__OBJECT,
+ 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 ("application-activated",
+ GTK_TYPE_APP_CHOOSER_WIDGET,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GtkAppChooserWidgetClass, application_activated),
+ NULL, NULL,
+ _gtk_marshal_VOID__OBJECT,
+ 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 ("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);
+
+ g_type_class_add_private (klass, sizeof (GtkAppChooserWidgetPrivate));
+}
+
+static void
+gtk_app_chooser_widget_init (GtkAppChooserWidget *self)
+{
+ GtkWidget *scrolled_window;
+ GtkTreeSelection *selection;
+
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_APP_CHOOSER_WIDGET,
+ GtkAppChooserWidgetPrivate);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_widget_set_size_request (scrolled_window, 400, 300);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show (scrolled_window);
+
+ self->priv->program_list = gtk_tree_view_new ();
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (self->priv->program_list),
+ FALSE);
+ gtk_container_add (GTK_CONTAINER (scrolled_window), self->priv->program_list);
+ gtk_box_pack_start (GTK_BOX (self), scrolled_window, TRUE, TRUE, 0);
+ gtk_widget_show (self->priv->program_list);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->program_list));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+ gtk_tree_selection_set_select_function (selection, gtk_app_chooser_selection_func,
+ self, NULL);
+ g_signal_connect_swapped (selection, "changed",
+ G_CALLBACK (refresh_and_emit_app_selected),
+ self);
+ g_signal_connect (self->priv->program_list, "row-activated",
+ G_CALLBACK (program_list_selection_activated),
+ self);
+ g_signal_connect (self->priv->program_list, "button-press-event",
+ G_CALLBACK (widget_button_press_event_cb),
+ 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;
+}
diff --git a/gtk/gtkappchooserwidget.h b/gtk/gtkappchooserwidget.h
new file mode 100644
index 0000000000..e12541762c
--- /dev/null
+++ b/gtk/gtkappchooserwidget.h
@@ -0,0 +1,101 @@
+/*
+ * gtkappchooserwidget.h: 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 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.
+ *
+ * Authors: Dave Camp <dave@novell.com>
+ * Alexander Larsson <alexl@redhat.com>
+ * Cosimo Cecchi <ccecchi@redhat.com>
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_APP_CHOOSER_WIDGET_H__
+#define __GTK_APP_CHOOSER_WIDGET_H__
+
+#include <gtk/gtkbox.h>
+#include <gtk/gtkmenu.h>
+#include <gio/gio.h>
+
+#define GTK_TYPE_APP_CHOOSER_WIDGET (gtk_app_chooser_widget_get_type ())
+#define GTK_APP_CHOOSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_APP_CHOOSER_WIDGET, GtkAppChooserWidget))
+#define GTK_APP_CHOOSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_APP_CHOOSER_WIDGET, GtkAppChooserWidgetClass))
+#define GTK_IS_APP_CHOOSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_APP_CHOOSER_WIDGET))
+#define GTK_IS_APP_CHOOSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_APP_CHOOSER_WIDGET))
+#define GTK_APP_CHOOSER_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_APP_CHOOSER_WIDGET, GtkAppChooserWidgetClass))
+
+typedef struct _GtkAppChooserWidget GtkAppChooserWidget;
+typedef struct _GtkAppChooserWidgetClass GtkAppChooserWidgetClass;
+typedef struct _GtkAppChooserWidgetPrivate GtkAppChooserWidgetPrivate;
+
+struct _GtkAppChooserWidget {
+ GtkBox parent;
+
+ /*< private >*/
+ GtkAppChooserWidgetPrivate *priv;
+};
+
+struct _GtkAppChooserWidgetClass {
+ GtkBoxClass parent_class;
+
+ void (* application_selected) (GtkAppChooserWidget *self,
+ GAppInfo *app_info);
+
+ void (* application_activated) (GtkAppChooserWidget *self,
+ GAppInfo *app_info);
+
+ void (* populate_popup) (GtkAppChooserWidget *self,
+ GtkMenu *menu,
+ GAppInfo *app_info);
+
+ /* padding for future class expansion */
+ gpointer padding[16];
+};
+
+GType gtk_app_chooser_widget_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gtk_app_chooser_widget_new (const gchar *content_type);
+
+void gtk_app_chooser_widget_set_show_default (GtkAppChooserWidget *self,
+ gboolean setting);
+gboolean gtk_app_chooser_widget_get_show_default (GtkAppChooserWidget *self);
+
+void gtk_app_chooser_widget_set_show_recommended (GtkAppChooserWidget *self,
+ gboolean setting);
+gboolean gtk_app_chooser_widget_get_show_recommended (GtkAppChooserWidget *self);
+
+void gtk_app_chooser_widget_set_show_fallback (GtkAppChooserWidget *self,
+ gboolean setting);
+gboolean gtk_app_chooser_widget_get_show_fallback (GtkAppChooserWidget *self);
+
+void gtk_app_chooser_widget_set_show_other (GtkAppChooserWidget *self,
+ gboolean setting);
+gboolean gtk_app_chooser_widget_get_show_other (GtkAppChooserWidget *self);
+
+void gtk_app_chooser_widget_set_show_all (GtkAppChooserWidget *self,
+ gboolean setting);
+gboolean gtk_app_chooser_widget_get_show_all (GtkAppChooserWidget *self);
+
+void gtk_app_chooser_widget_set_default_text (GtkAppChooserWidget *self,
+ const gchar *text);
+const gchar * gtk_app_chooser_widget_get_default_text (GtkAppChooserWidget *self);
+
+#endif /* __GTK_APP_CHOOSER_WIDGET_H__ */
diff --git a/gtk/gtkassistant.c b/gtk/gtkassistant.c
index ba3164296a..c3baf9cf16 100644
--- a/gtk/gtkassistant.c
+++ b/gtk/gtkassistant.c
@@ -164,6 +164,7 @@ static void gtk_assistant_get_child_property (GtkContainer *container,
GParamSpec *pspec);
static AtkObject *gtk_assistant_get_accessible (GtkWidget *widget);
+static GType gtk_assistant_accessible_factory_get_type (void);
static void gtk_assistant_buildable_interface_init (GtkBuildableIface *iface);
static GObject *gtk_assistant_buildable_get_internal_child (GtkBuildable *buildable,
@@ -2438,27 +2439,59 @@ gtk_assistant_commit (GtkAssistant *assistant)
set_assistant_buttons_state (assistant);
}
+static AtkObject *
+gtk_assistant_get_accessible (GtkWidget *widget)
+{
+ static gboolean first_time = TRUE;
+
+ if (first_time)
+ {
+ AtkObjectFactory *factory;
+ AtkRegistry *registry;
+ GType derived_type;
+ GType derived_atk_type;
+
+ /*
+ * Figure out whether accessibility is enabled by looking at the
+ * type of the accessible object which would be created for
+ * the parent type of GtkAssistant.
+ */
+ derived_type = g_type_parent (GTK_TYPE_ASSISTANT);
+
+ registry = atk_get_default_registry ();
+ factory = atk_registry_get_factory (registry, derived_type);
+ derived_atk_type = atk_object_factory_get_accessible_type (factory);
+ if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
+ atk_registry_set_factory_type (registry,
+ GTK_TYPE_ASSISTANT,
+ gtk_assistant_accessible_factory_get_type ());
+
+ first_time = FALSE;
+ }
+ return GTK_WIDGET_CLASS (gtk_assistant_parent_class)->get_accessible (widget);
+}
/* accessible implementation */
+/* dummy typedefs */
+typedef struct _GtkAssistantAccessible GtkAssistantAccessible;
+typedef struct _GtkAssistantAccessibleClass GtkAssistantAccessibleClass;
+
+ATK_DEFINE_TYPE (GtkAssistantAccessible, gtk_assistant_accessible, GTK_TYPE_ASSISTANT);
+
static gint
gtk_assistant_accessible_get_n_children (AtkObject *accessible)
{
- GtkAssistant *assistant;
GtkWidget *widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
-
- if (!widget)
+ if (widget == NULL)
return 0;
- assistant = GTK_ASSISTANT (widget);
-
- return g_list_length (assistant->priv->pages) + 1;
+ return g_list_length (GTK_ASSISTANT (accessible)->priv->pages) + 1;
}
-
static AtkObject *
gtk_assistant_accessible_ref_child (AtkObject *accessible,
gint index)
@@ -2471,7 +2504,7 @@ gtk_assistant_accessible_ref_child (AtkObject *accessible,
const gchar *title;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
- if (!widget)
+ if (widget == NULL)
return NULL;
assistant = GTK_ASSISTANT (widget);
@@ -2504,57 +2537,26 @@ gtk_assistant_accessible_ref_child (AtkObject *accessible,
}
static void
-gtk_assistant_accessible_class_init (AtkObjectClass *class)
+gtk_assistant_accessible_class_init (GtkAssistantAccessibleClass *klass)
{
- class->get_n_children = gtk_assistant_accessible_get_n_children;
- class->ref_child = gtk_assistant_accessible_ref_child;
-}
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
-static GType
-gtk_assistant_accessible_get_type (void)
-{
- static GType type = 0;
-
- if (!type)
- {
- /*
- * Figure out the size of the class and instance
- * we are deriving from
- */
- AtkObjectFactory *factory;
- GType derived_type;
- GTypeQuery query;
- GType derived_atk_type;
-
- derived_type = g_type_parent (GTK_TYPE_ASSISTANT);
- factory = atk_registry_get_factory (atk_get_default_registry (),
- derived_type);
- derived_atk_type = atk_object_factory_get_accessible_type (factory);
- g_type_query (derived_atk_type, &query);
-
- type = g_type_register_static_simple (derived_atk_type,
- I_("GtkAssistantAccessible"),
- query.class_size,
- (GClassInitFunc) gtk_assistant_accessible_class_init,
- query.instance_size,
- NULL, 0);
- }
-
- return type;
+ atk_class->get_n_children = gtk_assistant_accessible_get_n_children;
+ atk_class->ref_child = gtk_assistant_accessible_ref_child;
}
-static AtkObject *
-gtk_assistant_accessible_new (GObject *obj)
+static void
+gtk_assistant_accessible_init (GtkAssistantAccessible *self)
{
- AtkObject *accessible;
+}
- g_return_val_if_fail (GTK_IS_ASSISTANT (obj), NULL);
+/* factory */
+typedef AtkObjectFactory GtkAssistantAccessibleFactory;
+typedef AtkObjectFactoryClass GtkAssistantAccessibleFactoryClass;
- accessible = g_object_new (gtk_assistant_accessible_get_type (), NULL);
- atk_object_initialize (accessible, obj);
-
- return accessible;
-}
+G_DEFINE_TYPE (GtkAssistantAccessibleFactory,
+ gtk_assistant_accessible_factory,
+ ATK_TYPE_OBJECT_FACTORY);
static GType
gtk_assistant_accessible_factory_get_accessible_type (void)
@@ -2565,7 +2567,12 @@ gtk_assistant_accessible_factory_get_accessible_type (void)
static AtkObject*
gtk_assistant_accessible_factory_create_accessible (GObject *obj)
{
- return gtk_assistant_accessible_new (obj);
+ AtkObject *accessible;
+
+ accessible = g_object_new (gtk_assistant_accessible_get_type (), NULL);
+ atk_object_initialize (accessible, obj);
+
+ return accessible;
}
static void
@@ -2575,59 +2582,12 @@ gtk_assistant_accessible_factory_class_init (AtkObjectFactoryClass *class)
class->get_accessible_type = gtk_assistant_accessible_factory_get_accessible_type;
}
-static GType
-gtk_assistant_accessible_factory_get_type (void)
-{
- static GType type = 0;
-
- if (!type)
- {
- type = g_type_register_static_simple (ATK_TYPE_OBJECT_FACTORY,
- I_("GtkAssistantAccessibleFactory"),
- sizeof (AtkObjectFactoryClass),
- (GClassInitFunc) gtk_assistant_accessible_factory_class_init,
- sizeof (AtkObjectFactory),
- NULL, 0);
- }
-
- return type;
-}
-
-static AtkObject *
-gtk_assistant_get_accessible (GtkWidget *widget)
+static void
+gtk_assistant_accessible_factory_init (AtkObjectFactory *factory)
{
- static gboolean first_time = TRUE;
-
- if (first_time)
- {
- AtkObjectFactory *factory;
- AtkRegistry *registry;
- GType derived_type;
- GType derived_atk_type;
-
- /*
- * Figure out whether accessibility is enabled by looking at the
- * type of the accessible object which would be created for
- * the parent type of GtkAssistant.
- */
- derived_type = g_type_parent (GTK_TYPE_ASSISTANT);
-
- registry = atk_get_default_registry ();
- factory = atk_registry_get_factory (registry,
- derived_type);
- derived_atk_type = atk_object_factory_get_accessible_type (factory);
- if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
- {
- atk_registry_set_factory_type (registry,
- GTK_TYPE_ASSISTANT,
- gtk_assistant_accessible_factory_get_type ());
- }
- first_time = FALSE;
- }
-
- return GTK_WIDGET_CLASS (gtk_assistant_parent_class)->get_accessible (widget);
}
+/* buildable implementation */
static GtkBuildableIface *parent_buildable_iface;
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index 7ed026c0a7..97b0053da3 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -1563,7 +1563,7 @@ gtk_builder_value_from_string_type (GtkBuilder *builder,
{
GdkRGBA rgba = { 0 };
- if (gdk_rgba_parse (string, &rgba))
+ if (gdk_rgba_parse (&rgba, string))
g_value_set_boxed (value, &rgba);
else
{
diff --git a/gtk/gtkcellrenderer.c b/gtk/gtkcellrenderer.c
index dcbb668948..96465a84dc 100644
--- a/gtk/gtkcellrenderer.c
+++ b/gtk/gtkcellrenderer.c
@@ -539,7 +539,7 @@ gtk_cell_renderer_set_property (GObject *object,
if (!g_value_get_string (value))
set_cell_bg_color (cell, NULL);
- else if (gdk_rgba_parse (g_value_get_string (value), &rgba))
+ else if (gdk_rgba_parse (&rgba, g_value_get_string (value)))
set_cell_bg_color (cell, &rgba);
else
g_warning ("Don't know color `%s'", g_value_get_string (value));
diff --git a/gtk/gtkcellrenderertext.c b/gtk/gtkcellrenderertext.c
index 120100a52e..31ddd304c7 100644
--- a/gtk/gtkcellrenderertext.c
+++ b/gtk/gtkcellrenderertext.c
@@ -1201,7 +1201,7 @@ gtk_cell_renderer_text_set_property (GObject *object,
if (!g_value_get_string (value))
set_bg_color (celltext, NULL); /* reset to background_set to FALSE */
- else if (gdk_rgba_parse (g_value_get_string (value), &rgba))
+ else if (gdk_rgba_parse (&rgba, g_value_get_string (value)))
set_bg_color (celltext, &rgba);
else
g_warning ("Don't know color `%s'", g_value_get_string (value));
@@ -1216,7 +1216,7 @@ gtk_cell_renderer_text_set_property (GObject *object,
if (!g_value_get_string (value))
set_fg_color (celltext, NULL); /* reset to foreground_set to FALSE */
- else if (gdk_rgba_parse (g_value_get_string (value), &rgba))
+ else if (gdk_rgba_parse (&rgba, g_value_get_string (value)))
set_fg_color (celltext, &rgba);
else
g_warning ("Don't know color `%s'", g_value_get_string (value));
diff --git a/gtk/gtkcombobox.c b/gtk/gtkcombobox.c
index 0a922fa023..ebdd7f5e1d 100644
--- a/gtk/gtkcombobox.c
+++ b/gtk/gtkcombobox.c
@@ -143,6 +143,8 @@ struct _GtkComboBoxPrivate
gint text_column;
GtkCellRenderer *text_renderer;
+ gint id_column;
+
GSList *cells;
guint popup_in_progress : 1;
@@ -245,7 +247,9 @@ enum {
PROP_EDITING_CANCELED,
PROP_HAS_ENTRY,
PROP_ENTRY_TEXT_COLUMN,
- PROP_POPUP_FIXED_WIDTH
+ PROP_POPUP_FIXED_WIDTH,
+ PROP_ID_COLUMN,
+ PROP_ACTIVE_ID
};
static guint combo_box_signals[LAST_SIGNAL] = {0,};
@@ -949,6 +953,38 @@ gtk_combo_box_class_init (GtkComboBoxClass *klass)
GTK_PARAM_READWRITE));
/**
+ * GtkComboBox:id-column:
+ *
+ * The column in the combo box's model that provides string
+ * IDs for the values in the model, if != -1.
+ *
+ * Since: 3.0
+ */
+ g_object_class_install_property (object_class,
+ PROP_ID_COLUMN,
+ g_param_spec_int ("id-column",
+ P_("ID Column"),
+ P_("The column in the combo box's model that provides "
+ "string IDs for the values in the model"),
+ -1, G_MAXINT, -1,
+ GTK_PARAM_READWRITE));
+
+ /**
+ * GtkComboBox:active-id:
+ *
+ * The value of the ID column of the active row.
+ *
+ * Since: 3.0
+ */
+ g_object_class_install_property (object_class,
+ PROP_ACTIVE_ID,
+ g_param_spec_string ("active-id",
+ P_("Active id"),
+ P_("The value of the id column "
+ "for the active row"),
+ NULL, GTK_PARAM_READWRITE));
+
+ /**
* GtkComboBox:popup-fixed-width:
*
* Whether the popup's width should be a fixed width matching the
@@ -1077,6 +1113,7 @@ gtk_combo_box_init (GtkComboBox *combo_box)
priv->text_column = -1;
priv->text_renderer = NULL;
+ priv->id_column = -1;
gtk_combo_box_check_appearance (combo_box);
}
@@ -1168,6 +1205,14 @@ gtk_combo_box_set_property (GObject *object,
gtk_combo_box_set_entry_text_column (combo_box, g_value_get_int (value));
break;
+ case PROP_ID_COLUMN:
+ gtk_combo_box_set_id_column (combo_box, g_value_get_int (value));
+ break;
+
+ case PROP_ACTIVE_ID:
+ gtk_combo_box_set_active_id (combo_box, g_value_get_string (value));
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1245,6 +1290,14 @@ gtk_combo_box_get_property (GObject *object,
g_value_set_int (value, priv->text_column);
break;
+ case PROP_ID_COLUMN:
+ g_value_set_int (value, priv->id_column);
+ break;
+
+ case PROP_ACTIVE_ID:
+ g_value_set_string (value, gtk_combo_box_get_active_id (combo_box));
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -4879,6 +4932,7 @@ gtk_combo_box_new_with_model (GtkTreeModel *model)
/**
* gtk_combo_box_new_with_model_and_entry:
+ * @model: A #GtkTreeModel
*
* Creates a new empty #GtkComboBox with an entry
* and with the model initialized to @model.
@@ -5199,6 +5253,8 @@ gtk_combo_box_set_active_internal (GtkComboBox *combo_box,
g_signal_emit (combo_box, combo_box_signals[CHANGED], 0);
g_object_notify (G_OBJECT (combo_box), "active");
+ if (combo_box->priv->id_column >= 0)
+ g_object_notify (G_OBJECT (combo_box), "active-id");
}
@@ -6587,3 +6643,157 @@ gtk_combo_box_get_preferred_height_for_width (GtkWidget *widget,
if (natural_size)
*natural_size = nat_height;
}
+
+/**
+ * gtk_combo_box_set_id_column:
+ * @combo_box: A #GtkComboBox
+ * @id_column: A column in @model to get string IDs for values from
+ *
+ * Sets the model column which @combo_box should use to get string IDs
+ * for values from. The column @id_column in the model of @combo_box
+ * must be of type %G_TYPE_STRING.
+ *
+ * Since: 3.0
+ */
+void
+gtk_combo_box_set_id_column (GtkComboBox *combo_box,
+ gint id_column)
+{
+ GtkComboBoxPrivate *priv = combo_box->priv;
+ GtkTreeModel *model;
+
+ g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
+
+ if (id_column != priv->id_column)
+ {
+ model = gtk_combo_box_get_model (combo_box);
+
+ g_return_if_fail (id_column >= 0);
+ g_return_if_fail (model == NULL ||
+ id_column < gtk_tree_model_get_n_columns (model));
+
+ priv->id_column = id_column;
+
+ g_object_notify (G_OBJECT (combo_box), "id-column");
+ g_object_notify (G_OBJECT (combo_box), "active-id");
+ }
+}
+
+/**
+ * gtk_combo_box_get_id_column:
+ * @combo_box: A #GtkComboBox
+ *
+ * Returns the column which @combo_box is using to get string IDs
+ * for values from.
+ *
+ * Return value: A column in the data source model of @combo_box.
+ *
+ * Since: 3.0
+ */
+gint
+gtk_combo_box_get_id_column (GtkComboBox *combo_box)
+{
+ g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), 0);
+
+ return combo_box->priv->id_column;
+}
+
+/**
+ * gtk_combo_box_get_active_id:
+ * @combo_box: a #GtkComboBox
+ *
+ * Returns the ID of the active row of @combo_box. This value is taken
+ * from the active row and the column specified by the 'id-column'
+ * property of @combo_box (see gtk_combo_box_set_id_column()).
+ *
+ * The returned value is an interned string which means that you can
+ * compare the pointer by value to other interned strings and that you
+ * must not free it.
+ *
+ * If the 'id-column' property of @combo_box is not set or if no row is
+ * selected then %NULL is returned.
+ *
+ * Return value: the ID of the active row, or %NULL
+ *
+ * Since: 3.0
+ **/
+const gchar *
+gtk_combo_box_get_active_id (GtkComboBox *combo_box)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint column;
+
+ g_return_val_if_fail (GTK_IS_COMBO_BOX (combo_box), 0);
+
+ column = combo_box->priv->id_column;
+
+ if (column < 0)
+ return NULL;
+
+ model = gtk_combo_box_get_model (combo_box);
+ g_return_val_if_fail (gtk_tree_model_get_column_type (model, column) ==
+ G_TYPE_STRING, NULL);
+
+ if (gtk_combo_box_get_active_iter (combo_box, &iter))
+ {
+ const gchar *interned;
+ gchar *id;
+
+ gtk_tree_model_get (model, &iter, column, &id, -1);
+ interned = g_intern_string (id);
+ g_free (id);
+
+ return interned;
+ }
+
+ return NULL;
+}
+
+/**
+ * gtk_combo_box_set_active_id:
+ * @combo_box: a #GtkComboBox
+ * @active_id: the ID of the row to select
+ *
+ * Changes the active row of @combo_box to the one that has an ID equal to @id.
+ *
+ * If the 'id-column' property of @combo_box is unset or if no row has
+ * the given ID then nothing happens.
+ *
+ * Since: 3.0
+ **/
+void
+gtk_combo_box_set_active_id (GtkComboBox *combo_box,
+ const gchar *active_id)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint column;
+
+ g_return_if_fail (GTK_IS_COMBO_BOX (combo_box));
+
+ column = combo_box->priv->id_column;
+
+ if (column < 0)
+ return;
+
+ model = gtk_combo_box_get_model (combo_box);
+ g_return_if_fail (gtk_tree_model_get_column_type (model, column) ==
+ G_TYPE_STRING);
+
+ if (gtk_tree_model_get_iter_first (model, &iter))
+ do {
+ gboolean match;
+ gchar *id;
+
+ gtk_tree_model_get (model, &iter, column, &id, -1);
+ match = strcmp (id, active_id) == 0;
+ g_free (id);
+
+ if (match)
+ {
+ gtk_combo_box_set_active_iter (combo_box, &iter);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+}
diff --git a/gtk/gtkcombobox.h b/gtk/gtkcombobox.h
index c8931eb3b3..1c674b9c06 100644
--- a/gtk/gtkcombobox.h
+++ b/gtk/gtkcombobox.h
@@ -134,6 +134,12 @@ void gtk_combo_box_popup_for_device (GtkComboBox *combo_box,
void gtk_combo_box_popdown (GtkComboBox *combo_box);
AtkObject* gtk_combo_box_get_popup_accessible (GtkComboBox *combo_box);
+gint gtk_combo_box_get_id_column (GtkComboBox *combo_box);
+void gtk_combo_box_set_id_column (GtkComboBox *combo_box,
+ gint id_column);
+const gchar * gtk_combo_box_get_active_id (GtkComboBox *combo_box);
+void gtk_combo_box_set_active_id (GtkComboBox *combo_box,
+ const gchar *active_id);
G_END_DECLS
diff --git a/gtk/gtkcomboboxtext.c b/gtk/gtkcomboboxtext.c
index d36d6dc25e..c7b82d1063 100644
--- a/gtk/gtkcomboboxtext.c
+++ b/gtk/gtkcomboboxtext.c
@@ -72,7 +72,7 @@ gtk_combo_box_text_init (GtkComboBoxText *combo_box)
{
GtkListStore *store;
- store = gtk_list_store_new (1, G_TYPE_STRING);
+ store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (store));
g_object_unref (store);
}
@@ -102,6 +102,7 @@ gtk_combo_box_text_new (void)
{
return g_object_new (GTK_TYPE_COMBO_BOX_TEXT,
"entry-text-column", 0,
+ "id-column", 1,
NULL);
}
@@ -121,6 +122,7 @@ gtk_combo_box_text_new_with_entry (void)
return g_object_new (GTK_TYPE_COMBO_BOX_TEXT,
"has-entry", TRUE,
"entry-text-column", 0,
+ "id-column", 1,
NULL);
}
@@ -131,13 +133,16 @@ gtk_combo_box_text_new_with_entry (void)
*
* Appends @text to the list of strings stored in @combo_box.
*
+ * This is the same as calling gtk_combo_box_text_insert_text() with a
+ * position of -1.
+ *
* Since: 2.24
*/
void
gtk_combo_box_text_append_text (GtkComboBoxText *combo_box,
const gchar *text)
{
- gtk_combo_box_text_insert_text (combo_box, G_MAXINT, text);
+ gtk_combo_box_text_insert (combo_box, -1, NULL, text);
}
/**
@@ -147,13 +152,16 @@ gtk_combo_box_text_append_text (GtkComboBoxText *combo_box,
*
* Prepends @text to the list of strings stored in @combo_box.
*
+ * This is the same as calling gtk_combo_box_text_insert_text() with a
+ * position of 0.
+ *
* Since: 2.24
*/
void
gtk_combo_box_text_prepend_text (GtkComboBoxText *combo_box,
const gchar *text)
{
- gtk_combo_box_text_insert_text (combo_box, 0, text);
+ gtk_combo_box_text_insert (combo_box, 0, NULL, text);
}
/**
@@ -164,6 +172,11 @@ gtk_combo_box_text_prepend_text (GtkComboBoxText *combo_box,
*
* Inserts @text at @position in the list of strings stored in @combo_box.
*
+ * If @position is negative then @text is appended.
+ *
+ * This is the same as calling gtk_combo_box_text_insert() with a %NULL
+ * ID string.
+ *
* Since: 2.24
*/
void
@@ -171,15 +184,84 @@ gtk_combo_box_text_insert_text (GtkComboBoxText *combo_box,
gint position,
const gchar *text)
{
+ gtk_combo_box_text_insert (combo_box, position, NULL, text);
+}
+
+/**
+ * gtk_combo_box_text_append:
+ * @combo_box: A #GtkComboBoxText
+ * @text: A string
+ *
+ * Appends @text to the list of strings stored in @combo_box. If @id is
+ * non-%NULL then it is used as the ID of the row.
+ *
+ * This is the same as calling gtk_combo_box_text_insert() with a
+ * position of -1.
+ *
+ * Since: 2.24
+ */
+void
+gtk_combo_box_text_append (GtkComboBoxText *combo_box,
+ const gchar *id,
+ const gchar *text)
+{
+ gtk_combo_box_text_insert (combo_box, -1, id, text);
+}
+
+/**
+ * gtk_combo_box_text_prepend:
+ * @combo_box: A #GtkComboBox
+ * @text: A string
+ *
+ * Prepends @text to the list of strings stored in @combo_box. If @id
+ * is non-%NULL then it is used as the ID of the row.
+ *
+ * This is the same as calling gtk_combo_box_text_insert() with a
+ * position of 0.
+ *
+ * Since: 2.24
+ */
+void
+gtk_combo_box_text_prepend (GtkComboBoxText *combo_box,
+ const gchar *id,
+ const gchar *text)
+{
+ gtk_combo_box_text_insert (combo_box, 0, id, text);
+}
+
+
+/**
+ * gtk_combo_box_text_insert:
+ * @combo_box: A #GtkComboBoxText
+ * @position: An index to insert @text
+ * @id: a string ID for this value, or %NULL
+ * @text: A string to display
+ *
+ * Inserts @text at @position in the list of strings stored in @combo_box.
+ * If @id is non-%NULL then it is used as the ID of the row. See
+ * #GtkComboBox::id-column.
+ *
+ * If @position is negative then @text is appended.
+ *
+ * Since: 3.0
+ */
+void
+gtk_combo_box_text_insert (GtkComboBoxText *combo_box,
+ gint position,
+ const gchar *id,
+ const gchar *text)
+{
GtkListStore *store;
GtkTreeIter iter;
gint text_column;
gint column_type;
g_return_if_fail (GTK_IS_COMBO_BOX_TEXT (combo_box));
- g_return_if_fail (position >= 0);
g_return_if_fail (text != NULL);
+ if (position < 0)
+ position = G_MAXINT;
+
store = GTK_LIST_STORE (gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)));
g_return_if_fail (GTK_IS_LIST_STORE (store));
text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (combo_box));
@@ -188,6 +270,17 @@ gtk_combo_box_text_insert_text (GtkComboBoxText *combo_box,
gtk_list_store_insert (store, &iter, position);
gtk_list_store_set (store, &iter, text_column, text, -1);
+
+ if (id != NULL)
+ {
+ gint id_column;
+
+ id_column = gtk_combo_box_get_id_column (GTK_COMBO_BOX (combo_box));
+ column_type = gtk_tree_model_get_column_type (GTK_TREE_MODEL (store), id_column);
+ g_return_if_fail (column_type == G_TYPE_STRING);
+
+ gtk_list_store_set (store, &iter, id_column, id, -1);
+ }
}
/**
diff --git a/gtk/gtkcomboboxtext.h b/gtk/gtkcomboboxtext.h
index ae08d2d40e..d1c362f30c 100644
--- a/gtk/gtkcomboboxtext.h
+++ b/gtk/gtkcomboboxtext.h
@@ -72,6 +72,16 @@ void gtk_combo_box_text_remove (GtkComboBoxText *combo_box
void gtk_combo_box_text_remove_all (GtkComboBoxText *combo_box);
gchar *gtk_combo_box_text_get_active_text (GtkComboBoxText *combo_box);
+void gtk_combo_box_text_insert (GtkComboBoxText *combo_box,
+ gint position,
+ const gchar *id,
+ const gchar *text);
+void gtk_combo_box_text_append (GtkComboBoxText *combo_box,
+ const gchar *id,
+ const gchar *text);
+void gtk_combo_box_text_prepend (GtkComboBoxText *combo_box,
+ const gchar *id,
+ const gchar *text);
G_END_DECLS
diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h
index 9dbab6dff1..e7dd7f56c9 100644
--- a/gtk/gtkenums.h
+++ b/gtk/gtkenums.h
@@ -557,7 +557,6 @@ typedef enum
GTK_SCROLL_NATURAL
} GtkScrollablePolicy;
-
G_END_DECLS
diff --git a/gtk/gtkiconview.c b/gtk/gtkiconview.c
index c080bba048..8a3defdb80 100644
--- a/gtk/gtkiconview.c
+++ b/gtk/gtkiconview.c
@@ -317,7 +317,7 @@ static void gtk_icon_view_set_hadjustment (GtkIco
GtkAdjustment *adjustment);
static void gtk_icon_view_set_vadjustment (GtkIconView *icon_view,
GtkAdjustment *adjustment);
-static void gtk_icon_view_accessible_set_adjustment (GtkIconView *icon_view,
+static void gtk_icon_view_accessible_set_adjustment (AtkObject *accessible,
GtkOrientation orientation,
GtkAdjustment *adjustment);
static void gtk_icon_view_adjustment_changed (GtkAdjustment *adjustment,
@@ -2707,6 +2707,7 @@ gtk_icon_view_set_hadjustment (GtkIconView *icon_view,
GtkAdjustment *adjustment)
{
GtkIconViewPrivate *priv = icon_view->priv;
+ AtkObject *atk_obj;
if (adjustment && priv->hadjustment == adjustment)
return;
@@ -2728,9 +2729,10 @@ gtk_icon_view_set_hadjustment (GtkIconView *icon_view,
priv->hadjustment = g_object_ref_sink (adjustment);
gtk_icon_view_set_hadjustment_values (icon_view);
- gtk_icon_view_accessible_set_adjustment (icon_view,
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_view));
+ gtk_icon_view_accessible_set_adjustment (atk_obj,
GTK_ORIENTATION_HORIZONTAL,
- priv->hadjustment);
+ adjustment);
g_object_notify (G_OBJECT (icon_view), "hadjustment");
}
@@ -2740,6 +2742,7 @@ gtk_icon_view_set_vadjustment (GtkIconView *icon_view,
GtkAdjustment *adjustment)
{
GtkIconViewPrivate *priv = icon_view->priv;
+ AtkObject *atk_obj;
if (adjustment && priv->vadjustment == adjustment)
return;
@@ -2761,9 +2764,10 @@ gtk_icon_view_set_vadjustment (GtkIconView *icon_view,
priv->vadjustment = g_object_ref_sink (adjustment);
gtk_icon_view_set_vadjustment_values (icon_view);
- gtk_icon_view_accessible_set_adjustment (icon_view,
+ atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_view));
+ gtk_icon_view_accessible_set_adjustment (atk_obj,
GTK_ORIENTATION_VERTICAL,
- priv->vadjustment);
+ adjustment);
g_object_notify (G_OBJECT (icon_view), "vadjustment");
}
@@ -9207,32 +9211,21 @@ gtk_icon_view_accessible_traverse_items (GtkIconViewAccessible *view,
}
static void
-gtk_icon_view_accessible_adjustment_changed (GtkAdjustment *adjustment,
- GtkIconView *icon_view)
+gtk_icon_view_accessible_adjustment_changed (GtkAdjustment *adjustment,
+ GtkIconViewAccessible *view)
{
- AtkObject *obj;
- GtkIconViewAccessible *view;
-
- /*
- * The scrollbars have changed
- */
- obj = gtk_widget_get_accessible (GTK_WIDGET (icon_view));
- view = GTK_ICON_VIEW_ACCESSIBLE (obj);
-
gtk_icon_view_accessible_traverse_items (view, NULL);
}
static void
-gtk_icon_view_accessible_set_adjustment (GtkIconView *icon_view,
+gtk_icon_view_accessible_set_adjustment (AtkObject *accessible,
GtkOrientation orientation,
GtkAdjustment *adjustment)
{
- AtkObject *atk_obj;
GtkIconViewAccessiblePrivate *priv;
GtkAdjustment **old_adj_ptr;
- atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_view));
- priv = gtk_icon_view_accessible_get_priv (atk_obj);
+ priv = gtk_icon_view_accessible_get_priv (accessible);
/* Adjustments are set for the first time in constructor and priv is not
* initialized at that time, so skip this first setting. */
@@ -9261,7 +9254,7 @@ gtk_icon_view_accessible_set_adjustment (GtkIconView *icon_view,
(gpointer *)&priv->old_hadj);
g_signal_handlers_disconnect_by_func (*old_adj_ptr,
gtk_icon_view_accessible_adjustment_changed,
- icon_view);
+ accessible);
}
/* Connect signal */
@@ -9269,7 +9262,7 @@ gtk_icon_view_accessible_set_adjustment (GtkIconView *icon_view,
g_object_add_weak_pointer (G_OBJECT (adjustment), (gpointer *)old_adj_ptr);
g_signal_connect (adjustment, "value-changed",
G_CALLBACK (gtk_icon_view_accessible_adjustment_changed),
- icon_view);
+ accessible);
}
static void
@@ -9572,11 +9565,11 @@ gtk_icon_view_accessible_initialize (AtkObject *accessible,
icon_view = GTK_ICON_VIEW (data);
if (icon_view->priv->hadjustment)
- gtk_icon_view_accessible_set_adjustment (icon_view,
+ gtk_icon_view_accessible_set_adjustment (accessible,
GTK_ORIENTATION_HORIZONTAL,
icon_view->priv->hadjustment);
if (icon_view->priv->vadjustment)
- gtk_icon_view_accessible_set_adjustment (icon_view,
+ gtk_icon_view_accessible_set_adjustment (accessible,
GTK_ORIENTATION_VERTICAL,
icon_view->priv->vadjustment);
g_signal_connect (data,
@@ -9623,7 +9616,7 @@ gtk_icon_view_accessible_destroyed (GtkWidget *widget,
g_signal_handlers_disconnect_by_func (priv->old_hadj,
(gpointer) gtk_icon_view_accessible_adjustment_changed,
- widget);
+ accessible);
priv->old_hadj = NULL;
}
if (priv->old_vadj)
@@ -9633,7 +9626,7 @@ gtk_icon_view_accessible_destroyed (GtkWidget *widget,
g_signal_handlers_disconnect_by_func (priv->old_vadj,
(gpointer) gtk_icon_view_accessible_adjustment_changed,
- widget);
+ accessible);
priv->old_vadj = NULL;
}
}
diff --git a/gtk/gtkrange.c b/gtk/gtkrange.c
index 6cbbdd64c7..27e8a7424e 100644
--- a/gtk/gtkrange.c
+++ b/gtk/gtkrange.c
@@ -1280,7 +1280,7 @@ gtk_range_set_range (GtkRange *range,
gdouble value;
g_return_if_fail (GTK_IS_RANGE (range));
- g_return_if_fail (min < max);
+ g_return_if_fail (min <= max);
priv = range->priv;
@@ -2016,11 +2016,16 @@ gtk_range_draw (GtkWidget *widget,
gint focus_line_width = 0;
gint focus_padding = 0;
gboolean touchscreen;
+ gboolean draw_trough = TRUE;
g_object_get (gtk_widget_get_settings (widget),
"gtk-touchscreen-mode", &touchscreen,
NULL);
+ if (GTK_IS_SCALE (widget) &&
+ priv->adjustment->upper == priv->adjustment->lower)
+ draw_trough = FALSE;
+
style = gtk_widget_get_style (widget);
if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
gtk_widget_style_get (GTK_WIDGET (range),
@@ -2112,6 +2117,7 @@ gtk_range_draw (GtkWidget *widget,
}
}
+ if (draw_trough)
{
gint trough_change_pos_x = width;
gint trough_change_pos_y = height;
@@ -2147,6 +2153,17 @@ gtk_range_draw (GtkWidget *widget,
width - trough_change_pos_x,
height - trough_change_pos_y);
}
+ else
+ {
+ gtk_paint_box (style, cr,
+ sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
+ GTK_SHADOW_IN,
+ GTK_WIDGET (range),
+ "trough-upper",
+ x, y,
+ width,
+ height);
+ }
if (priv->show_fill_level &&
priv->adjustment->upper - priv->adjustment->page_size -
@@ -2236,6 +2253,7 @@ gtk_range_draw (GtkWidget *widget,
gdk_cairo_rectangle (cr, &priv->slider);
cairo_clip (cr);
+ if (draw_trough)
{
gtk_paint_slider (style,
cr,
diff --git a/gtk/gtkscale.c b/gtk/gtkscale.c
index 51a4111e3f..cd00b2ea2e 100644
--- a/gtk/gtkscale.c
+++ b/gtk/gtkscale.c
@@ -55,6 +55,11 @@
* To detect changes to the value, you would normally use the
* #GtkRange::value-changed signal.
*
+ * Note that using the same upper and lower bounds for the #GtkScale (through
+ * the #GtkRange methods) will hide the slider itself. This is useful for
+ * applications that want to show an undeterminate value on the scale, without
+ * changing the layout of the application (such as movie or music players).
+ *
* <refsect2 id="GtkScale-BUILDER-UI"><title>GtkScale as GtkBuildable</title>
* GtkScale supports a custom &lt;marks&gt; element, which
* can contain multiple &lt;mark&gt; elements. The "value" and "position"
diff --git a/gtk/gtkselection.h b/gtk/gtkselection.h
index 4fa30a37d9..eddab0f4ad 100644
--- a/gtk/gtkselection.h
+++ b/gtk/gtkselection.h
@@ -68,9 +68,9 @@ struct _GtkSelectionData
struct _GtkTargetEntry
{
- const gchar *target;
- guint flags;
- guint info;
+ gchar *target;
+ guint flags;
+ guint info;
};
/* These structures not public, and are here only for the convenience of
diff --git a/gtk/gtkspinner.c b/gtk/gtkspinner.c
index af96fe9d50..2af87b061f 100644
--- a/gtk/gtkspinner.c
+++ b/gtk/gtkspinner.c
@@ -293,6 +293,7 @@ gtk_spinner_add_timeout (GtkSpinner *spinner)
priv = spinner->priv;
+ g_assert (priv->timeout == 0);
priv->timeout = gdk_threads_add_timeout ((guint) priv->cycle_duration / priv->num_steps, gtk_spinner_timeout, spinner);
}
@@ -315,7 +316,7 @@ gtk_spinner_map (GtkWidget *widget)
GTK_WIDGET_CLASS (gtk_spinner_parent_class)->map (widget);
- if (priv->active)
+ if (priv->active && priv->timeout == 0)
gtk_spinner_add_timeout (spinner);
}
@@ -364,20 +365,22 @@ gtk_spinner_dispose (GObject *gobject)
}
static void
-gtk_spinner_set_active (GtkSpinner *spinner, gboolean active)
+gtk_spinner_set_active (GtkSpinner *spinner,
+ gboolean active)
{
- GtkSpinnerPrivate *priv;
-
- active = active != FALSE;
+ GtkSpinnerPrivate *priv = spinner->priv;
- priv = GTK_SPINNER (spinner)->priv;
+ active = !!active;
if (priv->active != active)
{
priv->active = active;
+
g_object_notify (G_OBJECT (spinner), "active");
- if (active && gtk_widget_get_realized (GTK_WIDGET (spinner)) && priv->timeout == 0)
+ if (active &&
+ gtk_widget_get_realized (GTK_WIDGET (spinner)) &&
+ priv->timeout == 0)
{
gtk_spinner_add_timeout (spinner);
}
@@ -388,84 +391,7 @@ gtk_spinner_set_active (GtkSpinner *spinner, gboolean active)
}
}
-static GType
-gtk_spinner_accessible_factory_get_accessible_type (void)
-{
- return gtk_spinner_accessible_get_type ();
-}
-
-static AtkObject *
-gtk_spinner_accessible_new (GObject *obj)
-{
- AtkObject *accessible;
-
- g_return_val_if_fail (GTK_IS_WIDGET (obj), NULL);
-
- accessible = g_object_new (gtk_spinner_accessible_get_type (), NULL);
- atk_object_initialize (accessible, obj);
-
- return accessible;
-}
-
-static AtkObject*
-gtk_spinner_accessible_factory_create_accessible (GObject *obj)
-{
- return gtk_spinner_accessible_new (obj);
-}
-
-static void
-gtk_spinner_accessible_factory_class_init (AtkObjectFactoryClass *klass)
-{
- klass->create_accessible = gtk_spinner_accessible_factory_create_accessible;
- klass->get_accessible_type = gtk_spinner_accessible_factory_get_accessible_type;
-}
-
-static GType
-gtk_spinner_accessible_factory_get_type (void)
-{
- static GType type = 0;
-
- if (!type)
- {
- const GTypeInfo tinfo =
- {
- sizeof (AtkObjectFactoryClass),
- NULL, /* base_init */
- NULL, /* base_finalize */
- (GClassInitFunc) gtk_spinner_accessible_factory_class_init,
- NULL, /* class_finalize */
- NULL, /* class_data */
- sizeof (AtkObjectFactory),
- 0, /* n_preallocs */
- NULL, NULL
- };
-
- type = g_type_register_static (ATK_TYPE_OBJECT_FACTORY,
- I_("GtkSpinnerAccessibleFactory"),
- &tinfo, 0);
- }
- return type;
-}
-
-static AtkObjectClass *a11y_parent_class = NULL;
-
-static void
-gtk_spinner_accessible_initialize (AtkObject *accessible,
- gpointer widget)
-{
- atk_object_set_name (accessible, C_("throbbing progress animation widget", "Spinner"));
- atk_object_set_description (accessible, _("Provides visual indication of progress"));
-
- a11y_parent_class->initialize (accessible, widget);
-}
-
-static void
-gtk_spinner_accessible_class_init (AtkObjectClass *klass)
-{
- a11y_parent_class = g_type_class_peek_parent (klass);
-
- klass->initialize = gtk_spinner_accessible_initialize;
-}
+/* accessible implementation */
static void
gtk_spinner_accessible_image_get_size (AtkImage *image,
@@ -476,7 +402,7 @@ gtk_spinner_accessible_image_get_size (AtkImage *image,
GtkWidget *widget;
widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (image));
- if (!widget)
+ if (widget == NULL)
{
*width = *height = 0;
}
@@ -489,62 +415,79 @@ gtk_spinner_accessible_image_get_size (AtkImage *image,
}
static void
-gtk_spinner_accessible_image_interface_init (AtkImageIface *iface)
+gtk_spinner_accessible_image_iface_init (AtkImageIface *iface)
{
iface->get_image_size = gtk_spinner_accessible_image_get_size;
}
-static GType
-gtk_spinner_accessible_get_type (void)
+/* dummy typedef */
+typedef struct _GtkSpinnerAccessible GtkSpinnerAccessible;
+typedef struct _GtkSpinnerAccessibleClass GtkSpinnerAccessibleClass;
+
+ATK_DEFINE_TYPE_WITH_CODE (GtkSpinnerAccessible,
+ gtk_spinner_accessible,
+ GTK_TYPE_IMAGE,
+ G_IMPLEMENT_INTERFACE (ATK_TYPE_IMAGE,
+ gtk_spinner_accessible_image_iface_init));
+
+static void
+gtk_spinner_accessible_initialize (AtkObject *accessible,
+ gpointer widget)
{
- static GType type = 0;
+ ATK_OBJECT_CLASS (gtk_spinner_accessible_parent_class)->initialize (accessible, widget);
- /* Action interface
- Name etc. ... */
- if (G_UNLIKELY (type == 0))
- {
- const GInterfaceInfo atk_image_info = {
- (GInterfaceInitFunc) gtk_spinner_accessible_image_interface_init,
- (GInterfaceFinalizeFunc) NULL,
- NULL
- };
- GType parent_atk_type;
- GTypeInfo tinfo = { 0 };
- GTypeQuery query;
- AtkObjectFactory *factory;
+ atk_object_set_name (accessible, C_("throbbing progress animation widget", "Spinner"));
+ atk_object_set_description (accessible, _("Provides visual indication of progress"));
+}
- if ((type = g_type_from_name ("GtkSpinnerAccessible")))
- return type;
+static void
+gtk_spinner_accessible_class_init (GtkSpinnerAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
- factory = atk_registry_get_factory (atk_get_default_registry (),
- GTK_TYPE_IMAGE);
- if (!factory)
- return G_TYPE_INVALID;
+ atk_class->initialize = gtk_spinner_accessible_initialize;
+}
- parent_atk_type = atk_object_factory_get_accessible_type (factory);
- if (!parent_atk_type)
- return G_TYPE_INVALID;
+static void
+gtk_spinner_accessible_init (GtkSpinnerAccessible *self)
+{
+}
- /*
- * Figure out the size of the class and instance
- * we are deriving from
- */
- g_type_query (parent_atk_type, &query);
+/* factory */
+typedef AtkObjectFactory GtkSpinnerAccessibleFactory;
+typedef AtkObjectFactoryClass GtkSpinnerAccessibleFactoryClass;
- tinfo.class_init = (GClassInitFunc) gtk_spinner_accessible_class_init;
- tinfo.class_size = query.class_size;
- tinfo.instance_size = query.instance_size;
+G_DEFINE_TYPE (GtkSpinnerAccessibleFactory,
+ gtk_spinner_accessible_factory,
+ ATK_TYPE_OBJECT_FACTORY);
- /* Register the type */
- type = g_type_register_static (parent_atk_type,
- "GtkSpinnerAccessible",
- &tinfo, 0);
+static GType
+gtk_spinner_accessible_factory_get_accessible_type (void)
+{
+ return gtk_spinner_accessible_get_type ();
+}
- g_type_add_interface_static (type, ATK_TYPE_IMAGE,
- &atk_image_info);
- }
+static AtkObject *
+gtk_spinner_accessible_factory_create_accessible (GObject *obj)
+{
+ AtkObject *accessible;
+
+ accessible = g_object_new (gtk_spinner_accessible_get_type (), NULL);
+ atk_object_initialize (accessible, obj);
+
+ return accessible;
+}
+
+static void
+gtk_spinner_accessible_factory_class_init (AtkObjectFactoryClass *klass)
+{
+ klass->create_accessible = gtk_spinner_accessible_factory_create_accessible;
+ klass->get_accessible_type = gtk_spinner_accessible_factory_get_accessible_type;
+}
- return type;
+static void
+gtk_spinner_accessible_factory_init (AtkObjectFactory *factory)
+{
}
static AtkObject *
@@ -567,8 +510,7 @@ gtk_spinner_get_accessible (GtkWidget *widget)
derived_type = g_type_parent (GTK_TYPE_SPINNER);
registry = atk_get_default_registry ();
- factory = atk_registry_get_factory (registry,
- derived_type);
+ factory = atk_registry_get_factory (registry, derived_type);
derived_atk_type = atk_object_factory_get_accessible_type (factory);
if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
atk_registry_set_factory_type (registry,
@@ -576,6 +518,7 @@ gtk_spinner_get_accessible (GtkWidget *widget)
gtk_spinner_accessible_factory_get_type ());
first_time = FALSE;
}
+
return GTK_WIDGET_CLASS (gtk_spinner_parent_class)->get_accessible (widget);
}
diff --git a/gtk/gtkswitch.c b/gtk/gtkswitch.c
new file mode 100644
index 0000000000..93549ef17e
--- /dev/null
+++ b/gtk/gtkswitch.c
@@ -0,0 +1,997 @@
+/* GTK - The GIMP Toolkit
+ *
+ * Copyright (C) 2010 Intel Corporation
+ * Copyright (C) 2010 RedHat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author:
+ * Emmanuele Bassi <ebassi@linux.intel.com>
+ * Matthias Clasen <mclasen@redhat.com>
+ *
+ * Based on similar code from Mx.
+ */
+
+/**
+ * SECTION:gtkswitch
+ * @Short_Description: A "light switch" style toggle
+ * @Title: GtkSwitch
+ * @See_Also: #GtkToggleButton
+ *
+ * #GtkSwitch is a widget that has two states: on or off. The user can control
+ * which state should be active by clicking the empty area, or by dragging the
+ * handle.
+ */
+
+#include "config.h"
+
+#include "gtkswitch.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include "gtkaccessible.h"
+#include "gtkactivatable.h"
+#include "gtkintl.h"
+#include "gtkstyle.h"
+#include "gtkprivate.h"
+#include "gtktoggleaction.h"
+#include "gtkwidget.h"
+
+#define DEFAULT_SLIDER_WIDTH (36)
+#define DEFAULT_SLIDER_HEIGHT (22)
+
+struct _GtkSwitchPrivate
+{
+ GdkWindow *event_window;
+ GtkAction *action;
+
+ gint handle_x;
+ gint offset;
+ gint drag_start;
+ gint drag_threshold;
+
+ guint is_active : 1;
+ guint is_dragging : 1;
+ guint in_press : 1;
+ guint in_switch : 1;
+ guint use_action_appearance : 1;
+};
+
+enum
+{
+ PROP_0,
+ PROP_ACTIVE,
+ PROP_RELATED_ACTION,
+ PROP_USE_ACTION_APPEARANCE,
+ LAST_PROP
+};
+
+static GParamSpec *switch_props[LAST_PROP] = { NULL, };
+
+static GType gtk_switch_accessible_factory_get_type (void);
+
+static void gtk_switch_activatable_interface_init (GtkActivatableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkSwitch, gtk_switch, GTK_TYPE_WIDGET,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
+ gtk_switch_activatable_interface_init));
+
+static gboolean
+gtk_switch_button_press (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ if (priv->is_active)
+ {
+ /* if the event occurred in the "off" area, then this
+ * is a direct toggle
+ */
+ if (event->x <= allocation.width / 2)
+ {
+ priv->in_press = TRUE;
+ return FALSE;
+ }
+
+ priv->offset = event->x - allocation.width / 2;
+ }
+ else
+ {
+ /* if the event occurred in the "on" area, then this
+ * is a direct toggle
+ */
+ if (event->x >= allocation.width / 2)
+ {
+ priv->in_press = TRUE;
+ return FALSE;
+ }
+
+ priv->offset = event->x;
+ }
+
+ priv->drag_start = event->x;
+
+ g_object_get (gtk_widget_get_settings (widget),
+ "gtk-dnd-drag-threshold", &priv->drag_threshold,
+ NULL);
+
+ return FALSE;
+}
+
+static gboolean
+gtk_switch_motion (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+
+ /* if this is a direct toggle we don't handle motion */
+ if (priv->in_press)
+ return FALSE;
+
+ if (ABS (event->x - priv->drag_start) < priv->drag_threshold)
+ return TRUE;
+
+ if (event->state & GDK_BUTTON1_MASK)
+ {
+ gint position = event->x - priv->offset;
+ GtkAllocation allocation;
+ GtkStyle *style;
+
+ style = gtk_widget_get_style (widget);
+ gtk_widget_get_allocation (widget, &allocation);
+
+ /* constrain the handle within the trough width */
+ if (position > (allocation.width / 2 - style->xthickness))
+ priv->handle_x = allocation.width / 2 - style->xthickness;
+ else if (position < style->xthickness)
+ priv->handle_x = style->xthickness;
+ else
+ priv->handle_x = position;
+
+ priv->is_dragging = TRUE;
+
+ /* we need to redraw the handle */
+ gtk_widget_queue_draw (widget);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_switch_button_release (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ /* dragged toggles have a "soft" grab, so we don't care whether we
+ * are in the switch or not when the button is released; we do care
+ * for direct toggles, instead
+ */
+ if (!priv->is_dragging && !priv->in_switch)
+ return FALSE;
+
+ /* direct toggle */
+ if (priv->in_press)
+ {
+ priv->in_press = FALSE;
+ gtk_switch_set_active (GTK_SWITCH (widget), !priv->is_active);
+
+ return TRUE;
+ }
+
+ /* dragged toggle */
+ if (priv->is_dragging)
+ {
+ priv->is_dragging = FALSE;
+
+ /* if half the handle passed the middle of the switch, then we
+ * consider it to be on
+ */
+ if ((priv->handle_x + (allocation.width / 4)) >= (allocation.width / 2))
+ {
+ gtk_switch_set_active (GTK_SWITCH (widget), TRUE);
+ priv->handle_x = allocation.width / 2;
+ }
+ else
+ {
+ gtk_switch_set_active (GTK_SWITCH (widget), FALSE);
+ priv->handle_x = 0;
+ }
+
+ gtk_widget_queue_draw (widget);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gtk_switch_enter (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+
+ if (event->window == priv->event_window)
+ priv->in_switch = TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+gtk_switch_leave (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+
+ if (event->window == priv->event_window)
+ priv->in_switch = FALSE;
+
+ return FALSE;
+}
+
+static gboolean
+gtk_switch_key_release (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+
+ if (event->keyval == GDK_KEY_Return ||
+ event->keyval == GDK_KEY_KP_Enter ||
+ event->keyval == GDK_KEY_ISO_Enter ||
+ event->keyval == GDK_KEY_space ||
+ event->keyval == GDK_KEY_KP_Space)
+ {
+ gtk_switch_set_active (GTK_SWITCH (widget), !priv->is_active);
+ }
+
+ return FALSE;
+}
+
+static void
+gtk_switch_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ GtkStyle *style = gtk_widget_get_style (widget);
+ gint width, slider_width, focus_width, focus_pad;
+ PangoLayout *layout;
+ PangoRectangle logical_rect;
+
+ width = style->xthickness * 2;
+
+ gtk_widget_style_get (widget,
+ "slider-width", &slider_width,
+ "focus-line-width", &focus_width,
+ "focus-padding", &focus_pad,
+ NULL);
+
+ width += 2 * (focus_width + focus_pad);
+
+ /* Translators: if the "on" state label requires more than three
+ * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
+ * the state
+ */
+ layout = gtk_widget_create_pango_layout (widget, C_("switch", "ON"));
+ pango_layout_get_extents (layout, NULL, &logical_rect);
+ pango_extents_to_pixels (&logical_rect, NULL);
+ width += MAX (logical_rect.width, slider_width);
+
+ /* Translators: if the "off" state label requires more than three
+ * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
+ */
+ pango_layout_set_text (layout, C_("switch", "OFF"), -1);
+ pango_layout_get_extents (layout, NULL, &logical_rect);
+ pango_extents_to_pixels (&logical_rect, NULL);
+ width += MAX (logical_rect.width, slider_width);
+
+ g_object_unref (layout);
+
+ if (minimum)
+ *minimum = width;
+
+ if (natural)
+ *natural = width;
+}
+
+static void
+gtk_switch_get_preferred_height (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ GtkStyle *style = gtk_widget_get_style (widget);
+ gint height, focus_width, focus_pad;
+ PangoLayout *layout;
+ PangoRectangle logical_rect;
+ gchar *str;
+
+ height = style->ythickness * 2;
+
+ gtk_widget_style_get (widget,
+ "focus-line-width", &focus_width,
+ "focus-padding", &focus_pad,
+ NULL);
+
+ height += 2 * (focus_width + focus_pad);
+
+ str = g_strdup_printf ("%s%s",
+ C_("switch", "ON"),
+ C_("switch", "OFF"));
+
+ layout = gtk_widget_create_pango_layout (widget, str);
+ pango_layout_get_extents (layout, NULL, &logical_rect);
+ pango_extents_to_pixels (&logical_rect, NULL);
+ height += MAX (DEFAULT_SLIDER_HEIGHT, logical_rect.height);
+
+ g_object_unref (layout);
+ g_free (str);
+
+ if (minimum)
+ *minimum = height;
+
+ if (natural)
+ *natural = height;
+}
+
+static void
+gtk_switch_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ if (gtk_widget_get_realized (widget))
+ gdk_window_move_resize (priv->event_window,
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height);
+}
+
+static void
+gtk_switch_realize (GtkWidget *widget)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+ GdkWindow *parent_window;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+ GtkAllocation allocation;
+
+ gtk_widget_set_realized (widget, TRUE);
+ parent_window = gtk_widget_get_parent_window (widget);
+ gtk_widget_set_window (widget, parent_window);
+ g_object_ref (parent_window);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_ONLY;
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.event_mask = gtk_widget_get_events (widget);
+ attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON1_MOTION_MASK |
+ GDK_POINTER_MOTION_HINT_MASK |
+ GDK_POINTER_MOTION_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK);
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ priv->event_window = gdk_window_new (parent_window,
+ &attributes,
+ attributes_mask);
+ gdk_window_set_user_data (priv->event_window, widget);
+
+ gtk_widget_style_attach (widget);
+}
+
+static void
+gtk_switch_unrealize (GtkWidget *widget)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+
+ if (priv->event_window != NULL)
+ {
+ gdk_window_set_user_data (priv->event_window, NULL);
+ gdk_window_destroy (priv->event_window);
+ priv->event_window = NULL;
+ }
+
+ GTK_WIDGET_CLASS (gtk_switch_parent_class)->unrealize (widget);
+}
+
+static void
+gtk_switch_map (GtkWidget *widget)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+
+ GTK_WIDGET_CLASS (gtk_switch_parent_class)->map (widget);
+
+ if (priv->event_window)
+ gdk_window_show (priv->event_window);
+}
+
+static void
+gtk_switch_unmap (GtkWidget *widget)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+
+ if (priv->event_window)
+ gdk_window_hide (priv->event_window);
+
+ GTK_WIDGET_CLASS (gtk_switch_parent_class)->unmap (widget);
+}
+
+static inline void
+gtk_switch_paint_handle (GtkWidget *widget,
+ cairo_t *cr,
+ GdkRectangle *box)
+{
+ GtkStyle *style = gtk_widget_get_style (widget);
+
+ gtk_paint_slider (style, cr,
+ gtk_widget_get_state (widget),
+ GTK_SHADOW_OUT,
+ GTK_WIDGET (widget), "switch-slider",
+ box->x,
+ box->y,
+ box->width,
+ box->height,
+ GTK_ORIENTATION_HORIZONTAL);
+}
+
+static gboolean
+gtk_switch_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
+ GtkStyle *style;
+ GdkRectangle handle;
+ PangoLayout *layout;
+ PangoRectangle rect;
+ gint label_x, label_y;
+ GtkStateType state;
+ gint focus_width, focus_pad;
+ gint x, y, width, height;
+
+ gtk_widget_style_get (widget,
+ "focus-line-width", &focus_width,
+ "focus-padding", &focus_pad,
+ NULL);
+
+ style = gtk_widget_get_style (widget);
+
+ x = 0;
+ y = 0;
+ width = gtk_widget_get_allocated_width (widget);
+ height = gtk_widget_get_allocated_height (widget);
+
+ if (gtk_widget_has_focus (widget))
+ gtk_paint_focus (style, cr,
+ gtk_widget_get_state (widget),
+ widget, "button",
+ x, y, width, height);
+
+ x += focus_width + focus_pad;
+ y += focus_width + focus_pad;
+ width -= 2 * (focus_width + focus_pad);
+ height -= 2 * (focus_width + focus_pad);
+
+ state = priv->is_active ? GTK_STATE_SELECTED : gtk_widget_get_state (widget);
+
+ /* background - XXX should this be a flat box instead? we're missing
+ * the border given by the shadow with that, which would require
+ * fixing all theme engines to add a subtle border for this widget
+ */
+ gtk_paint_box (style, cr,
+ state,
+ GTK_SHADOW_IN,
+ widget, "switch-background",
+ x, y, width, height);
+
+ if (!gtk_widget_is_sensitive (widget))
+ state = GTK_STATE_INSENSITIVE;
+
+ /* XXX the +1/-1 it's pixel wriggling after checking with the default
+ * theme and xmag
+ */
+ handle.y = y + style->ythickness + 1;
+ handle.width = (width - style->xthickness * 2) / 2;
+ handle.height = (height - style->ythickness * 2) - 1;
+
+ /* Translators: if the "on" state label requires more than three
+ * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
+ * the state
+ */
+ layout = gtk_widget_create_pango_layout (widget, C_("switch", "ON"));
+ pango_layout_get_extents (layout, NULL, &rect);
+ pango_extents_to_pixels (&rect, NULL);
+
+ label_x = x + style->xthickness
+ + ((width / 2) - rect.width - (style->xthickness * 2)) / 2;
+ label_y = y + style->ythickness
+ + (height - rect.height - (style->ythickness * 2)) / 2;
+
+ gtk_paint_layout (style, cr,
+ state,
+ FALSE,
+ widget, "switch-on-label",
+ label_x, label_y,
+ layout);
+
+ g_object_unref (layout);
+
+ /* Translators: if the "off" state label requires more than three
+ * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
+ */
+ layout = gtk_widget_create_pango_layout (widget, C_("switch", "OFF"));
+ pango_layout_get_extents (layout, NULL, &rect);
+ pango_extents_to_pixels (&rect, NULL);
+
+ label_x = x + style->xthickness
+ + (width / 2)
+ + ((width / 2) - rect.width - (style->xthickness * 2)) / 2;
+ label_y = y + style->ythickness
+ + (height - rect.height - (style->ythickness * 2)) / 2;
+
+ gtk_paint_layout (style, cr,
+ state,
+ FALSE,
+ widget, "switch-off-label",
+ label_x, label_y,
+ layout);
+
+ g_object_unref (layout);
+
+ if (priv->is_dragging)
+ handle.x = x + priv->handle_x;
+ else if (priv->is_active)
+ handle.x = x + width - handle.width - style->xthickness;
+ else
+ handle.x = x + style->xthickness;
+
+ gtk_switch_paint_handle (widget, cr, &handle);
+
+ return FALSE;
+}
+
+static AtkObject *
+gtk_switch_get_accessible (GtkWidget *widget)
+{
+ static gboolean first_time = TRUE;
+
+ if (G_UNLIKELY (first_time))
+ {
+ AtkObjectFactory *factory;
+ AtkRegistry *registry;
+ GType derived_type;
+ GType derived_atk_type;
+
+ /* Figure out whether accessibility is enabled by looking at the
+ * type of the accessible object which would be created for the
+ * parent type of GtkSwitch
+ */
+ derived_type = g_type_parent (GTK_TYPE_SWITCH);
+
+ registry = atk_get_default_registry ();
+ factory = atk_registry_get_factory (registry, derived_type);
+ derived_atk_type = atk_object_factory_get_accessible_type (factory);
+ if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
+ atk_registry_set_factory_type (registry,
+ GTK_TYPE_SWITCH,
+ gtk_switch_accessible_factory_get_type ());
+
+ first_time = FALSE;
+ }
+
+ return GTK_WIDGET_CLASS (gtk_switch_parent_class)->get_accessible (widget);
+}
+
+static void
+gtk_switch_set_related_action (GtkSwitch *sw,
+ GtkAction *action)
+{
+ GtkSwitchPrivate *priv = sw->priv;
+
+ if (priv->action == action)
+ return;
+
+ gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (sw), action);
+
+ priv->action = action;
+}
+
+static void
+gtk_switch_set_use_action_appearance (GtkSwitch *sw,
+ gboolean use_appearance)
+{
+ GtkSwitchPrivate *priv = sw->priv;
+
+ if (priv->use_action_appearance != use_appearance)
+ {
+ priv->use_action_appearance = use_appearance;
+
+ gtk_activatable_sync_action_properties (GTK_ACTIVATABLE (sw), priv->action);
+ }
+}
+
+static void
+gtk_switch_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSwitch *sw = GTK_SWITCH (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE:
+ gtk_switch_set_active (sw, g_value_get_boolean (value));
+ break;
+
+ case PROP_RELATED_ACTION:
+ gtk_switch_set_related_action (sw, g_value_get_object (value));
+ break;
+
+ case PROP_USE_ACTION_APPEARANCE:
+ gtk_switch_set_use_action_appearance (sw, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+gtk_switch_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE:
+ g_value_set_boolean (value, priv->is_active);
+ break;
+
+ case PROP_RELATED_ACTION:
+ g_value_set_object (value, priv->action);
+ break;
+
+ case PROP_USE_ACTION_APPEARANCE:
+ g_value_set_boolean (value, priv->use_action_appearance);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ }
+}
+
+static void
+gtk_switch_dispose (GObject *object)
+{
+ GtkSwitchPrivate *priv = GTK_SWITCH (object)->priv;
+
+ if (priv->action)
+ {
+ gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (object), NULL);
+ priv->action = NULL;
+ }
+
+ G_OBJECT_CLASS (gtk_switch_parent_class)->dispose (object);
+}
+
+static void
+gtk_switch_class_init (GtkSwitchClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ gpointer activatable_iface;
+
+ g_type_class_add_private (klass, sizeof (GtkSwitchPrivate));
+
+ activatable_iface = g_type_default_interface_peek (GTK_TYPE_ACTIVATABLE);
+ switch_props[PROP_RELATED_ACTION] =
+ g_param_spec_override ("related-action",
+ g_object_interface_find_property (activatable_iface,
+ "related-action"));
+
+ switch_props[PROP_USE_ACTION_APPEARANCE] =
+ g_param_spec_override ("use-action-appearance",
+ g_object_interface_find_property (activatable_iface,
+ "use-action-appearance"));
+
+ /**
+ * GtkSwitch:active:
+ *
+ * Whether the #GtkSwitch widget is in its on or off state.
+ */
+ switch_props[PROP_ACTIVE] =
+ g_param_spec_boolean ("active",
+ P_("Active"),
+ P_("Whether the switch is on or off"),
+ FALSE,
+ GTK_PARAM_READWRITE);
+
+ gobject_class->set_property = gtk_switch_set_property;
+ gobject_class->get_property = gtk_switch_get_property;
+ gobject_class->dispose = gtk_switch_dispose;
+
+ g_object_class_install_properties (gobject_class, LAST_PROP, switch_props);
+
+ widget_class->get_preferred_width = gtk_switch_get_preferred_width;
+ widget_class->get_preferred_height = gtk_switch_get_preferred_height;
+ widget_class->size_allocate = gtk_switch_size_allocate;
+ widget_class->realize = gtk_switch_realize;
+ widget_class->unrealize = gtk_switch_unrealize;
+ widget_class->map = gtk_switch_map;
+ widget_class->unmap = gtk_switch_unmap;
+ widget_class->draw = gtk_switch_draw;
+ widget_class->button_press_event = gtk_switch_button_press;
+ widget_class->button_release_event = gtk_switch_button_release;
+ widget_class->motion_notify_event = gtk_switch_motion;
+ widget_class->enter_notify_event = gtk_switch_enter;
+ widget_class->leave_notify_event = gtk_switch_leave;
+ widget_class->key_release_event = gtk_switch_key_release;
+ widget_class->get_accessible = gtk_switch_get_accessible;
+
+ /**
+ * GtkSwitch:slider-width:
+ *
+ * The minimum width of the #GtkSwitch handle, in pixels.
+ */
+ gtk_widget_class_install_style_property (widget_class,
+ g_param_spec_int ("slider-width",
+ P_("Slider Width"),
+ P_("The minimum width of the handle"),
+ DEFAULT_SLIDER_WIDTH, G_MAXINT,
+ DEFAULT_SLIDER_WIDTH,
+ GTK_PARAM_READABLE));
+}
+
+static void
+gtk_switch_init (GtkSwitch *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_SWITCH, GtkSwitchPrivate);
+ self->priv->use_action_appearance = TRUE;
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+ gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
+}
+
+/**
+ * gtk_switch_new:
+ *
+ * Creates a new #GtkSwitch widget.
+ *
+ * Return value: the newly created #GtkSwitch instance
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+gtk_switch_new (void)
+{
+ return g_object_new (GTK_TYPE_SWITCH, NULL);
+}
+
+/**
+ * gtk_switch_set_active:
+ * @sw: a #GtkSwitch
+ * @is_active: %TRUE if @sw should be active, and %FALSE otherwise
+ *
+ * Changes the state of @sw to the desired one.
+ *
+ * Since: 3.0
+ */
+void
+gtk_switch_set_active (GtkSwitch *sw,
+ gboolean is_active)
+{
+ GtkSwitchPrivate *priv;
+
+ g_return_if_fail (GTK_IS_SWITCH (sw));
+
+ is_active = !!is_active;
+
+ priv = sw->priv;
+
+ if (priv->is_active != is_active)
+ {
+ AtkObject *accessible;
+
+ priv->is_active = is_active;
+
+ g_object_notify_by_pspec (G_OBJECT (sw), switch_props[PROP_ACTIVE]);
+
+ if (priv->action)
+ gtk_action_activate (priv->action);
+
+ accessible = gtk_widget_get_accessible (GTK_WIDGET (sw));
+ atk_object_notify_state_change (accessible, ATK_STATE_CHECKED, priv->is_active);
+
+ gtk_widget_queue_draw (GTK_WIDGET (sw));
+ }
+}
+
+/**
+ * gtk_switch_get_active:
+ * @sw: a #GtkSwitch
+ *
+ * Gets whether the #GtkSwitch is in its "on" or "off" state.
+ *
+ * Return value: %TRUE if the #GtkSwitch is active, and %FALSE otherwise
+ *
+ * Since: 3.0
+ */
+gboolean
+gtk_switch_get_active (GtkSwitch *sw)
+{
+ g_return_val_if_fail (GTK_IS_SWITCH (sw), FALSE);
+
+ return sw->priv->is_active;
+}
+
+static void
+gtk_switch_update (GtkActivatable *activatable,
+ GtkAction *action,
+ const gchar *property_name)
+{
+ if (strcmp (property_name, "visible") == 0)
+ {
+ if (gtk_action_is_visible (action))
+ gtk_widget_show (GTK_WIDGET (activatable));
+ else
+ gtk_widget_hide (GTK_WIDGET (activatable));
+ }
+ else if (strcmp (property_name, "sensitive") == 0)
+ {
+ gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
+ }
+ else if (strcmp (property_name, "active") == 0)
+ {
+ gtk_action_block_activate (action);
+ gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+ gtk_action_unblock_activate (action);
+ }
+}
+
+static void
+gtk_switch_sync_action_properties (GtkActivatable *activatable,
+ GtkAction *action)
+{
+ if (!action)
+ return;
+
+ if (gtk_action_is_visible (action))
+ gtk_widget_show (GTK_WIDGET (activatable));
+ else
+ gtk_widget_hide (GTK_WIDGET (activatable));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
+
+ gtk_action_block_activate (action);
+ gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+ gtk_action_unblock_activate (action);
+}
+
+static void
+gtk_switch_activatable_interface_init (GtkActivatableIface *iface)
+{
+ iface->update = gtk_switch_update;
+ iface->sync_action_properties = gtk_switch_sync_action_properties;
+}
+
+/* accessibility: object */
+
+/* dummy typedefs */
+typedef struct _GtkSwitchAccessible GtkSwitchAccessible;
+typedef struct _GtkSwitchAccessibleClass GtkSwitchAccessibleClass;
+
+ATK_DEFINE_TYPE (GtkSwitchAccessible, _gtk_switch_accessible, GTK_TYPE_WIDGET);
+
+static AtkStateSet *
+gtk_switch_accessible_ref_state_set (AtkObject *accessible)
+{
+ AtkStateSet *state_set;
+ GtkWidget *widget;
+
+ state_set = ATK_OBJECT_CLASS (_gtk_switch_accessible_parent_class)->ref_state_set (accessible);
+
+ widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible));
+ if (widget == NULL)
+ return state_set;
+
+ if (gtk_switch_get_active (GTK_SWITCH (widget)))
+ atk_state_set_add_state (state_set, ATK_STATE_CHECKED);
+
+ return state_set;
+}
+
+static void
+_gtk_switch_accessible_initialize (AtkObject *accessible,
+ gpointer widget)
+{
+ ATK_OBJECT_CLASS (_gtk_switch_accessible_parent_class)->initialize (accessible, widget);
+
+ atk_object_set_role (accessible, ATK_ROLE_TOGGLE_BUTTON);
+ atk_object_set_name (accessible, C_("light switch widget", "Switch"));
+ atk_object_set_description (accessible, _("Switches between on and off states"));
+}
+
+static void
+_gtk_switch_accessible_class_init (GtkSwitchAccessibleClass *klass)
+{
+ AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass);
+
+ atk_class->initialize = _gtk_switch_accessible_initialize;
+ atk_class->ref_state_set = gtk_switch_accessible_ref_state_set;
+}
+
+static void
+_gtk_switch_accessible_init (GtkSwitchAccessible *self)
+{
+}
+
+/* accessibility: factory */
+
+typedef AtkObjectFactoryClass GtkSwitchAccessibleFactoryClass;
+typedef AtkObjectFactory GtkSwitchAccessibleFactory;
+
+G_DEFINE_TYPE (GtkSwitchAccessibleFactory,
+ gtk_switch_accessible_factory,
+ ATK_TYPE_OBJECT_FACTORY);
+
+static GType
+gtk_switch_accessible_factory_get_accessible_type (void)
+{
+ return _gtk_switch_accessible_get_type ();
+}
+
+static AtkObject *
+gtk_switch_accessible_factory_create_accessible (GObject *obj)
+{
+ AtkObject *accessible;
+
+ accessible = g_object_new (_gtk_switch_accessible_get_type (), NULL);
+ atk_object_initialize (accessible, obj);
+
+ return accessible;
+}
+
+static void
+gtk_switch_accessible_factory_class_init (AtkObjectFactoryClass *klass)
+{
+ klass->create_accessible = gtk_switch_accessible_factory_create_accessible;
+ klass->get_accessible_type = gtk_switch_accessible_factory_get_accessible_type;
+}
+
+static void
+gtk_switch_accessible_factory_init (AtkObjectFactory *factory)
+{
+}
diff --git a/gtk/gtkswitch.h b/gtk/gtkswitch.h
new file mode 100644
index 0000000000..3706f618b5
--- /dev/null
+++ b/gtk/gtkswitch.h
@@ -0,0 +1,94 @@
+/* GTK - The GIMP Toolkit
+ *
+ * Copyright (C) 2010 Intel Corporation
+ * Copyright (C) 2010 RedHat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author:
+ * Emmanuele Bassi <ebassi@linux.intel.com>
+ * Matthias Clasen <mclasen@redhat.com>
+ *
+ * Based on similar code from Mx.
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_SWITCH_H__
+#define __GTK_SWITCH_H__
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SWITCH (gtk_switch_get_type ())
+#define GTK_SWITCH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SWITCH, GtkSwitch))
+#define GTK_IS_SWITCH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SWITCH))
+#define GTK_SWITCH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SWITCH, GtkSwitchClass))
+#define GTK_IS_SWITCH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SWITCH))
+#define GTK_SWITCH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SWITCH, GtkSwitchClass))
+
+typedef struct _GtkSwitch GtkSwitch;
+typedef struct _GtkSwitchPrivate GtkSwitchPrivate;
+typedef struct _GtkSwitchClass GtkSwitchClass;
+
+/**
+ * GtkSwitch:
+ *
+ * The <structname>GtkSwitch</structname> structure contains private
+ * data and it should only be accessed using the provided API.
+ */
+struct _GtkSwitch
+{
+ /*< private >*/
+ GtkWidget parent_instance;
+
+ GtkSwitchPrivate *priv;
+};
+
+/**
+ * GtkSwitchClass:
+ *
+ * The <structname>GtkSwitchClass</structname> structure contains only
+ * private data.
+ */
+struct _GtkSwitchClass
+{
+ /*< private >*/
+ GtkWidgetClass parent_class;
+
+ void (* _switch_padding_1) (void);
+ void (* _switch_padding_2) (void);
+ void (* _switch_padding_3) (void);
+ void (* _switch_padding_4) (void);
+ void (* _switch_padding_5) (void);
+ void (* _switch_padding_6) (void);
+ void (* _switch_padding_7) (void);
+};
+
+GType gtk_switch_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gtk_switch_new (void);
+
+void gtk_switch_set_active (GtkSwitch *sw,
+ gboolean is_active);
+gboolean gtk_switch_get_active (GtkSwitch *sw);
+
+G_END_DECLS
+
+#endif /* __GTK_SWITCH_H__ */
diff --git a/gtk/gtktoolbar.c b/gtk/gtktoolbar.c
index 007d039467..5d914fada4 100644
--- a/gtk/gtktoolbar.c
+++ b/gtk/gtktoolbar.c
@@ -3125,8 +3125,12 @@ gtk_toolbar_finalize (GObject *object)
g_timer_destroy (priv->timer);
if (priv->menu)
- gtk_widget_destroy (GTK_WIDGET (priv->menu));
-
+ {
+ g_signal_handlers_disconnect_by_func (priv->menu,
+ menu_deactivated, toolbar);
+ gtk_widget_destroy (GTK_WIDGET (priv->menu));
+ }
+
if (priv->idle_id)
g_source_remove (priv->idle_id);