diff options
author | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2010-12-02 13:58:02 +0900 |
---|---|---|
committer | Tristan Van Berkom <tristan.van.berkom@gmail.com> | 2010-12-02 13:58:02 +0900 |
commit | 03a72118add37e6df51c12dba6c62298e810d41a (patch) | |
tree | 200a88ad09d55a5e0a207d0f7275de146e8b337c /gtk | |
parent | a9fd00a4358f0ff73280bfeb51164872aea50e9b (diff) | |
parent | dd678ac323b81e67bb1dea488f2123efee7e3500 (diff) | |
download | gtk+-03a72118add37e6df51c12dba6c62298e810d41a.tar.gz |
Merge branch 'master' into treeview-refactor
Conflicts:
tests/Makefile.am
Diffstat (limited to 'gtk')
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 @@ -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 <marks> element, which * can contain multiple <mark> 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); |