* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
- * License along with the Gnome Library; see the file COPYING.LIB. If not,
- * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Cosimo Cecchi <ccecchi@redhat.com>
*/
+/**
+ * SECTION:gtkappchooserbutton
+ * @Title: GtkAppChooserButton
+ * @Short_description: A button to launch an application chooser dialog
+ *
+ * The #GtkAppChooserButton is a widget that lets the user select
+ * an application. It implements the #GtkAppChooser interface.
+ *
+ * Initially, a #GtkAppChooserButton selects the first application
+ * in its list, which will either be the most-recently used application
+ * or, if #GtkAppChooserButton:show-default-item is %TRUE, the
+ * default application.
+ *
+ * The list of applications shown in a #GtkAppChooserButton includes
+ * the recommended applications for the given content type. When
+ * #GtkAppChooserButton:show-default-item is set, the default application
+ * is also included. To let the user chooser other applications,
+ * you can set the #GtkAppChooserButton:show-dialog-item property,
+ * which allows to open a full #GtkAppChooserDialog.
+ *
+ * It is possible to add custom items to the list, using
+ * gtk_app_chooser_button_append_custom_item(). These items cause
+ * the #GtkAppChooserButton::custom-item-activated signal to be
+ * emitted when they are selected.
+ *
+ * To track changes in the selected application, use the
+ * #GtkComboBox::changed signal.
+ */
#include "config.h"
#include "gtkappchooserbutton.h"
enum {
PROP_CONTENT_TYPE = 1,
PROP_SHOW_DIALOG_ITEM,
+ PROP_SHOW_DEFAULT_ITEM,
+ PROP_HEADING
};
enum {
GtkListStore *store;
gchar *content_type;
+ gchar *heading;
+ gint last_active;
gboolean show_dialog_item;
+ gboolean show_default_item;
GHashTable *custom_item_names;
};
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
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);
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,
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);
}
gtk_app_chooser_button_populate (GtkAppChooserButton *self)
{
GList *recommended_apps = NULL, *l;
- GAppInfo *app;
+ GAppInfo *app, *default_app = NULL;
GtkTreeIter iter, iter2;
- GIcon *icon;
- gboolean first;
+ gboolean cycled_recommended;
+
+#ifndef G_OS_WIN32
+ if (self->priv->content_type)
+ recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
+#endif
+ cycled_recommended = FALSE;
+
+ 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;
- recommended_apps = g_app_info_get_recommended_for_type (self->priv->content_type);
- first = TRUE;
+ insert_one_application (self, default_app, &iter);
+
+ g_object_unref (default_app);
+ }
+ }
for (l = recommended_apps; l != NULL; l = l->next)
{
app = l->data;
- icon = g_app_info_get_icon (app);
-
- if (icon == NULL)
- icon = g_themed_icon_new ("application-x-executable");
- else
- g_object_ref (icon);
+ if (default_app != NULL && g_app_info_equal (app, default_app))
+ continue;
- if (first)
+ if (cycled_recommended)
{
- get_first_iter (self->priv->store, &iter);
- first = FALSE;
+ gtk_list_store_insert_after (self->priv->store, &iter2, &iter);
+ iter = iter2;
}
else
{
- gtk_list_store_insert_after (self->priv->store, &iter2, &iter);
- iter = iter2;
+ get_first_iter (self->priv->store, &iter);
+ cycled_recommended = TRUE;
}
- gtk_list_store_set (self->priv->store, &iter,
- COLUMN_APP_INFO, app,
- COLUMN_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);
}
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);
}
{
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
{
g_free (name);
}
+ else
+ self->priv->last_active = gtk_combo_box_get_active (object);
}
static void
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);
}
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;
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;
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);
}
/**
* 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
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
*
* 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
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
*/
void
gtk_app_chooser_button_set_show_dialog_item (GtkAppChooserButton *self,
- gboolean setting)
+ gboolean setting)
{
if (self->priv->show_dialog_item != setting)
{
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;
+}