diff --git a/demos/gtk-demo/sidebar.c b/demos/gtk-demo/sidebar.c
index 5ec3d16fd0..d5732cec69 100644
--- a/demos/gtk-demo/sidebar.c
+++ b/demos/gtk-demo/sidebar.c
@@ -73,7 +73,7 @@ do_sidebar (GtkWidget *do_widget)
widget = gtk_label_new (c);
}
gtk_stack_add_named (GTK_STACK (stack), widget, c);
- gtk_container_child_set (GTK_CONTAINER (stack), widget, "title", c, NULL);
+ g_object_set (gtk_stack_get_page (GTK_STACK (stack), widget), "title", c, NULL);
}
gtk_container_add (GTK_CONTAINER (window), box);
diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml
index a5e25803fa..0611530b29 100644
--- a/docs/reference/gtk/gtk4-docs.xml
+++ b/docs/reference/gtk/gtk4-docs.xml
@@ -49,6 +49,8 @@
+
+
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index 1929eaf4a1..fd93df5a50 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -414,6 +414,47 @@ gtk_list_box_get_type
gtk_list_box_row_get_type
+
+gtkselectionmodel
+GtkSelectionModel
+GtkSelectionModel
+gtk_selection_model_is_selected
+gtk_selection_model_select_item
+gtk_selection_model_unselect_item
+gtk_selection_model_select_range
+gtk_selection_model_unselect_range
+gtk_selection_model_select_all
+gtk_selection_model_unselect_all
+
+gtk_selection_model_selection_changed
+
+GTK_SELECTION_MODEL
+GTK_SELECTION_MODEL_CLASS
+GTK_SELECTION_MODEL_GET_CLASS
+GTK_IS_SELECTION_MODEL
+GTK_IS_SELECTION_MODEL_CLASS
+GTK_TYPE_SELECTION_MODEL
+
+gtk_selection_model_get_type
+
+
+
+gtksingleselection
+GtkSingleSelection
+GtkSingleSelection
+GTK_INVALID_LIST_POSITION
+gtk_single_selection_new
+gtk_single_selection_get_selected
+gtk_single_selection_set_selected
+gtk_single_selection_get_selected_item
+gtk_single_selection_get_autoselect
+gtk_single_selection_set_autoselect
+gtk_single_selection_get_can_unselect
+gtk_single_selection_set_can_unselect
+
+gtk_single_selection_get_type
+
+
gtkbuildable
GtkBuildable
diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in
index ecb383d45b..79d73c04a4 100644
--- a/docs/reference/gtk/gtk4.types.in
+++ b/docs/reference/gtk/gtk4.types.in
@@ -142,6 +142,7 @@ gtk_scrollbar_get_type
gtk_scrolled_window_get_type
gtk_search_bar_get_type
gtk_search_entry_get_type
+gtk_selection_model_get_type
gtk_separator_get_type
gtk_separator_menu_item_get_type
gtk_separator_tool_item_get_type
@@ -151,6 +152,7 @@ gtk_shortcuts_window_get_type
gtk_shortcuts_section_get_type
gtk_shortcuts_group_get_type
gtk_shortcuts_shortcut_get_type
+gtk_single_selection_get_type
gtk_size_group_get_type
gtk_snapshot_get_type
gtk_spin_button_get_type
diff --git a/gtk/gtk.h b/gtk/gtk.h
index e5e1995bc0..c79f1a9dfb 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -184,6 +184,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -194,6 +195,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/gtk/gtkselectionmodel.c b/gtk/gtkselectionmodel.c
new file mode 100644
index 0000000000..0be3961bc2
--- /dev/null
+++ b/gtk/gtkselectionmodel.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * 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.1 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, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#include "config.h"
+
+#include "gtkselectionmodel.h"
+
+#include "gtkintl.h"
+#include "gtkmarshalers.h"
+
+/**
+ * SECTION:gtkselectionmodel
+ * @Title: GtkSelectionModel
+ * @Short_description: An extension of the list model interface that handles selections
+ * @See_also: #GListModel, #GtkSingleSelection
+ *
+ * #GtkSelectionModel is an interface that extends the #GListModel interface by adding
+ * support for selections. This support is then used by widgets using list models to add
+ * the ability to select and unselect various items.
+ *
+ * GTK provides default implementations of the mode common selection modes such as
+ * #GtkSingleSelection, so you will only need to implement this interface if you want
+ * detailed control about how selections should be handled.
+ *
+ * A #GtkSelectionModel supports a single boolean per row indicating if a row is selected
+ * or not. This can be queried via gtk_selection_model_is_selected(). When the selected
+ * state of one or more rows changes, the model will emit the
+ * GtkSelectionModel::selection-changed signal by calling the
+ * gtk_selection_model_selection_changed() function. The positions given in that signal
+ * may have their selection state changed, though that is not a requirement.
+ * If new items added to the model via the #GListModel::items-changed signal are selected
+ * or not is up to the implementation.
+ *
+ * Additionally, the interface can expose functionality to select and unselect items.
+ * If these functions are implemented, GTK's list widgets will allow users to select and
+ * unselect items. However, #GtkSelectionModels are free to only implement them
+ * partially or not at all. In that case the widgets will not support the unimplemented
+ * operations.
+ *
+ * When selecting or unselecting is supported by a model, the return values of the
+ * selection functions do NOT indicate if selection or unselection happened. They are
+ * only meant to indicate complete failure, like when this mode of selecting is not
+ * supported by the model.
+ * Selections may happen asynchronously, so the only reliable way to find out when an
+ * item was selected is to listen to the signals that indicate selection.
+ */
+
+G_DEFINE_INTERFACE (GtkSelectionModel, gtk_selection_model, G_TYPE_LIST_MODEL)
+
+enum {
+ SELECTION_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static gboolean
+gtk_selection_model_default_is_selected (GtkSelectionModel *model,
+ guint position)
+{
+ return FALSE;
+}
+
+static gboolean
+gtk_selection_model_default_select_item (GtkSelectionModel *model,
+ guint position,
+ gboolean exclusive)
+{
+ return FALSE;
+}
+static gboolean
+gtk_selection_model_default_unselect_item (GtkSelectionModel *model,
+ guint position)
+{
+ return FALSE;
+}
+
+static gboolean
+gtk_selection_model_default_select_range (GtkSelectionModel *model,
+ guint position,
+ guint n_items,
+ gboolean exclusive)
+{
+ return FALSE;
+}
+
+static gboolean
+gtk_selection_model_default_unselect_range (GtkSelectionModel *model,
+ guint position,
+ guint n_items)
+{
+ return FALSE;
+}
+
+static gboolean
+gtk_selection_model_default_select_all (GtkSelectionModel *model)
+{
+ return gtk_selection_model_select_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)), FALSE);
+}
+
+static gboolean
+gtk_selection_model_default_unselect_all (GtkSelectionModel *model)
+{
+ return gtk_selection_model_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)));;
+}
+
+static gboolean
+gtk_selection_model_default_query_range (GtkSelectionModel *model,
+ guint *position,
+ guint *n_items)
+{
+ *n_items = 1;
+ return gtk_selection_model_is_selected (model, *position);
+}
+
+static void
+gtk_selection_model_default_init (GtkSelectionModelInterface *iface)
+{
+ iface->is_selected = gtk_selection_model_default_is_selected;
+ iface->select_item = gtk_selection_model_default_select_item;
+ iface->unselect_item = gtk_selection_model_default_unselect_item;
+ iface->select_range = gtk_selection_model_default_select_range;
+ iface->unselect_range = gtk_selection_model_default_unselect_range;
+ iface->select_all = gtk_selection_model_default_select_all;
+ iface->unselect_all = gtk_selection_model_default_unselect_all;
+ iface->query_range = gtk_selection_model_default_query_range;
+
+ /**
+ * GtkSelectionModel::selection-changed
+ * @model: a #GtkSelectionModel
+ * @position: The first item that may have changed
+ * @n_items: number of items with changes
+ *
+ * Emitted when the selection state of some of the items in @model changes.
+ *
+ * Note that this signal does not specify the new selection state of the items,
+ * they need to be queried manually.
+ * It is also not necessary for a model to change the selection state of any of
+ * the items in the selection model, though it would be rather useless to emit
+ * such a signal.
+ */
+ signals[SELECTION_CHANGED] =
+ g_signal_new ("selection-changed",
+ GTK_TYPE_SELECTION_MODEL,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ _gtk_marshal_VOID__UINT_UINT,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT);
+ g_signal_set_va_marshaller (signals[SELECTION_CHANGED],
+ GTK_TYPE_SELECTION_MODEL,
+ _gtk_marshal_VOID__UINT_UINTv);
+}
+
+/**
+ * gtk_selection_model_is_selected:
+ * @model: a #GtkSelectionModel
+ * @position: the position of the item to query
+ *
+ * Checks if the given item is selected.
+ *
+ * Returns: %TRUE if the item is selected
+ **/
+gboolean
+gtk_selection_model_is_selected (GtkSelectionModel *model,
+ guint position)
+{
+ GtkSelectionModelInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
+
+ iface = GTK_SELECTION_MODEL_GET_IFACE (model);
+ return iface->is_selected (model, position);
+}
+
+gboolean
+gtk_selection_model_select_item (GtkSelectionModel *model,
+ guint position,
+ gboolean exclusive)
+{
+ GtkSelectionModelInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
+
+ iface = GTK_SELECTION_MODEL_GET_IFACE (model);
+ return iface->select_item (model, position, exclusive);
+}
+
+gboolean
+gtk_selection_model_unselect_item (GtkSelectionModel *model,
+ guint position)
+{
+ GtkSelectionModelInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
+
+ iface = GTK_SELECTION_MODEL_GET_IFACE (model);
+ return iface->unselect_item (model, position);
+}
+
+gboolean
+gtk_selection_model_select_range (GtkSelectionModel *model,
+ guint position,
+ guint n_items,
+ gboolean exclusive)
+{
+ GtkSelectionModelInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
+
+ iface = GTK_SELECTION_MODEL_GET_IFACE (model);
+ return iface->select_range (model, position, n_items, exclusive);
+}
+
+gboolean
+gtk_selection_model_unselect_range (GtkSelectionModel *model,
+ guint position,
+ guint n_items)
+{
+ GtkSelectionModelInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
+
+ iface = GTK_SELECTION_MODEL_GET_IFACE (model);
+ return iface->unselect_range (model, position, n_items);
+}
+
+gboolean
+gtk_selection_model_select_all (GtkSelectionModel *model)
+{
+ GtkSelectionModelInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
+
+ iface = GTK_SELECTION_MODEL_GET_IFACE (model);
+ return iface->select_all (model);
+}
+
+gboolean
+gtk_selection_model_unselect_all (GtkSelectionModel *model)
+{
+ GtkSelectionModelInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0);
+
+ iface = GTK_SELECTION_MODEL_GET_IFACE (model);
+ return iface->unselect_all (model);
+}
+
+/**
+ * gtk_selection_model_query_range:
+ * @model: a #GtkSelectionModel
+ * @position: (inout): specifies the position on input, and the first element of the range on output
+ * @n_items: (out): returns the size of the range
+ *
+ * This function allows to query the selection status of multiple elements at once.
+ * It is passed a position and returns a range of elements of uniform selection status.
+ * The returned range is guaranteed to include the passed-in position.
+ * The selection status is returned from this function.
+ *
+ * Returns: %TRUE if the elements in the returned range are selected, %FALSE otherwise
+ */
+gboolean
+gtk_selection_model_query_range (GtkSelectionModel *model,
+ guint *position,
+ guint *n_items)
+{
+ GtkSelectionModelInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE);
+
+ iface = GTK_SELECTION_MODEL_GET_IFACE (model);
+ return iface->query_range (model, position, n_items);
+}
+
+void
+gtk_selection_model_selection_changed (GtkSelectionModel *model,
+ guint position,
+ guint n_items)
+{
+ g_return_if_fail (GTK_IS_SELECTION_MODEL (model));
+
+ g_signal_emit (model, signals[SELECTION_CHANGED], 0, position, n_items);
+}
+
diff --git a/gtk/gtkselectionmodel.h b/gtk/gtkselectionmodel.h
new file mode 100644
index 0000000000..65c424716d
--- /dev/null
+++ b/gtk/gtkselectionmodel.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * 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.1 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, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#ifndef __GTK_SELECTION_MODEL_H__
+#define __GTK_SELECTION_MODEL_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#include
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SELECTION_MODEL (gtk_selection_model_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_INTERFACE (GtkSelectionModel, gtk_selection_model, GTK, SELECTION_MODEL, GListModel)
+
+/**
+ * GtkSelectionModelInterface:
+ * @is_selected: Return if the item at the given position is selected.
+ * @select_item: Select the item in the given position. If the operation
+ * is known to fail, return %FALSE.
+ * @unselect_item: Unselect the item in the given position. If the
+ * operation is known to fail, return %FALSE.
+ * @select_range: Select all items in the given range. If the operation
+ * is unsupported or known to fail for all items, return %FALSE.
+ * @unselect_range: Unselect all items in the given range. If the
+ * operation is unsupported or known to fail for all items, return
+ * %FALSE.
+ * @select_all: Select all items in the model. If the operation is
+ * unsupported or known to fail for all items, return %FALSE.
+ * @unselect_all: Unselect all items in the model. If the operation is
+ * unsupported or known to fail for all items, return %FALSE.
+ *
+ * The list of virtual functions for the #GtkSelectionModel interface.
+ * All getter functions are mandatory to implement, but the model does
+ * not need to implement any functions to support selecting or unselecting
+ * items. Of course, if the model does not do that, it means that users
+ * cannot select or unselect items in a list widgets using the model.
+ */
+struct _GtkSelectionModelInterface
+{
+ /*< private >*/
+ GTypeInterface g_iface;
+
+ /*< public >*/
+ gboolean (* is_selected) (GtkSelectionModel *model,
+ guint position);
+
+ gboolean (* select_item) (GtkSelectionModel *model,
+ guint position,
+ gboolean exclusive);
+ gboolean (* unselect_item) (GtkSelectionModel *model,
+ guint position);
+ gboolean (* select_range) (GtkSelectionModel *model,
+ guint position,
+ guint n_items,
+ gboolean exclusive);
+ gboolean (* unselect_range) (GtkSelectionModel *model,
+ guint position,
+ guint n_items);
+ gboolean (* select_all) (GtkSelectionModel *model);
+ gboolean (* unselect_all) (GtkSelectionModel *model);
+ gboolean (* query_range) (GtkSelectionModel *model,
+ guint *position,
+ guint *n_items);
+};
+
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_selection_model_is_selected (GtkSelectionModel *model,
+ guint position);
+
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_selection_model_select_item (GtkSelectionModel *model,
+ guint position,
+ gboolean exclusive);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_selection_model_unselect_item (GtkSelectionModel *model,
+ guint position);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_selection_model_select_range (GtkSelectionModel *model,
+ guint position,
+ guint n_items,
+ gboolean exclusive);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_selection_model_unselect_range (GtkSelectionModel *model,
+ guint position,
+ guint n_items);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_selection_model_select_all (GtkSelectionModel *model);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_selection_model_unselect_all (GtkSelectionModel *model);
+
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_selection_model_query_range (GtkSelectionModel *model,
+ guint *position,
+ guint *n_items);
+
+/* for implementations only */
+GDK_AVAILABLE_IN_ALL
+void gtk_selection_model_selection_changed (GtkSelectionModel *model,
+ guint position,
+ guint n_items);
+
+G_END_DECLS
+
+#endif /* __GTK_SELECTION_MODEL_H__ */
diff --git a/gtk/gtksingleselection.c b/gtk/gtksingleselection.c
new file mode 100644
index 0000000000..17ae761c73
--- /dev/null
+++ b/gtk/gtksingleselection.c
@@ -0,0 +1,637 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * 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.1 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, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#include "config.h"
+
+#include "gtksingleselection.h"
+
+#include "gtkintl.h"
+#include "gtkselectionmodel.h"
+
+/**
+ * SECTION:gtksingleselection
+ * @Short_description: A selection model that allows selecting a single item
+ * @Title: GtkSingleSelection
+ * @see_also: #GtkSelectionModel
+ *
+ * GtkSingleSelection is an implementation of the #GtkSelectionModel interface
+ * that allows selecting a single element. It is the default selection method
+ * used by list widgets in GTK.
+ */
+struct _GtkSingleSelection
+{
+ GObject parent_instance;
+
+ GListModel *model;
+ guint selected;
+ gpointer selected_item;
+
+ guint autoselect : 1;
+ guint can_unselect : 1;
+};
+
+struct _GtkSingleSelectionClass
+{
+ GObjectClass parent_class;
+};
+
+enum {
+ PROP_0,
+ PROP_AUTOSELECT,
+ PROP_CAN_UNSELECT,
+ PROP_SELECTED,
+ PROP_SELECTED_ITEM,
+ PROP_MODEL,
+ N_PROPS
+};
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static GType
+gtk_single_selection_get_item_type (GListModel *list)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
+
+ return g_list_model_get_item_type (self->model);
+}
+
+static guint
+gtk_single_selection_get_n_items (GListModel *list)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
+
+ return g_list_model_get_n_items (self->model);
+}
+
+static gpointer
+gtk_single_selection_get_item (GListModel *list,
+ guint position)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (list);
+
+ return g_list_model_get_item (self->model, position);
+}
+
+static void
+gtk_single_selection_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtk_single_selection_get_item_type;
+ iface->get_n_items = gtk_single_selection_get_n_items;
+ iface->get_item = gtk_single_selection_get_item;
+}
+
+static gboolean
+gtk_single_selection_is_selected (GtkSelectionModel *model,
+ guint position)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
+
+ return self->selected == position;
+}
+
+static gboolean
+gtk_single_selection_select_item (GtkSelectionModel *model,
+ guint position,
+ gboolean exclusive)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
+
+ /* XXX: Should we check that position < n_items here? */
+ gtk_single_selection_set_selected (self, position);
+
+ return TRUE;
+}
+
+static gboolean
+gtk_single_selection_unselect_item (GtkSelectionModel *model,
+ guint position)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
+
+ if (!self->can_unselect)
+ return FALSE;
+
+ if (self->selected == position)
+ gtk_single_selection_set_selected (self, GTK_INVALID_LIST_POSITION);
+
+ return TRUE;
+}
+
+static gboolean
+gtk_single_selection_query_range (GtkSelectionModel *model,
+ guint *position,
+ guint *n_items)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (model);
+
+ if (self->selected == GTK_INVALID_LIST_POSITION)
+ {
+ *position = 0;
+ *n_items = g_list_model_get_n_items (self->model);
+ return FALSE;
+ }
+ else if (*position < self->selected)
+ {
+ *position = 0;
+ *n_items = self->selected;
+ return FALSE;
+ }
+ else if (*position > self->selected)
+ {
+ *position = self->selected + 1;
+ *n_items = g_list_model_get_n_items (self->model) - *position;
+ return FALSE;
+ }
+ else
+ {
+ *position = self->selected;
+ *n_items = 1;
+ return TRUE;
+ }
+}
+
+static void
+gtk_single_selection_selection_model_init (GtkSelectionModelInterface *iface)
+{
+ iface->is_selected = gtk_single_selection_is_selected;
+ iface->select_item = gtk_single_selection_select_item;
+ iface->unselect_item = gtk_single_selection_unselect_item;
+ iface->query_range = gtk_single_selection_query_range;
+}
+
+G_DEFINE_TYPE_EXTENDED (GtkSingleSelection, gtk_single_selection, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
+ gtk_single_selection_list_model_init)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL,
+ gtk_single_selection_selection_model_init))
+
+static void
+gtk_single_selection_items_changed_cb (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ GtkSingleSelection *self)
+{
+ gboolean emit_selection_changed = FALSE;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ if (self->selected_item == NULL)
+ {
+ if (self->autoselect)
+ {
+ self->selected_item = g_list_model_get_item (self->model, 0);
+ if (self->selected_item)
+ {
+ self->selected = 0;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED_ITEM]);
+ }
+ }
+ }
+ else if (self->selected < position)
+ {
+ /* unchanged */
+ }
+ else if (self->selected >= position + removed)
+ {
+ self->selected += added - removed;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED_ITEM]);
+ }
+ else
+ {
+ guint i;
+
+ for (i = 0; i < added; i++)
+ {
+ gpointer item = g_list_model_get_item (model, position + i);
+ if (item == self->selected_item)
+ {
+ /* the item moved */
+ if (self->selected != position + i)
+ {
+ self->selected = position + i;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED_ITEM]);
+ }
+ break;
+ }
+ }
+ if (i == added)
+ {
+ /* the item really was deleted */
+ g_clear_object (&self->selected_item);
+ if (self->autoselect)
+ {
+ self->selected = position + (self->selected - position) * added / removed;
+ self->selected_item = g_list_model_get_item (self->model, self->selected);
+ if (self->selected_item == NULL)
+ {
+ if (position > 0)
+ {
+ self->selected = position - 1;
+ self->selected_item = g_list_model_get_item (self->model, self->selected);
+ g_assert (self->selected_item);
+ }
+ else
+ self->selected = GTK_INVALID_LIST_POSITION;
+ }
+
+ emit_selection_changed = TRUE;
+ }
+ else
+ {
+ g_clear_object (&self->selected_item);
+ self->selected = GTK_INVALID_LIST_POSITION;
+ }
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED_ITEM]);
+ }
+ }
+
+ g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added);
+
+ if (emit_selection_changed && self->selected != GTK_INVALID_LIST_POSITION)
+ gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), self->selected, 1);
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+static void
+gtk_single_selection_clear_model (GtkSingleSelection *self)
+{
+ if (self->model == NULL)
+ return;
+
+ g_signal_handlers_disconnect_by_func (self->model,
+ gtk_single_selection_items_changed_cb,
+ self);
+ g_clear_object (&self->model);
+}
+
+static void
+gtk_single_selection_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_AUTOSELECT:
+ gtk_single_selection_set_autoselect (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_CAN_UNSELECT:
+ gtk_single_selection_set_can_unselect (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_MODEL:
+ gtk_single_selection_clear_model (self);
+ self->model = g_value_dup_object (value);
+ if (self->model)
+ g_signal_connect (self->model, "items-changed",
+ G_CALLBACK (gtk_single_selection_items_changed_cb), self);
+ if (self->autoselect)
+ gtk_single_selection_set_selected (self, 0);
+ break;
+
+ case PROP_SELECTED:
+ gtk_single_selection_set_selected (self, g_value_get_uint (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_single_selection_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (object);
+
+ switch (prop_id)
+ {
+ case PROP_AUTOSELECT:
+ g_value_set_boolean (value, self->autoselect);
+ break;
+
+ case PROP_CAN_UNSELECT:
+ g_value_set_boolean (value, self->can_unselect);
+ break;
+ case PROP_MODEL:
+ g_value_set_object (value, self->model);
+ break;
+
+ case PROP_SELECTED:
+ g_value_set_uint (value, self->selected);
+ break;
+
+ case PROP_SELECTED_ITEM:
+ g_value_set_object (value, self->selected_item);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_single_selection_dispose (GObject *object)
+{
+ GtkSingleSelection *self = GTK_SINGLE_SELECTION (object);
+
+ gtk_single_selection_clear_model (self);
+
+ self->selected = GTK_INVALID_LIST_POSITION;
+ g_clear_object (&self->selected_item);
+
+ G_OBJECT_CLASS (gtk_single_selection_parent_class)->dispose (object);
+}
+
+static void
+gtk_single_selection_class_init (GtkSingleSelectionClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = gtk_single_selection_get_property;
+ gobject_class->set_property = gtk_single_selection_set_property;
+ gobject_class->dispose = gtk_single_selection_dispose;
+
+ /**
+ * GtkSingleSelection:autoselect:
+ *
+ * If the selection will always select an item
+ */
+ properties[PROP_AUTOSELECT] =
+ g_param_spec_boolean ("autoselect",
+ P_("Autoselect"),
+ P_("If the selection will always select an item"),
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtkSingleSelection:can-unselect:
+ *
+ * If unselecting the selected item is allowed
+ */
+ properties[PROP_CAN_UNSELECT] =
+ g_param_spec_boolean ("can-unselect",
+ P_("Can unselect"),
+ P_("If unselecting the selected item is allowed"),
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtkSingleSelection:selected:
+ *
+ * Position of the selected item
+ */
+ properties[PROP_SELECTED] =
+ g_param_spec_uint ("selected",
+ P_("Selected"),
+ P_("Position of the selected item"),
+ 0, G_MAXUINT, GTK_INVALID_LIST_POSITION,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtkSingleSelection:selected-item:
+ *
+ * The selected item
+ */
+ properties[PROP_SELECTED_ITEM] =
+ g_param_spec_object ("selected-item",
+ P_("Selected Item"),
+ P_("The selected item"),
+ G_TYPE_OBJECT,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_MODEL] =
+ g_param_spec_object ("model",
+ P_("The model"),
+ P_("The model"),
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
+}
+
+static void
+gtk_single_selection_init (GtkSingleSelection *self)
+{
+ self->selected = GTK_INVALID_LIST_POSITION;
+ self->autoselect = TRUE;
+}
+
+/**
+ * gtk_single_selection_new:
+ * @model: (transfer none): the #GListModel to manage
+ *
+ * Creates a new selection to handle @model.
+ *
+ * Returns: (transfer full) (type GtkSingleSelection): a new #GtkSingleSelection
+ **/
+GtkSingleSelection *
+gtk_single_selection_new (GListModel *model)
+{
+ g_return_val_if_fail (G_IS_LIST_MODEL (model), NULL);
+
+ return g_object_new (GTK_TYPE_SINGLE_SELECTION,
+ "model", model,
+ NULL);
+}
+
+/**
+ * gtk_single_selection_get_selected:
+ * @self: a #GtkSingleSelection
+ *
+ * Gets the position of the selected item. If no item is selected,
+ * #GTK_INVALID_LIST_POSITION is returned.
+ *
+ * Returns: The position of the selected item
+ **/
+guint
+gtk_single_selection_get_selected (GtkSingleSelection *self)
+{
+ g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), GTK_INVALID_LIST_POSITION);
+
+ return self->selected;
+}
+
+/**
+ * gtk_single_selection_set_selected:
+ * @self: a #GtkSingleSelection
+ * @position: the item to select or #GTK_INVALID_LIST_POSITION
+ *
+ * Selects the item at the given position. If the list does not have an item at
+ * @position or #GTK_INVALID_LIST_POSITION is given, the behavior depends on the
+ * value of the GtkSingleSelection:autoselect property: If it is set, no change
+ * will occur and the old item will stay selected. If it is unset, the selection
+ * will be unset and no item will be selected.
+ **/
+void
+gtk_single_selection_set_selected (GtkSingleSelection *self,
+ guint position)
+{
+ gpointer new_selected = NULL;
+ guint old_position;
+
+ g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
+
+ if (self->selected == position)
+ return;
+
+ if (self->model)
+ new_selected = g_list_model_get_item (self->model, position);
+
+ if (new_selected == NULL)
+ position = GTK_INVALID_LIST_POSITION;
+
+ if (self->selected == position)
+ return;
+
+ old_position = self->selected;
+ self->selected = position;
+ g_clear_object (&self->selected_item);
+ self->selected_item = new_selected;
+
+ if (old_position == GTK_INVALID_LIST_POSITION)
+ gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), position, 1);
+ else if (position == GTK_INVALID_LIST_POSITION)
+ gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), old_position, 1);
+ else if (position < old_position)
+ gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), position, old_position - position + 1);
+ else
+ gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), old_position, position - old_position + 1);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SELECTED]);
+}
+
+/**
+ * gtk_single_selection_get_selected_item:
+ * @self: a #GtkSingleSelection
+ *
+ * Gets the selected item. If no item is selected, %NULL is returned.
+ *
+ * Returns: (transfer none): The selected item
+ */
+gpointer
+gtk_single_selection_get_selected_item (GtkSingleSelection *self)
+{
+ g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), NULL);
+
+ return self->selected_item;
+}
+
+/**
+ * gtk_single_selection_get_autoselect:
+ * @self: a #GtkSingleSelection
+ *
+ * Checks if autoselect has been enabled or disabled via
+ * gtk_single_selection_set_autoselect().
+ *
+ * Returns: %TRUE if autoselect is enabled
+ **/
+gboolean
+gtk_single_selection_get_autoselect (GtkSingleSelection *self)
+{
+ g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), TRUE);
+
+ return self->autoselect;
+}
+
+/**
+ * gtk_single_selection_set_autoselect:
+ * @self: a #GtkSingleSelection
+ * @autoselect: %TRUE to always select an item
+ *
+ * If @autoselect is %TRUE, @self will enforce that an item is always
+ * selected. It will select a new item when the currently selected
+ * item is deleted and it will disallow unselecting the current item.
+ **/
+void
+gtk_single_selection_set_autoselect (GtkSingleSelection *self,
+ gboolean autoselect)
+{
+ g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
+
+ if (self->autoselect == autoselect)
+ return;
+
+ self->autoselect = autoselect;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_AUTOSELECT]);
+
+ if (self->autoselect && !self->selected_item)
+ gtk_single_selection_set_selected (self, 0);
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * gtk_single_selection_get_can_unselect:
+ * @self: a #GtkSingleSelection
+ *
+ * If %TRUE, gtk_selection_model_unselect_item() is supported and allows
+ * unselecting the selected item.
+ *
+ * Returns: %TRUE to support unselecting
+ **/
+gboolean
+gtk_single_selection_get_can_unselect (GtkSingleSelection *self)
+{
+ g_return_val_if_fail (GTK_IS_SINGLE_SELECTION (self), FALSE);
+
+ return self->can_unselect;
+}
+
+/**
+ * gtk_single_selection_set_can_unselect:
+ * @self: a #GtkSingleSelection
+ * @can_unselect: %TRUE to allow unselecting
+ *
+ * If %TRUE, unselecting the current item via
+ * gtk_selection_model_unselect_item() is supported.
+ *
+ * Note that setting GtkSingleSelection:autoselect will cause the
+ * unselecting to not work, so it practically makes no sense to set
+ * both at the same time the same time..
+ **/
+void
+gtk_single_selection_set_can_unselect (GtkSingleSelection *self,
+ gboolean can_unselect)
+{
+ g_return_if_fail (GTK_IS_SINGLE_SELECTION (self));
+
+ if (self->can_unselect == can_unselect)
+ return;
+
+ self->can_unselect = can_unselect;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CAN_UNSELECT]);
+}
diff --git a/gtk/gtksingleselection.h b/gtk/gtksingleselection.h
new file mode 100644
index 0000000000..ca96a4e052
--- /dev/null
+++ b/gtk/gtksingleselection.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * 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.1 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, see .
+ *
+ * Authors: Benjamin Otte
+ */
+
+#ifndef __GTK_SINGLE_SELECTION_H__
+#define __GTK_SINGLE_SELECTION_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SINGLE_SELECTION (gtk_single_selection_get_type ())
+
+/**
+ * GTK_INVALID_LIST_POSITION:
+ *
+ * The value used to refer to a guaranteed invalid position in a #GListModel. This
+ * value may be returned from some functions, others may accept it as input.
+ * Its interpretion may differ for different functions.
+ *
+ * Refer to each function's documentation for if this value is allowed and what it
+ * does.
+ */
+#define GTK_INVALID_LIST_POSITION (G_MAXUINT)
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkSingleSelection, gtk_single_selection, GTK, SINGLE_SELECTION, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkSingleSelection * gtk_single_selection_new (GListModel *model);
+
+GDK_AVAILABLE_IN_ALL
+guint gtk_single_selection_get_selected (GtkSingleSelection *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_single_selection_set_selected (GtkSingleSelection *self,
+ guint position);
+GDK_AVAILABLE_IN_ALL
+gpointer gtk_single_selection_get_selected_item (GtkSingleSelection *self);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_single_selection_get_autoselect (GtkSingleSelection *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_single_selection_set_autoselect (GtkSingleSelection *self,
+ gboolean autoselect);
+GDK_AVAILABLE_IN_ALL
+gboolean gtk_single_selection_get_can_unselect (GtkSingleSelection *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_single_selection_set_can_unselect (GtkSingleSelection *self,
+ gboolean can_unselect);
+
+G_END_DECLS
+
+#endif /* __GTK_SINGLE_SELECTION_H__ */
diff --git a/gtk/gtkstack.c b/gtk/gtkstack.c
index 338199c319..2030e919f5 100644
--- a/gtk/gtkstack.c
+++ b/gtk/gtkstack.c
@@ -30,6 +30,8 @@
#include "gtksettingsprivate.h"
#include "gtksnapshot.h"
#include "gtkwidgetprivate.h"
+#include "gtksingleselection.h"
+#include "gtklistlistmodelprivate.h"
#include "a11y/gtkstackaccessible.h"
#include "a11y/gtkstackaccessibleprivate.h"
#include
@@ -138,6 +140,8 @@ typedef struct {
GtkStackTransitionType active_transition_type;
+ GtkSelectionModel *pages;
+
} GtkStackPrivate;
static void gtk_stack_buildable_interface_init (GtkBuildableIface *iface);
@@ -167,8 +171,8 @@ enum
CHILD_PROP_NAME,
CHILD_PROP_TITLE,
CHILD_PROP_ICON_NAME,
- CHILD_PROP_POSITION,
CHILD_PROP_NEEDS_ATTENTION,
+ CHILD_PROP_VISIBLE,
LAST_CHILD_PROP
};
@@ -179,6 +183,7 @@ struct _GtkStackPage {
gchar *title;
gchar *icon_name;
gboolean needs_attention;
+ gboolean visible;
GtkWidget *last_focus;
};
@@ -194,6 +199,7 @@ G_DEFINE_TYPE (GtkStackPage, gtk_stack_page, G_TYPE_OBJECT)
static void
gtk_stack_page_init (GtkStackPage *page)
{
+ page->visible = TRUE;
}
static void
@@ -239,32 +245,20 @@ gtk_stack_page_get_property (GObject *object,
g_value_set_string (value, info->icon_name);
break;
- case CHILD_PROP_POSITION:
- if (info->widget)
- {
- GtkWidget *stack = gtk_widget_get_parent (GTK_WIDGET (info->widget));
- GtkStackPrivate *priv = gtk_stack_get_instance_private (GTK_STACK (stack));
-
- g_value_set_int (value, g_list_index (priv->children, info));
- }
- else
- g_value_set_int (value, 0);
- break;
-
case CHILD_PROP_NEEDS_ATTENTION:
g_value_set_boolean (value, info->needs_attention);
break;
+ case CHILD_PROP_VISIBLE:
+ g_value_set_boolean (value, info->visible);
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
-static void reorder_child (GtkStack *stack,
- GtkWidget *child,
- gint position);
-
static void
gtk_stack_page_set_property (GObject *object,
guint property_id,
@@ -327,11 +321,6 @@ gtk_stack_page_set_property (GObject *object,
g_object_notify_by_pspec (object, pspec);
break;
- case CHILD_PROP_POSITION:
- if (stack)
- reorder_child (GTK_STACK (stack), info->widget, g_value_get_int (value));
- break;
-
case CHILD_PROP_NEEDS_ATTENTION:
if (info->needs_attention != g_value_get_boolean (value))
{
@@ -340,6 +329,16 @@ gtk_stack_page_set_property (GObject *object,
}
break;
+ case CHILD_PROP_VISIBLE:
+ if (info->visible != g_value_get_boolean (value))
+ {
+ info->visible = g_value_get_boolean (value);
+ if (info->widget)
+ gtk_widget_set_visible (info->widget, info->visible);
+ g_object_notify_by_pspec (object, pspec);
+ }
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
@@ -382,14 +381,6 @@ gtk_stack_page_class_init (GtkStackPageClass *class)
NULL,
GTK_PARAM_READWRITE);
- stack_child_props[CHILD_PROP_POSITION] =
- g_param_spec_int ("position",
- P_("Position"),
- P_("The index of the child in the parent"),
- -1, G_MAXINT,
- 0,
- GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-
/**
* GtkStack:needs-attention:
*
@@ -405,9 +396,134 @@ gtk_stack_page_class_init (GtkStackPageClass *class)
FALSE,
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+ stack_child_props[CHILD_PROP_VISIBLE] =
+ g_param_spec_boolean ("visible",
+ P_("Visible"),
+ P_("Whether this page is visible"),
+ TRUE,
+ GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
g_object_class_install_properties (object_class, LAST_CHILD_PROP, stack_child_props);
}
+#define GTK_TYPE_STACK_PAGES (gtk_stack_pages_get_type ())
+G_DECLARE_FINAL_TYPE (GtkStackPages, gtk_stack_pages, GTK, STACK_PAGES, GObject)
+
+struct _GtkStackPages
+{
+ GObject parent_instance;
+ GtkStack *stack;
+};
+
+struct _GtkStackPagesClass
+{
+ GObjectClass parent_class;
+};
+
+static GType
+gtk_stack_pages_get_item_type (GListModel *model)
+{
+ return GTK_TYPE_STACK_PAGE;
+}
+
+static guint
+gtk_stack_pages_get_n_items (GListModel *model)
+{
+ GtkStackPages *pages = GTK_STACK_PAGES (model);
+ GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack);
+
+ return g_list_length (priv->children);
+}
+
+static gpointer
+gtk_stack_pages_get_item (GListModel *model,
+ guint position)
+{
+ GtkStackPages *pages = GTK_STACK_PAGES (model);
+ GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack);
+ GtkStackPage *page;
+
+ page = g_list_nth_data (priv->children, position);
+
+ return g_object_ref (page);
+}
+
+static void
+gtk_stack_pages_list_model_init (GListModelInterface *iface)
+{
+ iface->get_item_type = gtk_stack_pages_get_item_type;
+ iface->get_n_items = gtk_stack_pages_get_n_items;
+ iface->get_item = gtk_stack_pages_get_item;
+}
+
+static gboolean
+gtk_stack_pages_is_selected (GtkSelectionModel *model,
+ guint position)
+{
+ GtkStackPages *pages = GTK_STACK_PAGES (model);
+ GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack);
+ GtkStackPage *page;
+
+ page = GTK_STACK_PAGE (g_list_model_get_item (G_LIST_MODEL (model), position));
+ g_object_unref (page);
+
+ return page == priv->visible_child;
+}
+
+static void set_visible_child (GtkStack *stack,
+ GtkStackPage *child_info,
+ GtkStackTransitionType transition_type,
+ guint transition_duration);
+
+static gboolean
+gtk_stack_pages_select_item (GtkSelectionModel *model,
+ guint position,
+ gboolean exclusive)
+{
+ GtkStackPages *pages = GTK_STACK_PAGES (model);
+ GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack);
+ GtkStackPage *page;
+
+ page = GTK_STACK_PAGE (g_list_model_get_item (G_LIST_MODEL (model), position));
+ g_object_unref (page);
+
+ set_visible_child (pages->stack, page, priv->transition_type, priv->transition_duration);
+
+ return TRUE;
+}
+
+static void
+gtk_stack_pages_selection_model_init (GtkSelectionModelInterface *iface)
+{
+ iface->is_selected = gtk_stack_pages_is_selected;
+ iface->select_item = gtk_stack_pages_select_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkStackPages, gtk_stack_pages, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_stack_pages_list_model_init)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, gtk_stack_pages_selection_model_init))
+
+static void
+gtk_stack_pages_init (GtkStackPages *pages)
+{
+}
+
+static void
+gtk_stack_pages_class_init (GtkStackPagesClass *class)
+{
+}
+
+static GtkStackPages *
+gtk_stack_pages_new (GtkStack *stack)
+{
+ GtkStackPages *pages;
+
+ pages = g_object_new (GTK_TYPE_STACK_PAGES, NULL);
+ pages->stack = stack;
+
+ return pages;
+}
+
static void gtk_stack_add (GtkContainer *widget,
GtkWidget *child);
static void gtk_stack_remove (GtkContainer *widget,
@@ -431,6 +547,7 @@ static void gtk_stack_measure (GtkWidget *widget
int *natural,
int *minimum_baseline,
int *natural_baseline);
+static void gtk_stack_dispose (GObject *obj);
static void gtk_stack_finalize (GObject *obj);
static void gtk_stack_get_property (GObject *object,
guint property_id,
@@ -466,6 +583,29 @@ gtk_stack_buildable_interface_init (GtkBuildableIface *iface)
iface->add_child = gtk_stack_buildable_add_child;
}
+static void stack_remove (GtkStack *stack,
+ GtkWidget *child,
+ gboolean in_dispose);
+
+static void
+remove_child (GtkWidget *child, gpointer user_data)
+{
+ stack_remove (GTK_STACK (user_data), child, TRUE);
+}
+
+static void
+gtk_stack_dispose (GObject *obj)
+{
+ GtkStack *stack = GTK_STACK (obj);
+ GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
+
+ if (priv->pages)
+ g_list_model_items_changed (G_LIST_MODEL (priv->pages), 0, g_list_length (priv->children), 0);
+ gtk_container_foreach (GTK_CONTAINER (obj), remove_child, obj);
+
+ G_OBJECT_CLASS (gtk_stack_parent_class)->dispose (obj);
+}
+
static void
gtk_stack_finalize (GObject *obj)
{
@@ -571,6 +711,7 @@ gtk_stack_class_init (GtkStackClass *klass)
object_class->get_property = gtk_stack_get_property;
object_class->set_property = gtk_stack_set_property;
+ object_class->dispose = gtk_stack_dispose;
object_class->finalize = gtk_stack_finalize;
widget_class->size_allocate = gtk_stack_size_allocate;
@@ -670,59 +811,6 @@ find_child_info_for_widget (GtkStack *stack,
return NULL;
}
-static void
-reorder_child (GtkStack *stack,
- GtkWidget *child,
- gint position)
-{
- GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
- GList *l;
- GList *old_link = NULL;
- GList *new_link = NULL;
- GtkStackPage *child_info = NULL;
- gint num = 0;
-
- l = priv->children;
-
- /* Loop to find the old position and link of child, new link of child and
- * total number of children. new_link will be NULL if the child should be
- * moved to the end (in case of position being < 0 || >= num)
- */
- while (l && (new_link == NULL || old_link == NULL))
- {
- /* Record the new position if found */
- if (position == num)
- new_link = l;
-
- if (old_link == NULL)
- {
- GtkStackPage *info;
- info = l->data;
-
- /* Keep trying to find the current position and link location of the child */
- if (info->widget == child)
- {
- old_link = l;
- child_info = info;
- }
- }
-
- l = l->next;
- num++;
- }
-
- g_return_if_fail (old_link != NULL);
-
- if (old_link == new_link || (old_link->next == NULL && new_link == NULL))
- return;
-
- priv->children = g_list_delete_link (priv->children, old_link);
- priv->children = g_list_insert_before (priv->children, new_link, child_info);
-
- gtk_container_child_notify_by_pspec (GTK_CONTAINER (stack), child, stack_child_props[CHILD_PROP_POSITION]);
-}
-
-
static inline gboolean
is_left_transition (GtkStackTransitionType transition_type)
{
@@ -1028,6 +1116,8 @@ set_visible_child (GtkStack *stack,
GtkWidget *toplevel;
GtkWidget *focus;
gboolean contains_focus = FALSE;
+ guint old_pos = 0;
+ guint new_pos = 0;
/* if we are being destroyed, do not bother with transitions
* and notifications
@@ -1052,6 +1142,22 @@ set_visible_child (GtkStack *stack,
if (child_info == priv->visible_child)
return;
+ if (child_info == NULL)
+ return;
+
+ if (priv->pages)
+ {
+ guint position;
+ for (l = priv->children, position = 0; l != NULL; l = l->next, position++)
+ {
+ info = l->data;
+ if (info == priv->visible_child)
+ old_pos = position;
+ else if (info == child_info)
+ new_pos = position;
+ }
+ }
+
toplevel = gtk_widget_get_toplevel (widget);
if (GTK_IS_WINDOW (toplevel))
{
@@ -1145,6 +1251,11 @@ set_visible_child (GtkStack *stack,
g_object_notify_by_pspec (G_OBJECT (stack),
stack_props[PROP_VISIBLE_CHILD_NAME]);
+ if (priv->pages)
+ gtk_selection_model_selection_changed (priv->pages,
+ MIN (old_pos, new_pos),
+ MAX (old_pos, new_pos) - MIN (old_pos, new_pos) + 1);
+
gtk_stack_start_transition (stack, transition_type, transition_duration);
}
@@ -1281,11 +1392,12 @@ gtk_stack_add_page (GtkStack *stack,
gtk_widget_set_child_visible (child_info->widget, FALSE);
gtk_widget_set_parent (child_info->widget, GTK_WIDGET (stack));
+ if (priv->pages)
+ g_list_model_items_changed (G_LIST_MODEL (priv->pages), g_list_length (priv->children) - 1, 0, 1);
+
g_signal_connect (child_info->widget, "notify::visible",
G_CALLBACK (stack_child_visibility_notify_cb), stack);
- g_object_notify_by_pspec (G_OBJECT (child_info), stack_child_props[CHILD_PROP_POSITION]);
-
if (priv->visible_child == NULL &&
gtk_widget_get_visible (child_info->widget))
set_visible_child (stack, child_info, priv->transition_type, priv->transition_duration);
@@ -1295,10 +1407,10 @@ gtk_stack_add_page (GtkStack *stack,
}
static void
-gtk_stack_remove (GtkContainer *container,
- GtkWidget *child)
+stack_remove (GtkStack *stack,
+ GtkWidget *child,
+ gboolean in_dispose)
{
- GtkStack *stack = GTK_STACK (container);
GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
GtkStackPage *child_info;
gboolean was_visible;
@@ -1308,7 +1420,7 @@ gtk_stack_remove (GtkContainer *container,
return;
priv->children = g_list_remove (priv->children, child_info);
-
+
g_signal_handlers_disconnect_by_func (child,
stack_child_visibility_notify_cb,
stack);
@@ -1318,7 +1430,12 @@ gtk_stack_remove (GtkContainer *container,
child_info->widget = NULL;
if (priv->visible_child == child_info)
- set_visible_child (stack, NULL, priv->transition_type, priv->transition_duration);
+ {
+ if (in_dispose)
+ priv->visible_child = NULL;
+ else
+ set_visible_child (stack, NULL, priv->transition_type, priv->transition_duration);
+ }
if (priv->last_visible_child == child_info)
priv->last_visible_child = NULL;
@@ -1327,10 +1444,33 @@ gtk_stack_remove (GtkContainer *container,
g_object_unref (child_info);
- if ((priv->hhomogeneous || priv->vhomogeneous) && was_visible)
+ if (!in_dispose &&
+ (priv->hhomogeneous || priv->vhomogeneous) &&
+ was_visible)
gtk_widget_queue_resize (GTK_WIDGET (stack));
}
+static void
+gtk_stack_remove (GtkContainer *container,
+ GtkWidget *child)
+{
+ GtkStackPrivate *priv = gtk_stack_get_instance_private (GTK_STACK (container));
+ GList *l;
+ guint position;
+
+ for (l = priv->children, position = 0; l; l = l->next, position++)
+ {
+ GtkStackPage *page = l->data;
+ if (page->widget == child)
+ break;
+ }
+
+ stack_remove (GTK_STACK (container), child, FALSE);
+
+ if (priv->pages)
+ g_list_model_items_changed (G_LIST_MODEL (priv->pages), position, 1, 0);
+}
+
/**
* gtk_stack_get_page:
* @stack: a #GtkStack
@@ -2294,3 +2434,30 @@ gtk_stack_init (GtkStack *stack)
priv->transition_duration = 200;
priv->transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
}
+
+/**
+ * gtk_stack_get_pages:
+ * @stack: a #GtkStack
+ *
+ * Returns a #GListModel that contains the pages of the stack,
+ * and can be used to keep and up-to-date view. The model also
+ * implements #GtkSelectionModel and can be used to track and
+ * modify the visible page..
+ *
+ * Returns: (transfer full): a #GtkSelectionModel for the stack's children
+ */
+GtkSelectionModel *
+gtk_stack_get_pages (GtkStack *stack)
+{
+ GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
+
+ g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
+
+ if (priv->pages)
+ return g_object_ref (priv->pages);
+
+ priv->pages = GTK_SELECTION_MODEL (gtk_stack_pages_new (stack));
+ g_object_add_weak_pointer (G_OBJECT (priv->pages), (gpointer *)&priv->pages);
+
+ return priv->pages;
+}
diff --git a/gtk/gtkstack.h b/gtk/gtkstack.h
index 6379328041..658ef8bb6a 100644
--- a/gtk/gtkstack.h
+++ b/gtk/gtkstack.h
@@ -27,6 +27,7 @@
#endif
#include
+#include
G_BEGIN_DECLS
@@ -155,6 +156,10 @@ void gtk_stack_set_interpolate_size (GtkStack *stack,
gboolean interpolate_size);
GDK_AVAILABLE_IN_ALL
gboolean gtk_stack_get_interpolate_size (GtkStack *stack);
+
+GDK_AVAILABLE_IN_ALL
+GtkSelectionModel * gtk_stack_get_pages (GtkStack *stack);
+
G_END_DECLS
#endif
diff --git a/gtk/gtkstacksidebar.c b/gtk/gtkstacksidebar.c
index bf1b13759d..4ac5c7f713 100644
--- a/gtk/gtkstacksidebar.c
+++ b/gtk/gtkstacksidebar.c
@@ -28,6 +28,7 @@
#include "gtkscrolledwindow.h"
#include "gtkseparator.h"
#include "gtkstylecontext.h"
+#include "gtkselectionmodel.h"
#include "gtkprivate.h"
#include "gtkintl.h"
@@ -58,8 +59,8 @@ struct _GtkStackSidebarPrivate
{
GtkListBox *list;
GtkStack *stack;
+ GtkSelectionModel *pages;
GHashTable *rows;
- gboolean in_child_changed;
};
G_DEFINE_TYPE_WITH_PRIVATE (GtkStackSidebar, gtk_stack_sidebar, GTK_TYPE_BIN)
@@ -129,30 +130,14 @@ sort_list (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer userdata)
{
- GtkStackSidebar *sidebar = GTK_STACK_SIDEBAR (userdata);
- GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
- GtkWidget *item;
- GtkWidget *widget;
gint left = 0; gint right = 0;
if (row1)
- {
- item = gtk_bin_get_child (GTK_BIN (row1));
- widget = g_object_get_data (G_OBJECT (item), "stack-child");
- g_object_get (gtk_stack_get_page (GTK_STACK (priv->stack), widget),
- "position", &left,
- NULL);
- }
+ left = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row1), "child-index"));
if (row2)
- {
- item = gtk_bin_get_child (GTK_BIN (row2));
- widget = g_object_get_data (G_OBJECT (item), "stack-child");
- g_object_get (gtk_stack_get_page (GTK_STACK (priv->stack), widget),
- "position", &right,
- NULL);
- }
+ right = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row2), "child-index"));
if (left < right)
return -1;
@@ -170,18 +155,13 @@ gtk_stack_sidebar_row_selected (GtkListBox *box,
{
GtkStackSidebar *sidebar = GTK_STACK_SIDEBAR (userdata);
GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
- GtkWidget *item;
- GtkWidget *widget;
+ guint index;
- if (priv->in_child_changed)
+ if (row == NULL)
return;
- if (!row)
- return;
-
- item = gtk_bin_get_child (GTK_BIN (row));
- widget = g_object_get_data (G_OBJECT (item), "stack-child");
- gtk_stack_set_visible_child (priv->stack, widget);
+ index = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "child-index"));
+ gtk_selection_model_select_item (priv->pages, index, TRUE);
}
static void
@@ -218,24 +198,25 @@ gtk_stack_sidebar_init (GtkStackSidebar *sidebar)
static void
update_row (GtkStackSidebar *sidebar,
- GtkWidget *widget,
+ GtkStackPage *page,
GtkWidget *row)
{
- GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
GtkWidget *item;
gchar *title;
gboolean needs_attention;
+ gboolean visible;
GtkStyleContext *context;
- g_object_get (gtk_stack_get_page (GTK_STACK (priv->stack), widget),
+ g_object_get (page,
"title", &title,
"needs-attention", &needs_attention,
+ "visible", &visible,
NULL);
item = gtk_bin_get_child (GTK_BIN (row));
gtk_label_set_text (GTK_LABEL (item), title);
- gtk_widget_set_visible (row, gtk_widget_get_visible (widget) && title != NULL);
+ gtk_widget_set_visible (row, visible && title != NULL);
context = gtk_widget_get_style_context (row);
if (needs_attention)
@@ -247,29 +228,19 @@ update_row (GtkStackSidebar *sidebar,
}
static void
-on_position_updated (GtkWidget *widget,
- GParamSpec *pspec,
- GtkStackSidebar *sidebar)
-{
- GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
-
- gtk_list_box_invalidate_sort (priv->list);
-}
-
-static void
-on_child_updated (GtkWidget *widget,
- GParamSpec *pspec,
- GtkStackSidebar *sidebar)
+on_page_updated (GtkStackPage *page,
+ GParamSpec *pspec,
+ GtkStackSidebar *sidebar)
{
GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
GtkWidget *row;
- row = g_hash_table_lookup (priv->rows, widget);
- update_row (sidebar, widget, row);
+ row = g_hash_table_lookup (priv->rows, page);
+ update_row (sidebar, page, row);
}
static void
-add_child (GtkWidget *widget,
+add_child (guint position,
GtkStackSidebar *sidebar)
{
GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
@@ -277,118 +248,92 @@ add_child (GtkWidget *widget,
GtkWidget *row;
GtkStackPage *page;
- /* Check we don't actually already know about this widget */
- if (g_hash_table_lookup (priv->rows, widget))
- return;
-
/* Make a pretty item when we add kids */
item = gtk_label_new ("");
gtk_widget_set_halign (item, GTK_ALIGN_START);
gtk_widget_set_valign (item, GTK_ALIGN_CENTER);
row = gtk_list_box_row_new ();
gtk_container_add (GTK_CONTAINER (row), item);
- gtk_widget_show (item);
- update_row (sidebar, widget, row);
+ page = g_list_model_get_item (G_LIST_MODEL (priv->pages), position);
+ update_row (sidebar, page, row);
- /* Hook up for events */
- page = gtk_stack_get_page (GTK_STACK (priv->stack), widget);
- g_signal_connect (widget, "notify::visible",
- G_CALLBACK (on_child_updated), sidebar);
- g_signal_connect (page, "notify::title",
- G_CALLBACK (on_child_updated), sidebar);
- g_signal_connect (page, "notify::needs-attention",
- G_CALLBACK (on_child_updated), sidebar);
- g_signal_connect (page, "notify::position",
- G_CALLBACK (on_position_updated), sidebar);
-
- g_object_set_data (G_OBJECT (item), I_("stack-child"), widget);
- g_hash_table_insert (priv->rows, widget, row);
gtk_container_add (GTK_CONTAINER (priv->list), row);
-}
-static void
-remove_child (GtkWidget *widget,
- GtkStackSidebar *sidebar)
-{
- GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
- GtkWidget *row;
+ g_object_set_data (G_OBJECT (row), "child-index", GUINT_TO_POINTER (position));
+ if (gtk_selection_model_is_selected (priv->pages, position))
+ gtk_list_box_select_row (priv->list, GTK_LIST_BOX_ROW (row));
+ else
+ gtk_list_box_unselect_row (priv->list, GTK_LIST_BOX_ROW (row));
- row = g_hash_table_lookup (priv->rows, widget);
- if (!row)
- return;
+ g_signal_connect (page, "notify", G_CALLBACK (on_page_updated), sidebar);
- if (priv->stack)
- {
- GtkStackPage *page = gtk_stack_get_page (GTK_STACK (priv->stack), widget);
- if (page)
- {
- g_signal_handlers_disconnect_by_func (page, on_child_updated, sidebar);
- g_signal_handlers_disconnect_by_func (page, on_position_updated, sidebar);
- }
- }
+ g_hash_table_insert (priv->rows, page, row);
- gtk_container_remove (GTK_CONTAINER (priv->list), row);
- g_hash_table_remove (priv->rows, widget);
+ g_object_unref (page);
}
static void
populate_sidebar (GtkStackSidebar *sidebar)
{
GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
- GtkWidget *widget, *row;
+ guint i;
- gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback)add_child, sidebar);
-
- widget = gtk_stack_get_visible_child (priv->stack);
- if (widget)
- {
- row = g_hash_table_lookup (priv->rows, widget);
- gtk_list_box_select_row (priv->list, GTK_LIST_BOX_ROW (row));
- }
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (priv->pages)); i++)
+ add_child (i, sidebar);
}
static void
clear_sidebar (GtkStackSidebar *sidebar)
{
GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
-
- gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback)remove_child, sidebar);
-}
-
-static void
-on_child_changed (GtkWidget *widget,
- GParamSpec *pspec,
- GtkStackSidebar *sidebar)
-{
- GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
- GtkWidget *child;
+ GHashTableIter iter;
+ GtkStackPage *page;
GtkWidget *row;
- child = gtk_stack_get_visible_child (GTK_STACK (widget));
- row = g_hash_table_lookup (priv->rows, child);
- if (row != NULL)
+ g_hash_table_iter_init (&iter, priv->rows);
+ while (g_hash_table_iter_next (&iter, (gpointer *)&page, (gpointer *)&row))
{
- priv->in_child_changed = TRUE;
- gtk_list_box_select_row (priv->list, GTK_LIST_BOX_ROW (row));
- priv->in_child_changed = FALSE;
+ gtk_container_remove (GTK_CONTAINER (priv->list), row);
+ g_hash_table_iter_remove (&iter);
+ g_signal_handlers_disconnect_by_func (page, on_page_updated, sidebar);
}
}
static void
-on_stack_child_added (GtkContainer *container,
- GtkWidget *widget,
- GtkStackSidebar *sidebar)
+items_changed_cb (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ GtkStackSidebar *sidebar)
{
- add_child (widget, sidebar);
+ /* FIXME: we can do better */
+ clear_sidebar (sidebar);
+ populate_sidebar (sidebar);
}
static void
-on_stack_child_removed (GtkContainer *container,
- GtkWidget *widget,
- GtkStackSidebar *sidebar)
+selection_changed_cb (GtkSelectionModel *model,
+ guint position,
+ guint n_items,
+ GtkStackSidebar *sidebar)
{
- remove_child (widget, sidebar);
+ GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
+ guint i;
+
+ for (i = position; i < position + n_items; i++)
+ {
+ GtkStackPage *page;
+ GtkWidget *row;
+
+ page = g_list_model_get_item (G_LIST_MODEL (priv->pages), i);
+ row = g_hash_table_lookup (priv->rows, page);
+ if (gtk_selection_model_is_selected (priv->pages, i))
+ gtk_list_box_select_row (priv->list, GTK_LIST_BOX_ROW (row));
+ else
+ gtk_list_box_unselect_row (priv->list, GTK_LIST_BOX_ROW (row));
+ g_object_unref (page);
+ }
}
static void
@@ -396,10 +341,8 @@ disconnect_stack_signals (GtkStackSidebar *sidebar)
{
GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
- g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_added, sidebar);
- g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_removed, sidebar);
- g_signal_handlers_disconnect_by_func (priv->stack, on_child_changed, sidebar);
- g_signal_handlers_disconnect_by_func (priv->stack, disconnect_stack_signals, sidebar);
+ g_signal_handlers_disconnect_by_func (priv->pages, items_changed_cb, sidebar);
+ g_signal_handlers_disconnect_by_func (priv->pages, selection_changed_cb, sidebar);
}
static void
@@ -407,14 +350,37 @@ connect_stack_signals (GtkStackSidebar *sidebar)
{
GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
- g_signal_connect_after (priv->stack, "add",
- G_CALLBACK (on_stack_child_added), sidebar);
- g_signal_connect_after (priv->stack, "remove",
- G_CALLBACK (on_stack_child_removed), sidebar);
- g_signal_connect (priv->stack, "notify::visible-child",
- G_CALLBACK (on_child_changed), sidebar);
- g_signal_connect_swapped (priv->stack, "destroy",
- G_CALLBACK (disconnect_stack_signals), sidebar);
+ g_signal_connect (priv->pages, "items-changed", G_CALLBACK (items_changed_cb), sidebar);
+ g_signal_connect (priv->pages, "selection-changed", G_CALLBACK (selection_changed_cb), sidebar);
+}
+
+static void
+set_stack (GtkStackSidebar *sidebar,
+ GtkStack *stack)
+{
+ GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
+
+ if (stack)
+ {
+ priv->stack = g_object_ref (stack);
+ priv->pages = gtk_stack_get_pages (stack);
+ populate_sidebar (sidebar);
+ connect_stack_signals (sidebar);
+ }
+}
+
+static void
+unset_stack (GtkStackSidebar *sidebar)
+{
+ GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
+
+ if (priv->stack)
+ {
+ disconnect_stack_signals (sidebar);
+ clear_sidebar (sidebar);
+ g_clear_object (&priv->stack);
+ g_clear_object (&priv->pages);
+ }
}
static void
@@ -422,7 +388,7 @@ gtk_stack_sidebar_dispose (GObject *object)
{
GtkStackSidebar *sidebar = GTK_STACK_SIDEBAR (object);
- gtk_stack_sidebar_set_stack (sidebar, NULL);
+ unset_stack (sidebar);
G_OBJECT_CLASS (gtk_stack_sidebar_parent_class)->dispose (object);
}
@@ -487,28 +453,17 @@ void
gtk_stack_sidebar_set_stack (GtkStackSidebar *sidebar,
GtkStack *stack)
{
- GtkStackSidebarPrivate *priv;
+ GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar);
g_return_if_fail (GTK_IS_STACK_SIDEBAR (sidebar));
g_return_if_fail (GTK_IS_STACK (stack) || stack == NULL);
- priv = gtk_stack_sidebar_get_instance_private (sidebar);
if (priv->stack == stack)
return;
- if (priv->stack)
- {
- disconnect_stack_signals (sidebar);
- clear_sidebar (sidebar);
- g_clear_object (&priv->stack);
- }
- if (stack)
- {
- priv->stack = g_object_ref (stack);
- populate_sidebar (sidebar);
- connect_stack_signals (sidebar);
- }
+ unset_stack (sidebar);
+ set_stack (sidebar, stack);
gtk_widget_queue_resize (GTK_WIDGET (sidebar));
diff --git a/gtk/gtkstackswitcher.c b/gtk/gtkstackswitcher.c
index ab9d91b27d..e088ba1cca 100644
--- a/gtk/gtkstackswitcher.c
+++ b/gtk/gtkstackswitcher.c
@@ -29,6 +29,7 @@
#include "gtkwidgetprivate.h"
#include "gtktypebuiltins.h"
#include "gtkimage.h"
+#include "gtkselectionmodel.h"
/**
* SECTION:gtkstackswitcher
@@ -66,8 +67,8 @@ typedef struct _GtkStackSwitcherPrivate GtkStackSwitcherPrivate;
struct _GtkStackSwitcherPrivate
{
GtkStack *stack;
+ GtkSelectionModel *pages;
GHashTable *buttons;
- gboolean in_child_changed;
GtkWidget *switch_button;
guint switch_timer;
};
@@ -89,11 +90,9 @@ gtk_stack_switcher_init (GtkStackSwitcher *switcher)
priv = gtk_stack_switcher_get_instance_private (switcher);
- priv->stack = NULL;
- priv->buttons = g_hash_table_new (g_direct_hash, g_direct_equal);
+ priv->buttons = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
context = gtk_widget_get_style_context (GTK_WIDGET (switcher));
- gtk_style_context_add_class (context, "stack-switcher");
gtk_style_context_add_class (context, GTK_STYLE_CLASS_LINKED);
gtk_orientable_set_orientation (GTK_ORIENTABLE (switcher), GTK_ORIENTATION_HORIZONTAL);
@@ -103,18 +102,25 @@ gtk_stack_switcher_init (GtkStackSwitcher *switcher)
}
static void
-on_button_clicked (GtkWidget *widget,
+on_button_toggled (GtkWidget *button,
+ GParamSpec *pspec,
GtkStackSwitcher *self)
{
- GtkWidget *child;
- GtkStackSwitcherPrivate *priv;
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (self);
+ gboolean active;
+ guint index;
- priv = gtk_stack_switcher_get_instance_private (self);
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ index = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (button), "child-index"));
- if (!priv->in_child_changed)
+ if (active)
{
- child = g_object_get_data (G_OBJECT (widget), "stack-child");
- gtk_stack_set_visible_child (priv->stack, child);
+ gtk_selection_model_select_item (priv->pages, index, TRUE);
+ }
+ else
+ {
+ gboolean selected = gtk_selection_model_is_selected (priv->pages, index);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), selected);
}
}
@@ -161,25 +167,25 @@ rebuild_child (GtkWidget *self,
static void
update_button (GtkStackSwitcher *self,
- GtkWidget *widget,
+ GtkStackPage *page,
GtkWidget *button)
{
gchar *title;
gchar *icon_name;
gboolean needs_attention;
- GtkStackSwitcherPrivate *priv;
+ gboolean visible;
GtkStyleContext *context;
- priv = gtk_stack_switcher_get_instance_private (self);
- g_object_get (gtk_stack_get_page (priv->stack, widget),
+ g_object_get (page,
"title", &title,
"icon-name", &icon_name,
"needs-attention", &needs_attention,
+ "visible", &visible,
NULL);
rebuild_child (button, icon_name, title);
- gtk_widget_set_visible (button, gtk_widget_get_visible (widget) && (title != NULL || icon_name != NULL));
+ gtk_widget_set_visible (button, visible && (title != NULL || icon_name != NULL));
context = gtk_widget_get_style_context (button);
if (needs_attention)
@@ -192,88 +198,21 @@ update_button (GtkStackSwitcher *self,
}
static void
-on_visible_updated (GtkWidget *widget,
- GParamSpec *pspec,
- GtkStackSwitcher *self)
+on_page_updated (GtkStackPage *page,
+ GParamSpec *pspec,
+ GtkStackSwitcher *self)
{
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (self);
GtkWidget *button;
- GtkStackSwitcherPrivate *priv;
- priv = gtk_stack_switcher_get_instance_private (self);
-
- button = g_hash_table_lookup (priv->buttons, widget);
- update_button (self, widget, button);
-}
-
-static void
-on_title_icon_updated (GtkStackPage *page,
- GParamSpec *pspec,
- GtkStackSwitcher *self)
-{
- GtkWidget *widget;
- GtkWidget *button;
- GtkStackSwitcherPrivate *priv;
-
- priv = gtk_stack_switcher_get_instance_private (self);
-
- widget = gtk_stack_page_get_child (page);
- button = g_hash_table_lookup (priv->buttons, widget);
- update_button (self, widget, button);
-}
-
-static void
-on_position_updated (GtkStackPage *page,
- GParamSpec *pspec,
- GtkStackSwitcher *self)
-{
- GtkWidget *widget;
- GtkWidget *button;
- gint position;
- GtkStackSwitcherPrivate *priv;
-
- priv = gtk_stack_switcher_get_instance_private (self);
-
- widget = gtk_stack_page_get_child (page);
- button = g_hash_table_lookup (priv->buttons, widget);
-
- gtk_container_child_get (GTK_CONTAINER (priv->stack), widget,
- "position", &position,
- NULL);
-
- if (position == 0)
- gtk_box_reorder_child_after (GTK_BOX (self), button, NULL);
- else
- {
- GtkWidget *sibling = gtk_widget_get_first_child (GTK_WIDGET (self));
- int i;
- for (i = 1; i < position; i++)
- sibling = gtk_widget_get_next_sibling (sibling);
- gtk_box_reorder_child_after (GTK_BOX (self), button, sibling);
- }
-}
-
-static void
-on_needs_attention_updated (GtkStackPage *page,
- GParamSpec *pspec,
- GtkStackSwitcher *self)
-{
- GtkWidget *widget;
- GtkWidget *button;
- GtkStackSwitcherPrivate *priv;
-
- priv = gtk_stack_switcher_get_instance_private (self);
-
- widget = gtk_stack_page_get_child (page);
- button = g_hash_table_lookup (priv->buttons, widget);
- update_button (self, widget, button);
+ button = g_hash_table_lookup (priv->buttons, page);
+ update_button (self, page, button);
}
static void
remove_switch_timer (GtkStackSwitcher *self)
{
- GtkStackSwitcherPrivate *priv;
-
- priv = gtk_stack_switcher_get_instance_private (self);
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (self);
if (priv->switch_timer)
{
@@ -286,11 +225,9 @@ static gboolean
gtk_stack_switcher_switch_timeout (gpointer data)
{
GtkStackSwitcher *self = data;
- GtkStackSwitcherPrivate *priv;
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (self);
GtkWidget *button;
- priv = gtk_stack_switcher_get_instance_private (self);
-
priv->switch_timer = 0;
button = priv->switch_button;
@@ -309,14 +246,12 @@ gtk_stack_switcher_drag_motion (GtkWidget *widget,
gint y)
{
GtkStackSwitcher *self = GTK_STACK_SWITCHER (widget);
- GtkStackSwitcherPrivate *priv;
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (self);
GtkWidget *button;
GHashTableIter iter;
gpointer value;
gboolean retval = FALSE;
- priv = gtk_stack_switcher_get_instance_private (self);
-
button = NULL;
g_hash_table_iter_init (&iter, priv->buttons);
while (g_hash_table_iter_next (&iter, NULL, &value))
@@ -355,160 +290,143 @@ gtk_stack_switcher_drag_leave (GtkWidget *widget,
}
static void
-add_child (GtkWidget *widget,
+add_child (guint position,
GtkStackSwitcher *self)
{
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (self);
GtkWidget *button;
- GList *group;
- GtkStackSwitcherPrivate *priv;
+ gboolean selected;
GtkStackPage *page;
- priv = gtk_stack_switcher_get_instance_private (self);
-
- button = gtk_radio_button_new (NULL);
-
+ button = gtk_toggle_button_new ();
gtk_widget_set_focus_on_click (button, FALSE);
- gtk_check_button_set_draw_indicator (GTK_CHECK_BUTTON (button), FALSE);
- page = gtk_stack_get_page (GTK_STACK (priv->stack), widget);
- update_button (self, widget, button);
-
- group = gtk_container_get_children (GTK_CONTAINER (self));
- if (group != NULL)
- {
- gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (group->data));
- g_list_free (group);
- }
+ page = g_list_model_get_item (G_LIST_MODEL (priv->pages), position);
+ update_button (self, page, button);
gtk_container_add (GTK_CONTAINER (self), button);
- g_object_set_data (G_OBJECT (button), "stack-child", widget);
- g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), self);
- g_signal_connect (widget, "notify::visible", G_CALLBACK (on_visible_updated), self);
- g_signal_connect (page, "notify::title", G_CALLBACK (on_title_icon_updated), self);
- g_signal_connect (page, "notify::icon-name", G_CALLBACK (on_title_icon_updated), self);
- g_signal_connect (page, "notify::position", G_CALLBACK (on_position_updated), self);
- g_signal_connect (page, "notify::needs-attention", G_CALLBACK (on_needs_attention_updated), self);
+ g_object_set_data (G_OBJECT (button), "child-index", GUINT_TO_POINTER (position));
+ selected = gtk_selection_model_is_selected (priv->pages, position);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), selected);
- g_hash_table_insert (priv->buttons, widget, button);
-}
+ g_signal_connect (button, "notify::active", G_CALLBACK (on_button_toggled), self);
+ g_signal_connect (page, "notify", G_CALLBACK (on_page_updated), self);
-static void
-remove_child (GtkWidget *widget,
- GtkStackSwitcher *self)
-{
- GtkWidget *button;
- GtkStackSwitcherPrivate *priv;
+ g_hash_table_insert (priv->buttons, g_object_ref (page), button);
- priv = gtk_stack_switcher_get_instance_private (self);
-
- if (priv->stack)
- {
- GtkStackPage *page = gtk_stack_get_page (priv->stack, widget);
- if (page)
- {
- g_signal_handlers_disconnect_by_func (page, on_title_icon_updated, self);
- g_signal_handlers_disconnect_by_func (page, on_position_updated, self);
- g_signal_handlers_disconnect_by_func (page, on_needs_attention_updated, self);
- }
- g_signal_handlers_disconnect_by_func (widget, on_visible_updated, self);
- }
- button = g_hash_table_lookup (priv->buttons, widget);
- gtk_container_remove (GTK_CONTAINER (self), button);
- g_hash_table_remove (priv->buttons, widget);
+ g_object_unref (page);
}
static void
populate_switcher (GtkStackSwitcher *self)
{
- GtkStackSwitcherPrivate *priv;
- GtkWidget *widget, *button;
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (self);
+ guint i;
- priv = gtk_stack_switcher_get_instance_private (self);
- gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback)add_child, self);
-
- widget = gtk_stack_get_visible_child (priv->stack);
- if (widget)
- {
- button = g_hash_table_lookup (priv->buttons, widget);
- priv->in_child_changed = TRUE;
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
- priv->in_child_changed = FALSE;
- }
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (priv->pages)); i++)
+ add_child (i, self);
}
static void
clear_switcher (GtkStackSwitcher *self)
{
- GtkStackSwitcherPrivate *priv;
-
- priv = gtk_stack_switcher_get_instance_private (self);
- gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback)remove_child, self);
-}
-
-static void
-on_child_changed (GtkWidget *widget,
- GParamSpec *pspec,
- GtkStackSwitcher *self)
-{
- GtkWidget *child;
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (self);
+ GHashTableIter iter;
+ GtkWidget *page;
GtkWidget *button;
- GtkStackSwitcherPrivate *priv;
- priv = gtk_stack_switcher_get_instance_private (self);
-
- child = gtk_stack_get_visible_child (GTK_STACK (widget));
- button = g_hash_table_lookup (priv->buttons, child);
- if (button != NULL)
+ g_hash_table_iter_init (&iter, priv->buttons);
+ while (g_hash_table_iter_next (&iter, (gpointer *)&page, (gpointer *)&button))
{
- priv->in_child_changed = TRUE;
- gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
- priv->in_child_changed = FALSE;
+ gtk_container_remove (GTK_CONTAINER (self), button);
+ g_signal_handlers_disconnect_by_func (page, on_page_updated, self);
+ g_hash_table_iter_remove (&iter);
}
}
static void
-on_stack_child_added (GtkContainer *container,
- GtkWidget *widget,
- GtkStackSwitcher *self)
+items_changed_cb (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ GtkStackSwitcher *switcher)
{
- add_child (widget, self);
+ clear_switcher (switcher);
+ populate_switcher (switcher);
}
static void
-on_stack_child_removed (GtkContainer *container,
- GtkWidget *widget,
- GtkStackSwitcher *self)
+selection_changed_cb (GtkSelectionModel *model,
+ guint position,
+ guint n_items,
+ GtkStackSwitcher *switcher)
{
- remove_child (widget, self);
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (switcher);
+ guint i;
+
+ for (i = position; i < position + n_items; i++)
+ {
+ GtkStackPage *page;
+ GtkWidget *button;
+ gboolean selected;
+
+ page = g_list_model_get_item (G_LIST_MODEL (priv->pages), i);
+ button = g_hash_table_lookup (priv->buttons, page);
+ if (button)
+ {
+ selected = gtk_selection_model_is_selected (priv->pages, i);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), selected);
+ }
+ g_object_unref (page);
+ }
}
static void
disconnect_stack_signals (GtkStackSwitcher *switcher)
{
- GtkStackSwitcherPrivate *priv;
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (switcher);
- priv = gtk_stack_switcher_get_instance_private (switcher);
- g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_added, switcher);
- g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_removed, switcher);
- g_signal_handlers_disconnect_by_func (priv->stack, on_child_changed, switcher);
- g_signal_handlers_disconnect_by_func (priv->stack, disconnect_stack_signals, switcher);
+ g_signal_handlers_disconnect_by_func (priv->pages, items_changed_cb, switcher);
+ g_signal_handlers_disconnect_by_func (priv->pages, selection_changed_cb, switcher);
}
static void
connect_stack_signals (GtkStackSwitcher *switcher)
{
- GtkStackSwitcherPrivate *priv;
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (switcher);
- priv = gtk_stack_switcher_get_instance_private (switcher);
- g_signal_connect_after (priv->stack, "add",
- G_CALLBACK (on_stack_child_added), switcher);
- g_signal_connect_after (priv->stack, "remove",
- G_CALLBACK (on_stack_child_removed), switcher);
- g_signal_connect (priv->stack, "notify::visible-child",
- G_CALLBACK (on_child_changed), switcher);
- g_signal_connect_swapped (priv->stack, "destroy",
- G_CALLBACK (disconnect_stack_signals), switcher);
+ g_signal_connect (priv->pages, "items-changed", G_CALLBACK (items_changed_cb), switcher);
+ g_signal_connect (priv->pages, "selection-changed", G_CALLBACK (selection_changed_cb), switcher);
+}
+
+static void
+set_stack (GtkStackSwitcher *switcher,
+ GtkStack *stack)
+{
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (switcher);
+
+ if (stack)
+ {
+ priv->stack = g_object_ref (stack);
+ priv->pages = gtk_stack_get_pages (stack);
+ populate_switcher (switcher);
+ connect_stack_signals (switcher);
+ }
+}
+
+static void
+unset_stack (GtkStackSwitcher *switcher)
+{
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (switcher);
+
+ if (priv->stack)
+ {
+ disconnect_stack_signals (switcher);
+ clear_switcher (switcher);
+ g_clear_object (&priv->stack);
+ g_clear_object (&priv->pages);
+ }
}
/**
@@ -522,28 +440,16 @@ void
gtk_stack_switcher_set_stack (GtkStackSwitcher *switcher,
GtkStack *stack)
{
- GtkStackSwitcherPrivate *priv;
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (switcher);
g_return_if_fail (GTK_IS_STACK_SWITCHER (switcher));
g_return_if_fail (GTK_IS_STACK (stack) || stack == NULL);
- priv = gtk_stack_switcher_get_instance_private (switcher);
-
if (priv->stack == stack)
return;
- if (priv->stack)
- {
- disconnect_stack_signals (switcher);
- clear_switcher (switcher);
- g_clear_object (&priv->stack);
- }
- if (stack)
- {
- priv->stack = g_object_ref (stack);
- populate_switcher (switcher);
- connect_stack_signals (switcher);
- }
+ unset_stack (switcher);
+ set_stack (switcher, stack);
gtk_widget_queue_resize (GTK_WIDGET (switcher));
@@ -618,7 +524,7 @@ gtk_stack_switcher_dispose (GObject *object)
GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
remove_switch_timer (switcher);
- gtk_stack_switcher_set_stack (switcher, NULL);
+ unset_stack (switcher);
G_OBJECT_CLASS (gtk_stack_switcher_parent_class)->dispose (object);
}
@@ -627,9 +533,7 @@ static void
gtk_stack_switcher_finalize (GObject *object)
{
GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
- GtkStackSwitcherPrivate *priv;
-
- priv = gtk_stack_switcher_get_instance_private (switcher);
+ GtkStackSwitcherPrivate *priv = gtk_stack_switcher_get_instance_private (switcher);
g_hash_table_destroy (priv->buttons);
diff --git a/gtk/meson.build b/gtk/meson.build
index c700b37289..3ebb1b5867 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -323,6 +323,7 @@ gtk_public_sources = files([
'gtksearchbar.c',
'gtksearchentry.c',
'gtkselection.c',
+ 'gtkselectionmodel.c',
'gtkseparator.c',
'gtkseparatormenuitem.c',
'gtkseparatortoolitem.c',
@@ -334,6 +335,7 @@ gtk_public_sources = files([
'gtkshortcutswindow.c',
'gtkshow.c',
'gtksidebarrow.c',
+ 'gtksingleselection.c',
'gtksizegroup.c',
'gtksizerequest.c',
'gtkslicelistmodel.c',
@@ -557,6 +559,7 @@ gtk_public_headers = files([
'gtksearchbar.h',
'gtksearchentry.h',
'gtkselection.h',
+ 'gtkselectionmodel.h',
'gtkseparator.h',
'gtkseparatormenuitem.h',
'gtkseparatortoolitem.h',
@@ -567,6 +570,7 @@ gtk_public_headers = files([
'gtkshortcutsshortcut.h',
'gtkshortcutswindow.h',
'gtkshow.h',
+ 'gtksingleselection.h',
'gtksizegroup.h',
'gtksizerequest.h',
'gtkslicelistmodel.h',
diff --git a/testsuite/a11y/about.txt b/testsuite/a11y/about.txt
index 5dd586cc95..fdb3ab1e2c 100644
--- a/testsuite/a11y/about.txt
+++ b/testsuite/a11y/about.txt
@@ -370,15 +370,11 @@ See the GNU General Public License, version 3 or later for details.
layer: widget
alpha: 1
- unnamed-GtkRadioButtonAccessible-2
- "radio button"
+ unnamed-GtkToggleButtonAccessible-2
+ "toggle button"
parent: stack_switcher
index: 0
name: About
- member-of: unnamed-GtkRadioButtonAccessible-3
- unnamed-GtkRadioButtonAccessible-4
- unnamed-GtkRadioButtonAccessible-5
- unnamed-GtkRadioButtonAccessible-2
state: checked enabled focusable sensitive visible
toolkit: gtk
@@ -390,15 +386,11 @@ See the GNU General Public License, version 3 or later for details.
action 0 name: click
action 0 description: Clicks the button
- unnamed-GtkRadioButtonAccessible-5
- "radio button"
+ unnamed-GtkToggleButtonAccessible-3
+ "toggle button"
parent: stack_switcher
index: 1
name: Credits
- member-of: unnamed-GtkRadioButtonAccessible-3
- unnamed-GtkRadioButtonAccessible-4
- unnamed-GtkRadioButtonAccessible-5
- unnamed-GtkRadioButtonAccessible-2
state: enabled focusable sensitive
toolkit: gtk
@@ -410,15 +402,11 @@ See the GNU General Public License, version 3 or later for details.
action 0 name: click
action 0 description: Clicks the button
- unnamed-GtkRadioButtonAccessible-4
- "radio button"
+ unnamed-GtkToggleButtonAccessible-4
+ "toggle button"
parent: stack_switcher
index: 2
name: License
- member-of: unnamed-GtkRadioButtonAccessible-3
- unnamed-GtkRadioButtonAccessible-4
- unnamed-GtkRadioButtonAccessible-5
- unnamed-GtkRadioButtonAccessible-2
state: enabled focusable sensitive
toolkit: gtk
@@ -430,15 +418,11 @@ See the GNU General Public License, version 3 or later for details.
action 0 name: click
action 0 description: Clicks the button
- unnamed-GtkRadioButtonAccessible-3
- "radio button"
+ unnamed-GtkToggleButtonAccessible-5
+ "toggle button"
parent: stack_switcher
index: 3
name: System
- member-of: unnamed-GtkRadioButtonAccessible-3
- unnamed-GtkRadioButtonAccessible-4
- unnamed-GtkRadioButtonAccessible-5
- unnamed-GtkRadioButtonAccessible-2
state: enabled focusable sensitive
toolkit: gtk
diff --git a/testsuite/a11y/stack.txt b/testsuite/a11y/stack.txt
index 5a82eea477..bb07325586 100644
--- a/testsuite/a11y/stack.txt
+++ b/testsuite/a11y/stack.txt
@@ -25,13 +25,11 @@ window1
layer: widget
alpha: 1
- unnamed-GtkRadioButtonAccessible-0
- "radio button"
+ unnamed-GtkToggleButtonAccessible-0
+ "toggle button"
parent: stackswitcher1
index: 0
name: Page 1
- member-of: unnamed-GtkRadioButtonAccessible-1
- unnamed-GtkRadioButtonAccessible-0
state: checked enabled focusable sensitive showing visible
toolkit: gtk
@@ -43,13 +41,11 @@ window1
action 0 name: click
action 0 description: Clicks the button
- unnamed-GtkRadioButtonAccessible-1
- "radio button"
+ unnamed-GtkToggleButtonAccessible-1
+ "toggle button"
parent: stackswitcher1
index: 1
name: Page 2
- member-of: unnamed-GtkRadioButtonAccessible-1
- unnamed-GtkRadioButtonAccessible-0
state: enabled focusable sensitive showing visible
toolkit: gtk
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index e4e9eec959..c02e4ab177 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -44,6 +44,7 @@ tests = [
['regression-tests'],
['scrolledwindow'],
['searchbar'],
+ ['singleselection'],
['slicelistmodel'],
['sortlistmodel'],
['spinbutton'],
diff --git a/testsuite/gtk/notify.c b/testsuite/gtk/notify.c
index 228195b2d3..20315b22f2 100644
--- a/testsuite/gtk/notify.c
+++ b/testsuite/gtk/notify.c
@@ -611,6 +611,12 @@ test_type (gconstpointer data)
g_str_equal (pspec->name, "expanded"))
continue;
+ /* can't select items without an underlying, populated model */
+ if (g_type_is_a (type, GTK_TYPE_SINGLE_SELECTION) &&
+ (g_str_equal (pspec->name, "selected") ||
+ g_str_equal (pspec->name, "selected-item")))
+ continue;
+
if (g_test_verbose ())
g_print ("Property %s.%s\n", g_type_name (pspec->owner_type), pspec->name);
diff --git a/testsuite/gtk/singleselection.c b/testsuite/gtk/singleselection.c
new file mode 100644
index 0000000000..efe4a91ece
--- /dev/null
+++ b/testsuite/gtk/singleselection.c
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2019, Red Hat, Inc.
+ * Authors: Matthias Clasen
+ *
+ * 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, see .
+ */
+
+#include
+
+#include
+
+static GQuark number_quark;
+static GQuark changes_quark;
+static GQuark selection_quark;
+
+static guint
+get (GListModel *model,
+ guint position)
+{
+ guint number;
+ GObject *object = g_list_model_get_item (model, position);
+ g_assert (object != NULL);
+ number = GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark));
+ g_object_unref (object);
+ return number;
+}
+
+static char *
+model_to_string (GListModel *model)
+{
+ GString *string = g_string_new (NULL);
+ guint i;
+
+ for (i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ if (i > 0)
+ g_string_append (string, " ");
+ g_string_append_printf (string, "%u", get (model, i));
+ }
+
+ return g_string_free (string, FALSE);
+}
+
+static char *
+selection_to_string (GListModel *model)
+{
+ GString *string = g_string_new (NULL);
+ guint i;
+
+ for (i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ if (!gtk_selection_model_is_selected (GTK_SELECTION_MODEL (model), i))
+ continue;
+
+ if (string->len > 0)
+ g_string_append (string, " ");
+ g_string_append_printf (string, "%u", get (model, i));
+ }
+
+ return g_string_free (string, FALSE);
+}
+
+static GListStore *
+new_store (guint start,
+ guint end,
+ guint step);
+
+static GObject *
+make_object (guint number)
+{
+ GObject *object;
+
+ /* 0 cannot be differentiated from NULL, so don't use it */
+ g_assert (number != 0);
+
+ object = g_object_new (G_TYPE_OBJECT, NULL);
+ g_object_set_qdata (object, number_quark, GUINT_TO_POINTER (number));
+
+ return object;
+}
+
+static void
+splice (GListStore *store,
+ guint pos,
+ guint removed,
+ guint *numbers,
+ guint added)
+{
+ GObject *objects[added];
+ guint i;
+
+ for (i = 0; i < added; i++)
+ objects[i] = make_object (numbers[i]);
+
+ g_list_store_splice (store, pos, removed, (gpointer *) objects, added);
+
+ for (i = 0; i < added; i++)
+ g_object_unref (objects[i]);
+}
+
+static void
+add (GListStore *store,
+ guint number)
+{
+ GObject *object = make_object (number);
+ g_list_store_append (store, object);
+ g_object_unref (object);
+}
+
+static void
+insert (GListStore *store,
+ guint position,
+ guint number)
+{
+ GObject *object = make_object (number);
+ g_list_store_insert (store, position, object);
+ g_object_unref (object);
+}
+
+#define assert_model(model, expected) G_STMT_START{ \
+ char *s = model_to_string (G_LIST_MODEL (model)); \
+ if (!g_str_equal (s, expected)) \
+ g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+ #model " == " #expected, s, "==", expected); \
+ g_free (s); \
+}G_STMT_END
+
+#define ignore_changes(model) G_STMT_START{ \
+ GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
+ g_string_set_size (changes, 0); \
+}G_STMT_END
+
+#define assert_changes(model, expected) G_STMT_START{ \
+ GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
+ if (!g_str_equal (changes->str, expected)) \
+ g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+ #model " == " #expected, changes->str, "==", expected); \
+ g_string_set_size (changes, 0); \
+}G_STMT_END
+
+#define assert_selection(model, expected) G_STMT_START{ \
+ char *s = selection_to_string (G_LIST_MODEL (model)); \
+ if (!g_str_equal (s, expected)) \
+ g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+ #model " == " #expected, s, "==", expected); \
+ g_free (s); \
+}G_STMT_END
+
+#define assert_selection_changes(model, expected) G_STMT_START{ \
+ GString *changes = g_object_get_qdata (G_OBJECT (model), selection_quark); \
+ if (!g_str_equal (changes->str, expected)) \
+ g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+ #model " == " #expected, changes->str, "==", expected); \
+ g_string_set_size (changes, 0); \
+}G_STMT_END
+
+#define ignore_selection_changes(model) G_STMT_START{ \
+ GString *changes = g_object_get_qdata (G_OBJECT (model), selection_quark); \
+ g_string_set_size (changes, 0); \
+}G_STMT_END
+
+static GListStore *
+new_empty_store (void)
+{
+ return g_list_store_new (G_TYPE_OBJECT);
+}
+
+static GListStore *
+new_store (guint start,
+ guint end,
+ guint step)
+{
+ GListStore *store = new_empty_store ();
+ guint i;
+
+ for (i = start; i <= end; i += step)
+ add (store, i);
+
+ return store;
+}
+
+static void
+items_changed (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ GString *changes)
+{
+ g_assert (removed != 0 || added != 0);
+
+ if (changes->len)
+ g_string_append (changes, ", ");
+
+ if (removed == 1 && added == 0)
+ {
+ g_string_append_printf (changes, "-%u", position);
+ }
+ else if (removed == 0 && added == 1)
+ {
+ g_string_append_printf (changes, "+%u", position);
+ }
+ else
+ {
+ g_string_append_printf (changes, "%u", position);
+ if (removed > 0)
+ g_string_append_printf (changes, "-%u", removed);
+ if (added > 0)
+ g_string_append_printf (changes, "+%u", added);
+ }
+}
+
+static void
+selection_changed (GListModel *model,
+ guint position,
+ guint n_items,
+ GString *changes)
+{
+ if (changes->len)
+ g_string_append (changes, ", ");
+
+ g_string_append_printf (changes, "%u:%u", position, n_items);
+}
+
+static void
+free_changes (gpointer data)
+{
+ GString *changes = data;
+
+ /* all changes must have been checked via assert_changes() before */
+ g_assert_cmpstr (changes->str, ==, "");
+
+ g_string_free (changes, TRUE);
+}
+
+static GtkSelectionModel *
+new_model (GListStore *store, gboolean autoselect, gboolean can_unselect)
+{
+ GtkSelectionModel *result;
+ GString *changes;
+
+ result = GTK_SELECTION_MODEL (gtk_single_selection_new (G_LIST_MODEL (store)));
+
+ /* We want to return an empty selection unless autoselect is true,
+ * so undo the initial selection due to autoselect defaulting to TRUE.
+ */
+ gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (result), FALSE);
+ gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (result), TRUE);
+ gtk_selection_model_unselect_item (result, 0);
+ assert_selection (result, "");
+
+ gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (result), autoselect);
+ gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (result), can_unselect);
+
+ changes = g_string_new ("");
+ g_object_set_qdata_full (G_OBJECT(result), changes_quark, changes, free_changes);
+ g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes);
+
+ changes = g_string_new ("");
+ g_object_set_qdata_full (G_OBJECT(result), selection_quark, changes, free_changes);
+ g_signal_connect (result, "selection-changed", G_CALLBACK (selection_changed), changes);
+
+ return result;
+}
+
+static void
+test_create (void)
+{
+ GtkSelectionModel *selection;
+ GListStore *store;
+
+ store = new_store (1, 5, 2);
+ selection = new_model (store, FALSE, FALSE);
+ g_assert_false (gtk_single_selection_get_autoselect (GTK_SINGLE_SELECTION (selection)));
+
+ assert_model (selection, "1 3 5");
+ assert_changes (selection, "");
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "");
+
+ g_object_unref (store);
+
+ assert_model (selection, "1 3 5");
+ assert_changes (selection, "");
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "");
+
+ g_object_unref (selection);
+}
+
+static void
+test_changes (void)
+{
+ GtkSelectionModel *selection;
+ GListStore *store;
+
+ store = new_store (1, 5, 1);
+ selection = new_model (store, FALSE, FALSE);
+ assert_model (selection, "1 2 3 4 5");
+ assert_changes (selection, "");
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "");
+
+ g_list_store_remove (store, 3);
+ assert_model (selection, "1 2 3 5");
+ assert_changes (selection, "-3");
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "");
+
+ insert (store, 3, 99);
+ assert_model (selection, "1 2 3 99 5");
+ assert_changes (selection, "+3");
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "");
+
+ splice (store, 3, 2, (guint[]) { 97 }, 1);
+ assert_model (selection, "1 2 3 97");
+ assert_changes (selection, "3-2+1");
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "");
+
+ g_object_unref (selection);
+ g_object_unref (store);
+}
+
+static void
+test_selection (void)
+{
+ GtkSelectionModel *selection;
+ GListStore *store;
+ gboolean ret;
+
+ store = new_store (1, 5, 1);
+ selection = new_model (store, TRUE, FALSE);
+ assert_selection (selection, "1");
+ assert_selection_changes (selection, "");
+
+ ret = gtk_selection_model_select_item (selection, 3, FALSE);
+ g_assert_true (ret);
+ assert_selection (selection, "4");
+ assert_selection_changes (selection, "0:4");
+
+ ret = gtk_selection_model_unselect_item (selection, 3);
+ g_assert_false (ret);
+ assert_selection (selection, "4");
+ assert_selection_changes (selection, "");
+
+ ret = gtk_selection_model_select_item (selection, 1, FALSE);
+ g_assert_true (ret);
+ assert_selection (selection, "2");
+ assert_selection_changes (selection, "1:3");
+
+ ret = gtk_selection_model_select_range (selection, 3, 2, FALSE);
+ g_assert_false (ret);
+ assert_selection (selection, "2");
+ assert_selection_changes (selection, "");
+
+ ret = gtk_selection_model_unselect_range (selection, 4, 2);
+ g_assert_false (ret);
+ assert_selection (selection, "2");
+ assert_selection_changes (selection, "");
+
+ ret = gtk_selection_model_select_all (selection);
+ g_assert_false (ret);
+ assert_selection (selection, "2");
+ assert_selection_changes (selection, "");
+
+ ret = gtk_selection_model_unselect_all (selection);
+ g_assert_false (ret);
+ assert_selection (selection, "2");
+ assert_selection_changes (selection, "");
+
+ g_object_unref (store);
+ g_object_unref (selection);
+}
+
+static void
+test_autoselect (void)
+{
+ GtkSelectionModel *selection;
+ GListStore *store;
+
+ store = new_store (2, 1, 1);
+ selection = new_model (store, TRUE, FALSE);
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "");
+
+ add (store, 1);
+ assert_selection (selection, "1");
+ assert_selection_changes (selection, "");
+
+ splice (store, 0, 1, (guint[]) { 97 }, 1);
+ assert_selection (selection, "97");
+ assert_selection_changes (selection, "0:1");
+
+ gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (selection), FALSE);
+ gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (selection), TRUE);
+ gtk_selection_model_unselect_item (selection, 0);
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "0:1");
+
+ gtk_single_selection_set_autoselect (GTK_SINGLE_SELECTION (selection), TRUE);
+ assert_selection (selection, "97");
+ assert_selection_changes (selection, "0:1");
+
+ ignore_changes (selection);
+
+ g_object_unref (store);
+ g_object_unref (selection);
+}
+
+static void
+test_can_unselect (void)
+{
+ GtkSelectionModel *selection;
+ GListStore *store;
+ gboolean ret;
+
+ store = new_store (1, 5, 1);
+ selection = new_model (store, TRUE, FALSE);
+ assert_selection (selection, "1");
+ assert_selection_changes (selection, "");
+
+ ret = gtk_selection_model_unselect_item (selection, 0);
+ g_assert_false (ret);
+ assert_selection (selection, "1");
+ assert_selection_changes (selection, "");
+
+ gtk_single_selection_set_can_unselect (GTK_SINGLE_SELECTION (selection), TRUE);
+
+ assert_selection (selection, "1");
+ ret = gtk_selection_model_unselect_item (selection, 0);
+ g_assert_true (ret);
+ assert_selection (selection, "");
+ assert_selection_changes (selection, "0:1");
+
+ ignore_changes (selection);
+
+ g_object_unref (store);
+ g_object_unref (selection);
+}
+
+static int
+sort_inverse (gconstpointer a, gconstpointer b, gpointer data)
+{
+ int ia = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (a), number_quark));
+ int ib = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (b), number_quark));
+
+ return ib - ia;
+}
+
+static void
+test_persistence (void)
+{
+ GtkSelectionModel *selection;
+ GListStore *store;
+
+ store = new_store (1, 5, 1);
+ selection = new_model (store, TRUE, FALSE);
+ assert_selection (selection, "1");
+ assert_selection_changes (selection, "");
+ g_assert_true (gtk_selection_model_is_selected (selection, 0));
+ g_assert_false (gtk_selection_model_is_selected (selection, 4));
+
+ g_list_store_sort (store, sort_inverse, NULL);
+
+ assert_selection (selection, "1");
+ assert_selection_changes (selection, "");
+ g_assert_false (gtk_selection_model_is_selected (selection, 0));
+ g_assert_true (gtk_selection_model_is_selected (selection, 4));
+
+ ignore_changes (selection);
+
+ g_object_unref (store);
+ g_object_unref (selection);
+}
+
+static void
+check_query_range (GtkSelectionModel *selection)
+{
+ guint i, j;
+ guint position, n_items;
+ gboolean selected;
+
+ /* check that range always contains position, and has uniform selection */
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (selection)); i++)
+ {
+ position = i;
+ selected = gtk_selection_model_query_range (selection, &position, &n_items);
+ g_assert_cmpint (position, <=, i);
+ g_assert_cmpint (i, <, position + n_items);
+ for (j = position; j < position + n_items; j++)
+ g_assert_true (selected == gtk_selection_model_is_selected (selection, j));
+ }
+
+}
+
+static void
+test_query_range (void)
+{
+ GtkSelectionModel *selection;
+ GListStore *store;
+
+ store = new_store (1, 5, 1);
+ selection = new_model (store, TRUE, TRUE);
+ check_query_range (selection);
+
+ gtk_selection_model_unselect_item (selection, 0);
+ check_query_range (selection);
+
+ gtk_selection_model_select_item (selection, 2, TRUE);
+ check_query_range (selection);
+
+ gtk_selection_model_select_item (selection, 4, TRUE);
+ check_query_range (selection);
+
+ ignore_selection_changes (selection);
+
+ g_object_unref (store);
+ g_object_unref (selection);
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ setlocale (LC_ALL, "C");
+ g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=%s");
+
+ number_quark = g_quark_from_static_string ("Hell and fire was spawned to be released.");
+ changes_quark = g_quark_from_static_string ("What did I see? Can I believe what I saw?");
+ selection_quark = g_quark_from_static_string ("Mana mana, badibidibi");
+
+ g_test_add_func ("/singleselection/create", test_create);
+#if GLIB_CHECK_VERSION (2, 59, 0) /* g_list_store_get_item() has overflow issues before */
+ g_test_add_func ("/singleselection/autoselect", test_autoselect);
+ g_test_add_func ("/singleselection/selection", test_selection);
+ g_test_add_func ("/singleselection/can-unselect", test_can_unselect);
+ g_test_add_func ("/singleselection/persistence", test_persistence);
+#endif
+ g_test_add_func ("/singleselection/query-range", test_query_range);
+#if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */
+ g_test_add_func ("/singleselection/changes", test_changes);
+#endif
+
+ return g_test_run ();
+}