diff options
author | Cosimo Cecchi <cosimoc@gnome.org> | 2010-11-29 12:19:18 +0100 |
---|---|---|
committer | Cosimo Cecchi <cosimoc@gnome.org> | 2010-11-29 12:19:18 +0100 |
commit | 06f501fa07a0d450d53aa17b2af12b9b72378587 (patch) | |
tree | 02f7092b2f7ab442b6ac824e5a26dc3907299e49 /gtk/gtkappchooserbutton.c | |
parent | ef9543e978d8bd83004c362e27c7e1233f7c436a (diff) | |
download | gtk+-06f501fa07a0d450d53aa17b2af12b9b72378587.tar.gz |
app-chooser-button: rename GtkAppChooserComboBox -> GtkAppChooserButton
Diffstat (limited to 'gtk/gtkappchooserbutton.c')
-rw-r--r-- | gtk/gtkappchooserbutton.c | 656 |
1 files changed, 656 insertions, 0 deletions
diff --git a/gtk/gtkappchooserbutton.c b/gtk/gtkappchooserbutton.c new file mode 100644 index 0000000000..fcaf8750bc --- /dev/null +++ b/gtk/gtkappchooserbutton.c @@ -0,0 +1,656 @@ +/* + * 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" + +enum { + PROP_CONTENT_TYPE = 1, + PROP_SHOW_DIALOG_ITEM, +}; + +enum { + COLUMN_APP_INFO, + COLUMN_NAME, + COLUMN_ICON, + COLUMN_CUSTOM, + COLUMN_SEPARATOR, + COLUMN_CALLBACK, + NUM_COLUMNS, +}; + +typedef struct { + GtkAppChooserButtonItemFunc func; + gpointer user_data; +} CustomAppComboData; + +static gpointer +custom_app_data_copy (gpointer boxed) +{ + CustomAppComboData *retval, *original; + + original = boxed; + + retval = g_slice_new0 (CustomAppComboData); + retval->func = original->func; + retval->user_data = original->user_data; + + return retval; +} + +static void +custom_app_data_free (gpointer boxed) +{ + g_slice_free (CustomAppComboData, boxed); +} + +#define CUSTOM_COMBO_DATA_TYPE custom_app_combo_data_get_type() +G_DEFINE_BOXED_TYPE (CustomAppComboData, custom_app_combo_data, + custom_app_data_copy, + custom_app_data_free); + +static void app_chooser_iface_init (GtkAppChooserIface *iface); + +static void real_insert_custom_item (GtkAppChooserButton *self, + const gchar *label, + GIcon *icon, + GtkAppChooserButtonItemFunc func, + gpointer user_data, + gboolean custom, + GtkTreeIter *iter); + +static void real_insert_separator (GtkAppChooserButton *self, + gboolean custom, + GtkTreeIter *iter); + +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; +}; + +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, + gpointer _user_data) +{ + 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; + + if (!self->priv->show_dialog_item) + return; + + icon = g_themed_icon_new ("application-x-executable"); + + gtk_list_store_insert_after (self->priv->store, &iter, prev_iter); + real_insert_separator (self, FALSE, &iter); + *prev_iter = iter; + + gtk_list_store_insert_after (self->priv->store, &iter, prev_iter); + real_insert_custom_item (self, + _("Other application..."), icon, + other_application_item_activated_cb, + NULL, FALSE, &iter); + + 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 first; + + recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type); + first = TRUE; + + 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 (first) + { + get_first_iter (self->priv->store, &iter); + first = FALSE; + } + else + { + gtk_list_store_insert_after (self->priv->store, &iter2, &iter); + iter = iter2; + } + + gtk_list_store_set (self->priv->store, &iter, + COLUMN_APP_INFO, app, + COLUMN_NAME, g_app_info_get_display_name (app), + COLUMN_ICON, icon, + COLUMN_CUSTOM, FALSE, + -1); + + g_object_unref (icon); + } + + 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, + G_TYPE_ICON, + G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, + CUSTOM_COMBO_DATA_TYPE); + + 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_NAME, + "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; + CustomAppComboData *custom_data = NULL; + + if (!gtk_combo_box_get_active_iter (object, &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter, + COLUMN_CALLBACK, &custom_data, + -1); + + if (custom_data != NULL && custom_data->func != NULL) + custom_data->func (self, custom_data->user_data); +} + +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_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"); + + 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); + + 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); +} + +static void +real_insert_custom_item (GtkAppChooserButton *self, + const gchar *label, + GIcon *icon, + GtkAppChooserButtonItemFunc func, + gpointer user_data, + gboolean custom, + GtkTreeIter *iter) +{ + CustomAppComboData *data; + + data = g_slice_new0 (CustomAppComboData); + data->func = func; + data->user_data = user_data; + + gtk_list_store_set (self->priv->store, iter, + COLUMN_NAME, label, + COLUMN_ICON, icon, + COLUMN_CALLBACK, data, + 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 + * @label: the label for the custom item + * @icon: the icon for the custom item + * @func: callback to call if the item is activated + * @user_data: user data for @func + * + * Appends a custom item to the list of applications that is shown + * in the popup. See also gtk_app_chooser_button_append_separator(). + * + * Since: 3.0 + */ +void +gtk_app_chooser_button_append_custom_item (GtkAppChooserButton *self, + const gchar *label, + GIcon *icon, + GtkAppChooserButtonItemFunc func, + gpointer user_data) +{ + GtkTreeIter iter; + + g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self)); + + gtk_list_store_append (self->priv->store, &iter); + real_insert_custom_item (self, label, icon, + func, user_data, TRUE, &iter); +} + +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; +} + +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)); + } +} |