X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkappchooserbutton.c;h=235464c58e80cd1d1cffb2033bf44c4f02c91f56;hb=feb64f40b0f50735104da0a7fdafbe480763c180;hp=b510764ab18ab87fa222b52ce8efb8166e03d129;hpb=9ffd1f7adbda3866d7fe0acaefb5eca8f684724e;p=~andy%2Fgtk diff --git a/gtk/gtkappchooserbutton.c b/gtk/gtkappchooserbutton.c index b510764ab..235464c58 100644 --- a/gtk/gtkappchooserbutton.c +++ b/gtk/gtkappchooserbutton.c @@ -14,13 +14,39 @@ * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public - * License along with the Gnome Library; see the file COPYING.LIB. If not, - * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . * * Authors: Cosimo Cecchi */ +/** + * SECTION:gtkappchooserbutton + * @Title: GtkAppChooserButton + * @Short_description: A button to launch an application chooser dialog + * + * The #GtkAppChooserButton is a widget that lets the user select + * an application. It implements the #GtkAppChooser interface. + * + * Initially, a #GtkAppChooserButton selects the first application + * in its list, which will either be the most-recently used application + * or, if #GtkAppChooserButton:show-default-item is %TRUE, the + * default application. + * + * The list of applications shown in a #GtkAppChooserButton includes + * the recommended applications for the given content type. When + * #GtkAppChooserButton:show-default-item is set, the default application + * is also included. To let the user chooser other applications, + * you can set the #GtkAppChooserButton:show-dialog-item property, + * which allows to open a full #GtkAppChooserDialog. + * + * It is possible to add custom items to the list, using + * gtk_app_chooser_button_append_custom_item(). These items cause + * the #GtkAppChooserButton::custom-item-activated signal to be + * emitted when they are selected. + * + * To track changes in the selected application, use the + * #GtkComboBox::changed signal. + */ #include "config.h" #include "gtkappchooserbutton.h" @@ -39,6 +65,8 @@ enum { PROP_CONTENT_TYPE = 1, PROP_SHOW_DIALOG_ITEM, + PROP_SHOW_DEFAULT_ITEM, + PROP_HEADING }; enum { @@ -81,7 +109,10 @@ struct _GtkAppChooserButtonPrivate { GtkListStore *store; gchar *content_type; + gchar *heading; + gint last_active; gboolean show_dialog_item; + gboolean show_default_item; GHashTable *custom_item_names; }; @@ -142,25 +173,29 @@ select_application_func_cb (GtkTreeModel *model, SelectAppData *data = user_data; GAppInfo *app_to_match = data->info, *app = NULL; gboolean custom; + gboolean result; gtk_tree_model_get (model, iter, COLUMN_APP_INFO, &app, COLUMN_CUSTOM, &custom, -1); - /* cutsom items are always after GAppInfos, so iterating further here + /* custom items are always after GAppInfos, so iterating further here * is just useless. */ if (custom) - return TRUE; - - if (g_app_info_equal (app, app_to_match)) + result = TRUE; + else if (g_app_info_equal (app, app_to_match)) { gtk_combo_box_set_active_iter (GTK_COMBO_BOX (data->self), iter); - return TRUE; + result = TRUE; } + else + result = FALSE; - return FALSE; + g_object_unref (app); + + return result; } static void @@ -190,15 +225,17 @@ other_application_dialog_response_cb (GtkDialog *dialog, if (response_id != GTK_RESPONSE_OK) { /* reset the active item, otherwise we are stuck on - * 'Other application...' + * 'Other application…' */ - gtk_combo_box_set_active (GTK_COMBO_BOX (self), 0); + gtk_combo_box_set_active (GTK_COMBO_BOX (self), self->priv->last_active); gtk_widget_destroy (GTK_WIDGET (dialog)); return; } info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog)); + gtk_widget_destroy (GTK_WIDGET (dialog)); + /* refresh the combobox to get the new application */ gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); gtk_app_chooser_button_select_application (self, info); @@ -215,6 +252,11 @@ other_application_item_activated_cb (GtkAppChooserButton *self) toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self))); dialog = gtk_app_chooser_dialog_new_for_content_type (toplevel, GTK_DIALOG_DESTROY_WITH_PARENT, self->priv->content_type); + + gtk_window_set_modal (GTK_WINDOW (dialog), gtk_window_get_modal (toplevel)); + gtk_app_chooser_dialog_set_heading (GTK_APP_CHOOSER_DIALOG (dialog), + self->priv->heading); + widget = gtk_app_chooser_dialog_get_widget (GTK_APP_CHOOSER_DIALOG (dialog)); g_object_set (widget, "show-fallback", TRUE, @@ -230,22 +272,45 @@ static void gtk_app_chooser_button_ensure_dialog_item (GtkAppChooserButton *self, GtkTreeIter *prev_iter) { - GIcon *icon; - GtkTreeIter iter; + GtkTreeIter iter, iter2; - if (!self->priv->show_dialog_item) + if (!self->priv->show_dialog_item || !self->priv->content_type) return; - icon = g_themed_icon_new ("application-x-executable"); + if (prev_iter == NULL) + gtk_list_store_append (self->priv->store, &iter); + else + gtk_list_store_insert_after (self->priv->store, &iter, prev_iter); - gtk_list_store_insert_after (self->priv->store, &iter, prev_iter); real_insert_separator (self, FALSE, &iter); - *prev_iter = iter; + iter2 = iter; - gtk_list_store_insert_after (self->priv->store, &iter, prev_iter); + gtk_list_store_insert_after (self->priv->store, &iter, &iter2); real_insert_custom_item (self, CUSTOM_ITEM_OTHER_APP, - _("Other application..."), icon, + _("Other application…"), NULL, FALSE, &iter); +} + +static void +insert_one_application (GtkAppChooserButton *self, + GAppInfo *app, + GtkTreeIter *iter) +{ + GIcon *icon; + + icon = g_app_info_get_icon (app); + + if (icon == NULL) + icon = g_themed_icon_new ("application-x-executable"); + else + g_object_ref (icon); + + gtk_list_store_set (self->priv->store, iter, + COLUMN_APP_INFO, app, + COLUMN_LABEL, g_app_info_get_name (app), + COLUMN_ICON, icon, + COLUMN_CUSTOM, FALSE, + -1); g_object_unref (icon); } @@ -254,48 +319,60 @@ static void gtk_app_chooser_button_populate (GtkAppChooserButton *self) { GList *recommended_apps = NULL, *l; - GAppInfo *app; + GAppInfo *app, *default_app = NULL; GtkTreeIter iter, iter2; - GIcon *icon; - gboolean first; + gboolean cycled_recommended; - recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type); - first = TRUE; +#ifndef G_OS_WIN32 + if (self->priv->content_type) + recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type); +#endif + cycled_recommended = FALSE; - get_first_iter (self->priv->store, &iter); + if (self->priv->show_default_item) + { + default_app = g_app_info_get_default_for_type (self->priv->content_type, FALSE); + + if (default_app != NULL) + { + get_first_iter (self->priv->store, &iter); + cycled_recommended = TRUE; + + insert_one_application (self, default_app, &iter); + + g_object_unref (default_app); + } + } for (l = recommended_apps; l != NULL; l = l->next) { app = l->data; - icon = g_app_info_get_icon (app); + if (default_app != NULL && g_app_info_equal (app, default_app)) + continue; - if (icon == NULL) - icon = g_themed_icon_new ("application-x-executable"); - else - g_object_ref (icon); - - if (first) + if (cycled_recommended) { - first = FALSE; + gtk_list_store_insert_after (self->priv->store, &iter2, &iter); + iter = iter2; } else { - gtk_list_store_insert_after (self->priv->store, &iter2, &iter); - iter = iter2; + get_first_iter (self->priv->store, &iter); + cycled_recommended = TRUE; } - gtk_list_store_set (self->priv->store, &iter, - COLUMN_APP_INFO, app, - COLUMN_LABEL, g_app_info_get_display_name (app), - COLUMN_ICON, icon, - COLUMN_CUSTOM, FALSE, - -1); - - g_object_unref (icon); + insert_one_application (self, app, &iter); } - gtk_app_chooser_button_ensure_dialog_item (self, &iter); + if (recommended_apps != NULL) + g_list_free_full (recommended_apps, g_object_unref); + + if (!cycled_recommended) + gtk_app_chooser_button_ensure_dialog_item (self, NULL); + else + gtk_app_chooser_button_ensure_dialog_item (self, &iter); + gtk_combo_box_set_active (GTK_COMBO_BOX (self), 0); } @@ -303,35 +380,34 @@ 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 */ + GtkCellArea *area; gtk_combo_box_set_model (GTK_COMBO_BOX (self), GTK_TREE_MODEL (self->priv->store)); + area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (self)); + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (self), row_separator_func, NULL, NULL); cell = gtk_cell_renderer_pixbuf_new (); - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, FALSE); + gtk_cell_area_add_with_properties (area, cell, + "align", FALSE, + "expand", FALSE, + "fixed-size", FALSE, + NULL); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self), cell, "gicon", COLUMN_ICON, NULL); cell = gtk_cell_renderer_text_new (); - gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (self), cell, TRUE); + gtk_cell_area_add_with_properties (area, cell, + "align", FALSE, + "expand", TRUE, + NULL); gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (self), cell, "text", COLUMN_LABEL, NULL); - g_object_set (cell, - "xpad", 6, - NULL); gtk_app_chooser_button_populate (self); } @@ -382,6 +458,7 @@ gtk_app_chooser_button_changed (GtkComboBox *object) { name_quark = g_quark_from_string (name); g_signal_emit (self, signals[SIGNAL_CUSTOM_ITEM_ACTIVATED], name_quark, name); + self->priv->last_active = gtk_combo_box_get_active (object); } else { @@ -391,6 +468,8 @@ gtk_app_chooser_button_changed (GtkComboBox *object) g_free (name); } + else + self->priv->last_active = gtk_combo_box_get_active (object); } static void @@ -427,8 +506,6 @@ gtk_app_chooser_button_constructed (GObject *obj) if (G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed != NULL) G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->constructed (obj); - g_assert (self->priv->content_type != NULL); - gtk_app_chooser_button_build_ui (self); } @@ -448,6 +525,12 @@ gtk_app_chooser_button_set_property (GObject *obj, case PROP_SHOW_DIALOG_ITEM: gtk_app_chooser_button_set_show_dialog_item (self, g_value_get_boolean (value)); break; + case PROP_SHOW_DEFAULT_ITEM: + gtk_app_chooser_button_set_show_default_item (self, g_value_get_boolean (value)); + break; + case PROP_HEADING: + gtk_app_chooser_button_set_heading (self, g_value_get_string (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); break; @@ -470,6 +553,12 @@ gtk_app_chooser_button_get_property (GObject *obj, case PROP_SHOW_DIALOG_ITEM: g_value_set_boolean (value, self->priv->show_dialog_item); break; + case PROP_SHOW_DEFAULT_ITEM: + g_value_set_boolean (value, self->priv->show_default_item); + break; + case PROP_HEADING: + g_value_set_string (value, self->priv->heading); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec); break; @@ -483,6 +572,9 @@ gtk_app_chooser_button_finalize (GObject *obj) g_hash_table_destroy (self->priv->custom_item_names); g_free (self->priv->content_type); + g_free (self->priv->heading); + + g_object_unref (self->priv->store); G_OBJECT_CLASS (gtk_app_chooser_button_parent_class)->finalize (obj); } @@ -513,17 +605,50 @@ gtk_app_chooser_button_class_init (GtkAppChooserButtonClass *klass) /** * 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. + * 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_("Include an 'Other…' item"), P_("Whether the combobox should include an item that triggers a GtkAppChooserDialog"), FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); g_object_class_install_property (oclass, PROP_SHOW_DIALOG_ITEM, pspec); + /** + * GtkAppChooserButton:show-default-item: + * + * The #GtkAppChooserButton:show-default-item property determines + * whether the dropdown menu should show the default application + * on top for the provided content type. + * + * Since: 3.2 + */ + pspec = + g_param_spec_boolean ("show-default-item", + P_("Show default item"), + P_("Whether the combobox should show the default application on top"), + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (oclass, PROP_SHOW_DEFAULT_ITEM, pspec); + + + /** + * GtkAppChooserButton:heading: + * + * The text to show at the top of the dialog that can be + * opened from the button. The string may contain Pango markup. + */ + pspec = g_param_spec_string ("heading", + P_("Heading"), + P_("The text to show at the top of the dialog"), + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_property (oclass, PROP_HEADING, pspec); + + /** * GtkAppChooserButton::custom-item-activated: * @self: the object which received the signal @@ -554,6 +679,45 @@ gtk_app_chooser_button_init (GtkAppChooserButton *self) self->priv->custom_item_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + self->priv->store = gtk_list_store_new (NUM_COLUMNS, + G_TYPE_APP_INFO, + G_TYPE_STRING, /* name */ + G_TYPE_STRING, /* label */ + G_TYPE_ICON, + G_TYPE_BOOLEAN, /* separator */ + G_TYPE_BOOLEAN); /* custom */ +} + +static gboolean +app_chooser_button_iter_from_custom_name (GtkAppChooserButton *self, + const gchar *name, + GtkTreeIter *set_me) +{ + GtkTreeIter iter; + gchar *custom_name = NULL; + + if (!gtk_tree_model_get_iter_first + (GTK_TREE_MODEL (self->priv->store), &iter)) + return FALSE; + + do { + gtk_tree_model_get (GTK_TREE_MODEL (self->priv->store), &iter, + COLUMN_NAME, &custom_name, + -1); + + if (g_strcmp0 (custom_name, name) == 0) + { + g_free (custom_name); + *set_me = iter; + + return TRUE; + } + + g_free (custom_name); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->priv->store), &iter)); + + return FALSE; } static void @@ -648,9 +812,9 @@ gtk_app_chooser_button_append_separator (GtkAppChooserButton *self) * * 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. + * Clients can use the provided name as a detail for the + * #GtkAppChooserButton::custom-item-activated signal, to add a + * callback for the activation of a particular custom item in the list. * See also gtk_app_chooser_button_append_separator(). * * Since: 3.0 @@ -670,6 +834,39 @@ gtk_app_chooser_button_append_custom_item (GtkAppChooserButton *self, real_insert_custom_item (self, name, label, icon, TRUE, &iter); } +/** + * gtk_app_chooser_button_set_active_custom_item: + * @self: a #GtkAppChooserButton + * @name: the name of the custom item + * + * Selects a custom item previously added with + * gtk_app_chooser_button_append_custom_item(). + * + * Use gtk_app_chooser_refresh() to bring the selection + * to its initial state. + * + * Since: 3.0 + */ +void +gtk_app_chooser_button_set_active_custom_item (GtkAppChooserButton *self, + const gchar *name) +{ + GtkTreeIter iter; + + g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self)); + g_return_if_fail (name != NULL); + + if (g_hash_table_lookup (self->priv->custom_item_names, name) == NULL || + !app_chooser_button_iter_from_custom_name (self, name, &iter)) + { + g_warning ("Can't find the item named %s in the app chooser.", + name); + return; + } + + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (self), &iter); +} + /** * gtk_app_chooser_button_get_show_dialog_item: * @self: a #GtkAppChooserButton @@ -701,7 +898,7 @@ gtk_app_chooser_button_get_show_dialog_item (GtkAppChooserButton *self) */ void gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self, - gboolean setting) + gboolean setting) { if (self->priv->show_dialog_item != setting) { @@ -712,3 +909,83 @@ gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self, gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); } } + +/** + * gtk_app_chooser_button_get_show_default_item: + * @self: a #GtkAppChooserButton + * + * Returns the current value of the #GtkAppChooserButton:show-default-item + * property. + * + * Returns: the value of #GtkAppChooserButton:show-default-item + * + * Since: 3.2 + */ +gboolean +gtk_app_chooser_button_get_show_default_item (GtkAppChooserButton *self) +{ + g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), FALSE); + + return self->priv->show_default_item; +} + +/** + * gtk_app_chooser_button_set_show_default_item: + * @self: a #GtkAppChooserButton + * @setting: the new value for #GtkAppChooserButton:show-default-item + * + * Sets whether the dropdown menu of this button should show the + * default application for the given content type at top. + * + * Since: 3.2 + */ +void +gtk_app_chooser_button_set_show_default_item (GtkAppChooserButton *self, + gboolean setting) +{ + if (self->priv->show_default_item != setting) + { + self->priv->show_default_item = setting; + + g_object_notify (G_OBJECT (self), "show-default-item"); + + gtk_app_chooser_refresh (GTK_APP_CHOOSER (self)); + } +} + +/** + * gtk_app_chooser_button_set_heading: + * @self: a #GtkAppChooserButton + * @heading: a string containing Pango markup + * + * Sets the text to display at the top of the dialog. + * If the heading is not set, the dialog displays a default text. + */ +void +gtk_app_chooser_button_set_heading (GtkAppChooserButton *self, + const gchar *heading) +{ + g_return_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self)); + + g_free (self->priv->heading); + self->priv->heading = g_strdup (heading); + + g_object_notify (G_OBJECT (self), "heading"); +} + +/** + * gtk_app_chooser_button_get_heading: + * @self: a #GtkAppChooserButton + * + * Returns the text to display at the top of the dialog. + * + * Returns: the text to display at the top of the dialog, + * or %NULL, in which case a default text is displayed + */ +const gchar * +gtk_app_chooser_button_get_heading (GtkAppChooserButton *self) +{ + g_return_val_if_fail (GTK_IS_APP_CHOOSER_BUTTON (self), NULL); + + return self->priv->heading; +}