[gtk+] Populate popovers from menu models
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+] Populate popovers from menu models
- Date: Fri, 7 Feb 2014 20:12:06 +0000 (UTC)
commit 2fea2d4dbd700a60c41f07e8bfc44e05d0ef1f26
Author: Matthias Clasen <mclasen redhat com>
Date: Fri Feb 7 14:51:49 2014 -0500
Populate popovers from menu models
This adds a new function, gtk_popover_new_from_model, which creates
a popover and populates it with suitable content according to the
menu model. The current implementation uses GtkModelButton for the
individual items, and a GtkStack for submenus.
https://bugzilla.gnome.org/show_bug.cgi?id=723014
docs/reference/gtk/gtk3-sections.txt | 1 +
gtk/gtkpopover.c | 328 ++++++++++++++++++++++++++++++++++
gtk/gtkpopover.h | 4 +
3 files changed, 333 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt
index 1bd9a08..8fb9154 100644
--- a/docs/reference/gtk/gtk3-sections.txt
+++ b/docs/reference/gtk/gtk3-sections.txt
@@ -7859,6 +7859,7 @@ gtk_flow_box_child_changed
<TITLE>GtkPopover</TITLE>
GtkPopover
gtk_popover_new
+gtk_popover_new_from_model
gtk_popover_set_relative_to
gtk_popover_get_relative_to
gtk_popover_set_pointing_to
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index 820b9ca..0ac9e22 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -51,6 +51,16 @@
#include "gtkadjustment.h"
#include "gtkprivate.h"
#include "gtkintl.h"
+#include "gtklabel.h"
+#include "gtkbox.h"
+#include "gtkbutton.h"
+#include "gtkseparator.h"
+#include "gtkmodelbutton.h"
+#include "gtkwidgetprivate.h"
+#include "gtkactionmuxer.h"
+#include "gtkmenutracker.h"
+#include "gtkstack.h"
+#include "gtksizegroup.h"
#define TAIL_GAP_WIDTH 24
#define TAIL_HEIGHT 12
@@ -79,6 +89,7 @@ struct _GtkPopoverPrivate
GtkScrollable *parent_scrollable;
GtkAdjustment *vadj;
GtkAdjustment *hadj;
+ GtkMenuTracker *tracker;
cairo_rectangle_int_t pointing_to;
guint hierarchy_changed_id;
guint size_allocate_id;
@@ -190,6 +201,8 @@ gtk_popover_dispose (GObject *object)
GtkPopover *popover = GTK_POPOVER (object);
GtkPopoverPrivate *priv = popover->priv;
+ g_clear_pointer (&priv->tracker, gtk_menu_tracker_free);
+
if (priv->window)
_gtk_window_remove_popover (priv->window, GTK_WIDGET (object));
@@ -1819,3 +1832,318 @@ _gtk_popover_set_apply_shape (GtkPopover *popover,
gtk_popover_update_position (popover);
gtk_widget_queue_draw (GTK_WIDGET (popover));
}
+
+static void
+gtk_popover_tracker_remove_func (gint position,
+ gpointer user_data)
+{
+ GtkWidget *box = user_data;
+ GList *children;
+ GtkWidget *child;
+
+ g_assert (GTK_IS_BOX (box));
+
+ children = gtk_container_get_children (GTK_CONTAINER (box));
+ child = g_list_nth_data (children, position);
+ g_list_free (children);
+
+ gtk_widget_destroy (child);
+}
+
+static void
+gtk_popover_item_activate (GtkWidget *button,
+ gpointer user_data)
+{
+ GtkMenuTrackerItem *item = user_data;
+
+ gtk_menu_tracker_item_activated (item);
+
+ if (gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
+ gtk_widget_hide (gtk_widget_get_ancestor (button, GTK_TYPE_POPOVER));
+}
+
+static gboolean
+get_ancestors (GtkWidget *widget,
+ GType widget_type,
+ GtkWidget **ancestor,
+ GtkWidget **below)
+{
+ GtkWidget *a, *b;
+
+ a = NULL;
+ b = widget;
+ while (b != NULL)
+ {
+ a = gtk_widget_get_parent (b);
+ if (!a)
+ return FALSE;
+ if (g_type_is_a (G_OBJECT_TYPE (a), widget_type))
+ break;
+ b = a;
+ }
+
+ *below = b;
+ *ancestor = a;
+
+ return TRUE;
+}
+
+static void
+close_submenu (GtkWidget *button,
+ gpointer data)
+{
+ GtkMenuTrackerItem *item = data;
+ GtkWidget *stack;
+ GtkWidget *parent;
+ GtkWidget *focus;
+
+ if (gtk_menu_tracker_item_get_should_request_show (item))
+ gtk_menu_tracker_item_request_submenu_shown (item, FALSE);
+
+ focus = GTK_WIDGET (g_object_get_data (G_OBJECT (button), "focus"));
+
+ get_ancestors (focus, GTK_TYPE_STACK, &stack, &parent);
+ gtk_stack_set_visible_child (GTK_STACK (stack), parent);
+ gtk_widget_grab_focus (focus);
+}
+
+static void
+open_submenu (GtkWidget *button,
+ gpointer data)
+{
+ GtkMenuTrackerItem *item = data;
+ GtkWidget *stack;
+ GtkWidget *child;
+ GtkWidget *focus;
+
+ if (gtk_menu_tracker_item_get_should_request_show (item))
+ gtk_menu_tracker_item_request_submenu_shown (item, TRUE);
+
+ focus = GTK_WIDGET (g_object_get_data (G_OBJECT (button), "focus"));
+ get_ancestors (focus, GTK_TYPE_STACK, &stack, &child);
+ gtk_stack_set_visible_child (GTK_STACK (stack), child);
+ gtk_widget_grab_focus (focus);
+}
+
+static void
+gtk_popover_tracker_insert_func (GtkMenuTrackerItem *item,
+ gint position,
+ gpointer user_data)
+{
+ GtkWidget *box = user_data;
+ GtkWidget *stack;
+ GtkWidget *widget;
+ GtkSizeGroup *group;
+
+ stack = gtk_widget_get_ancestor (box, GTK_TYPE_STACK);
+ group = g_object_get_data (G_OBJECT (stack), "size-group");
+
+ if (gtk_menu_tracker_item_get_is_separator (item))
+ {
+ GtkWidget *separator;
+ const gchar *label;
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+
+ label = gtk_menu_tracker_item_get_label (item);
+
+ if (label != NULL)
+ {
+ GtkWidget *title;
+
+ title = gtk_label_new (label);
+ g_object_bind_property (item, "label", title, "label", G_BINDING_SYNC_CREATE);
+ gtk_style_context_add_class (gtk_widget_get_style_context (title), GTK_STYLE_CLASS_SEPARATOR);
+ gtk_widget_set_halign (title, GTK_ALIGN_START);
+ widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ g_object_set (widget,
+ "margin-start", 12,
+ "margin-end", 12,
+ "margin-top", 6,
+ "margin-bottom", 3,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (widget), title);
+ gtk_container_add (GTK_CONTAINER (widget), separator);
+ gtk_widget_show_all (widget);
+ }
+ else
+ {
+ widget = separator;
+ g_object_set (widget,
+ "margin-start", 12,
+ "margin-end", 12,
+ "margin-top", 3,
+ "margin-bottom", 3,
+ NULL);
+ gtk_widget_show (widget);
+ }
+ }
+ else if (gtk_menu_tracker_item_get_has_submenu (item))
+ {
+ GtkMenuTracker *tracker;
+ GtkWidget *child;
+ GtkWidget *button;
+ GtkWidget *content;
+ GtkWidget *parent;
+
+ child = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ g_object_set (child, "margin", 10, NULL);
+
+ button = (GtkWidget *) g_object_new (GTK_TYPE_MODEL_BUTTON,
+ "has-submenu", TRUE,
+ "inverted", TRUE,
+ "centered", TRUE,
+ NULL);
+ g_object_bind_property (item, "label", button, "text", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "icon", button, "icon", G_BINDING_SYNC_CREATE);
+
+ gtk_container_add (GTK_CONTAINER (child), button);
+ gtk_widget_show_all (child);
+
+ g_signal_connect (button, "clicked", G_CALLBACK (close_submenu), item);
+
+ gtk_stack_add_named (GTK_STACK (stack), child,
+ gtk_menu_tracker_item_get_label (item));
+ gtk_size_group_add_widget (group, child);
+
+ widget = (GtkWidget *) g_object_new (GTK_TYPE_MODEL_BUTTON,
+ "has-submenu", TRUE,
+ NULL);
+ g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "visible", widget, "visible", G_BINDING_SYNC_CREATE);
+ gtk_widget_show (widget);
+
+ g_signal_connect (widget, "clicked", G_CALLBACK (open_submenu), item);
+
+ g_object_set_data (G_OBJECT (widget), "focus", button);
+ g_object_set_data (G_OBJECT (button), "focus", widget);
+
+ content = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_widget_set_halign (content, GTK_ALIGN_FILL);
+ gtk_widget_show (content);
+ gtk_container_add (GTK_CONTAINER (child), content);
+ tracker = gtk_menu_tracker_new_for_item_submenu (item, gtk_popover_tracker_insert_func,
gtk_popover_tracker_remove_func, content);
+
+ g_object_set_data_full (G_OBJECT (widget), "submenutracker", tracker,
(GDestroyNotify)gtk_menu_tracker_free);
+
+ gtk_widget_show (widget);
+ }
+ else
+ {
+ widget = gtk_model_button_new ();
+ g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "icon", widget, "icon", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "visible", widget, "visible", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "role", widget, "action-role", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "toggled", widget, "toggled", G_BINDING_SYNC_CREATE);
+ g_object_bind_property (item, "accel", widget, "accel", G_BINDING_SYNC_CREATE);
+
+ g_signal_connect (widget, "clicked", G_CALLBACK (gtk_popover_item_activate), item);
+ }
+
+ g_object_set_data_full (G_OBJECT (widget), "GtkMenuTrackerItem", g_object_ref (item), g_object_unref);
+
+ gtk_container_add (GTK_CONTAINER (box), widget);
+ gtk_box_reorder_child (GTK_BOX (box), widget, position);
+}
+
+static void
+back_to_main (GtkWidget *popover)
+{
+ GtkWidget *stack;
+
+ stack = gtk_bin_get_child (GTK_BIN (popover));
+ gtk_stack_set_visible_child_name (GTK_STACK (stack), "main");
+}
+
+static void
+gtk_popover_bind_model (GtkPopover *popover,
+ GMenuModel *model,
+ const gchar *action_namespace,
+ gboolean with_separators)
+{
+ GtkActionMuxer *muxer;
+ GtkWidget *child;
+ GtkWidget *stack;
+ GtkWidget *box;
+ GtkPopoverPrivate *priv;
+ GtkSizeGroup *group;
+
+ g_return_if_fail (GTK_IS_POPOVER (popover));
+ g_return_if_fail (model == NULL || G_IS_MENU_MODEL (model));
+
+ priv = popover->priv;
+
+ muxer = _gtk_widget_get_action_muxer (GTK_WIDGET (popover));
+
+ g_clear_pointer (&priv->tracker, gtk_menu_tracker_free);
+
+ child = gtk_bin_get_child (GTK_BIN (popover));
+ if (child)
+ gtk_container_remove (GTK_CONTAINER (popover), child);
+
+ if (model)
+ {
+ stack = gtk_stack_new ();
+ group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ g_object_set_data_full (G_OBJECT (stack), "size-group", group, g_object_unref);
+ gtk_stack_set_homogeneous (GTK_STACK (stack), FALSE);
+ gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
+ gtk_widget_show (stack);
+ gtk_container_add (GTK_CONTAINER (popover), stack);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ g_object_set (box, "margin", 10, NULL);
+ gtk_widget_show (box);
+ gtk_stack_add_named (GTK_STACK (stack), box, "main");
+ gtk_size_group_add_widget (group, box);
+
+ g_signal_connect (popover, "unmap", G_CALLBACK (back_to_main), NULL);
+ g_signal_connect (popover, "map", G_CALLBACK (back_to_main), NULL);
+
+ priv->tracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (muxer),
+ model,
+ with_separators,
+ action_namespace,
+ gtk_popover_tracker_insert_func,
+ gtk_popover_tracker_remove_func,
+ box);
+ }
+}
+
+/**
+ * gtk_popover_new_from_model:
+ * @relative_to: #GtkWidget the popover is related to
+ * @model: a #GMenuModel
+ *
+ * Creates a #GtkPopover and populates it according to
+ * @model. The popover is pointed to the @relative_to wideget.
+ *
+ * The created buttons are connected to actions found in the
+ * #GtkApplicationWindow to which the popover belongs - typically
+ * by means of being attached to a widget that is contained within
+ * the #GtkApplicationWindows widget hierarchy.
+ *
+ * Actions can also be added using gtk_widget_insert_action_group()
+ * on the menus attach widget or on any of its parent widgets.
+ *
+ * Return value: the new #GtkPopover
+ *
+ * Since: 3.12
+ */
+GtkWidget *
+gtk_popover_new_from_model (GtkWidget *relative_to,
+ GMenuModel *model)
+{
+ GtkWidget *popover;
+
+ g_return_val_if_fail (G_IS_MENU_MODEL (model), NULL);
+
+ popover = gtk_popover_new (relative_to);
+ gtk_popover_bind_model (GTK_POPOVER (popover), model, NULL, TRUE);
+
+ return popover;
+}
diff --git a/gtk/gtkpopover.h b/gtk/gtkpopover.h
index b3b5a05..5343af1 100644
--- a/gtk/gtkpopover.h
+++ b/gtk/gtkpopover.h
@@ -61,6 +61,10 @@ GDK_AVAILABLE_IN_3_12
GtkWidget * gtk_popover_new (GtkWidget *relative_to);
GDK_AVAILABLE_IN_3_12
+GtkWidget * gtk_popover_new_from_model (GtkWidget *relative_to,
+ GMenuModel *model);
+
+GDK_AVAILABLE_IN_3_12
void gtk_popover_set_relative_to (GtkPopover *popover,
GtkWidget *relative_to);
GDK_AVAILABLE_IN_3_12
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]