Compare commits

...

10 Commits

Author SHA1 Message Date
Ryan Lortie
92d891e6ed GtkMenuSectionBox: hack and slash iconic support
A quick demo of what iconic support would look like....
2014-04-28 18:14:48 +02:00
Matthias Clasen
e66872474f Document iconic section support
https://bugzilla.gnome.org/show_bug.cgi?id=727477
2014-04-28 17:58:30 +02:00
Matthias Clasen
891b6fbb84 Add an example of iconic rendering
testpopover now shows several examples of icon buttons.

https://bugzilla.gnome.org/show_bug.cgi?id=727477
2014-04-28 17:58:30 +02:00
Matthias Clasen
aab6c15cb1 Add an iconic mode to model buttons
In iconic mode, model buttons will be styled like regular icon
buttons, preferring to show only the icon if one is set, falling
back to showing the label.

https://bugzilla.gnome.org/show_bug.cgi?id=727477
2014-04-28 17:58:30 +02:00
Matthias Clasen
36c1f1941f GtkMenuTrackerItem: Add support for verb-icons
When rendering iconic sections, we want to use icons for verbs,
and we want to differentiate these in the menu model, to keep
the icon attribute reserved for nouns.

https://bugzilla.gnome.org/show_bug.cgi?id=727477
2014-04-28 17:58:30 +02:00
Ryan Lortie
50e64e5978 GtkPopover: new approach to menu model binding
Instead of using GtkMenuTracker to flatten the sections into a single
linear menu, handle the sections ourselves by nesting boxes.

Each section gets an inner and outer box.  The inner box numbers its
children in the way that the tracker instructs.  The outer box
containes the inner box and the separator, if appropriate.

Having the two separate boxes will allow us to change the orientation of
the inner box if we want to pack widgets horizontally within a section.
2014-04-28 17:55:52 +02:00
Ryan Lortie
b82ef4ecc7 GtkMenuTracker: add 'merge_sections' flag
Add the possibility of a GtkMenuTracker that performs no section
merging.  Instead, it will report an item in the form of a separator for
subsections.  It is then possible to get a separate tracker for the
subsection contents by using gtk_menu_tracker_new_for_item_link().
2014-04-28 14:20:08 +02:00
Ryan Lortie
4408bfe1da GtkMenuTracker: don't specialise "submenu" link
We have some API in GtkMenuTracker and GtkMenuTrackerItem that is
specifically designed to deal with submenus.

Generalise these APIs to take a 'link_name' parameter that we always
give as G_MENU_SUBMENU for now.  In the future, this will allow creating
trackers for other types of links, such as sections.
2014-04-28 14:15:24 +02:00
Ryan Lortie
1299192411 GtkMenuTracker: make "is-visible" a property
Make this a property just like all of the other things and make the APIs
for accessing it non-private (but add a note that they are not intended
to be used).
2014-04-28 10:13:50 +02:00
Ryan Lortie
b79deb86cd GtkMenuTracker: remove "visible" property
This is in conflict with the "is-visible" pseudo-property which is about
to be promoted to being an actual property.
2014-04-28 10:12:25 +02:00
12 changed files with 764 additions and 325 deletions

View File

@@ -511,6 +511,7 @@ gtk_private_h_sources = \
gtkmenubuttonprivate.h \
gtkmenuprivate.h \
gtkmenuitemprivate.h \
gtkmenusectionbox.h \
gtkmenushellprivate.h \
gtkmenutracker.h \
gtkmenutrackeritem.h \
@@ -789,6 +790,7 @@ gtk_base_c_sources = \
gtkmenubar.c \
gtkmenubutton.c \
gtkmenuitem.c \
gtkmenusectionbox.c \
gtkmenushell.c \
gtkmenutracker.c \
gtkmenutrackeritem.c \

424
gtk/gtkmenusectionbox.c Normal file
View File

@@ -0,0 +1,424 @@
/*
* Copyright © 2014 Canonical Limited
* Copyright © 2013 Carlos Garnacho
*
* 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 licence, 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 <http://www.gnu.org/licenses/>.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
#include "config.h"
#include "gtkmenusectionbox.h"
#include "gtkwidgetprivate.h"
#include "gtklabel.h"
#include "gtkmenutracker.h"
#include "gtkmodelbutton.h"
#include "gtkseparator.h"
#include "gtksizegroup.h"
#include "gtkstack.h"
#include "gtkstylecontext.h"
#include "gtkpopover.h"
#include "gtkorientable.h"
typedef GtkBoxClass GtkMenuSectionBoxClass;
struct _GtkMenuSectionBox
{
GtkBox parent_instance;
GtkSizeGroup *size_group;
GtkMenuSectionBox *toplevel;
GtkMenuTracker *tracker;
GtkBox *item_box;
GtkWidget *separator;
guint separator_sync_idle;
gboolean iconic;
};
G_DEFINE_TYPE (GtkMenuSectionBox, gtk_menu_section_box, GTK_TYPE_BOX)
void gtk_menu_section_box_sync_separators (GtkMenuSectionBox *box,
gint *n_items);
void gtk_menu_section_box_new_submenu (GtkMenuTrackerItem *item,
GtkMenuSectionBox *toplevel,
GtkWidget *focus);
GtkWidget * gtk_menu_section_box_new_section (GtkMenuTrackerItem *item,
GtkMenuSectionBox *toplevel);
static void
gtk_menu_section_box_sync_item (GtkWidget *widget,
gpointer user_data)
{
gint *n_items = user_data;
if (GTK_IS_MENU_SECTION_BOX (widget))
gtk_menu_section_box_sync_separators (GTK_MENU_SECTION_BOX (widget), n_items);
else
(*n_items)++;
}
void
gtk_menu_section_box_sync_separators (GtkMenuSectionBox *box,
gint *n_items)
{
gboolean should_have_separator;
gint n_items_before = *n_items;
gtk_container_foreach (GTK_CONTAINER (box->item_box), gtk_menu_section_box_sync_item, n_items);
should_have_separator = n_items_before > 0 && *n_items > n_items_before;
if (box->separator == NULL)
return;
if (should_have_separator == (gtk_widget_get_parent (box->separator) != NULL))
return;
if (should_have_separator)
gtk_box_pack_start (GTK_BOX (box), box->separator, FALSE, FALSE, 0);
else
gtk_container_remove (GTK_CONTAINER (box), box->separator);
}
static gboolean
gtk_menu_section_box_handle_sync_separators (gpointer user_data)
{
GtkMenuSectionBox *box = user_data;
gint n_items = 0;
gtk_menu_section_box_sync_separators (box, &n_items);
box->separator_sync_idle = 0;
return G_SOURCE_REMOVE;
}
static void
gtk_menu_section_box_schedule_separator_sync (GtkMenuSectionBox *box)
{
box = box->toplevel;
if (!box->separator_sync_idle)
box->separator_sync_idle = gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE, /* before resize... */
gtk_menu_section_box_handle_sync_separators,
box, NULL);
}
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 void
gtk_menu_section_box_remove_func (gint position,
gpointer user_data)
{
GtkMenuSectionBox *box = user_data;
GList *children;
children = gtk_container_get_children (GTK_CONTAINER (box->item_box));
gtk_widget_destroy (g_list_nth_data (children, position));
g_list_free (children);
gtk_menu_section_box_schedule_separator_sync (box);
}
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_menu_section_box_insert_func (GtkMenuTrackerItem *item,
gint position,
gpointer user_data)
{
GtkMenuSectionBox *box = user_data;
GtkWidget *widget;
if (gtk_menu_tracker_item_get_is_separator (item))
{
widget = gtk_menu_section_box_new_section (item, box->toplevel);
}
else if (gtk_menu_tracker_item_get_has_link (item, G_MENU_LINK_SUBMENU))
{
widget = 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);
gtk_menu_section_box_new_submenu (item, box->toplevel, widget);
gtk_widget_show (widget);
}
else
{
widget = gtk_model_button_new ();
g_object_bind_property (item, "label", widget, "text", G_BINDING_SYNC_CREATE);
if (box->iconic)
{
g_object_bind_property (item, "verb-icon", widget, "icon", G_BINDING_SYNC_CREATE);
g_object_set (widget, "iconic", TRUE, "centered", TRUE, NULL);
}
else
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, "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);
}
gtk_widget_show (widget);
g_object_set_data_full (G_OBJECT (widget), "GtkMenuTrackerItem", g_object_ref (item), g_object_unref);
gtk_widget_set_halign (widget, GTK_ALIGN_FILL);
gtk_container_add (GTK_CONTAINER (box->item_box), widget);
gtk_box_reorder_child (GTK_BOX (box->item_box), widget, position);
gtk_menu_section_box_schedule_separator_sync (box);
}
static void
gtk_menu_section_box_init (GtkMenuSectionBox *box)
{
GtkWidget *item_box;
gtk_orientable_set_orientation (GTK_ORIENTABLE (box), GTK_ORIENTATION_VERTICAL);
box->toplevel = box;
item_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
box->item_box = GTK_BOX (item_box);
gtk_box_pack_end (GTK_BOX (box), item_box, FALSE, FALSE, 0);
gtk_widget_set_halign (GTK_WIDGET (item_box), GTK_ALIGN_FILL);
gtk_widget_show (item_box);
gtk_widget_set_halign (GTK_WIDGET (box), GTK_ALIGN_FILL);
g_object_set (box, "margin", 10, NULL);
}
static void
gtk_menu_section_box_dispose (GObject *object)
{
GtkMenuSectionBox *box = GTK_MENU_SECTION_BOX (object);
g_print ("disposed %p\n", object);
if (box->separator_sync_idle)
{
g_source_remove (box->separator_sync_idle);
box->separator_sync_idle = 0;
}
G_OBJECT_CLASS (gtk_menu_section_box_parent_class)->dispose (object);
}
static void
gtk_menu_section_box_class_init (GtkMenuSectionBoxClass *class)
{
G_OBJECT_CLASS (class)->dispose = gtk_menu_section_box_dispose;
}
void
gtk_menu_section_box_new_toplevel (GtkStack *stack,
GMenuModel *model,
const gchar *action_namespace)
{
GtkMenuSectionBox *box;
box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
box->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
gtk_size_group_add_widget (box->size_group, GTK_WIDGET (box));
gtk_stack_add_named (stack, GTK_WIDGET (box), "main");
box->tracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (_gtk_widget_get_action_muxer (GTK_WIDGET (box))),
model, TRUE, FALSE, action_namespace,
gtk_menu_section_box_insert_func,
gtk_menu_section_box_remove_func, box);
gtk_widget_show (GTK_WIDGET (box));
}
void
gtk_menu_section_box_new_submenu (GtkMenuTrackerItem *item,
GtkMenuSectionBox *toplevel,
GtkWidget *focus)
{
GtkMenuSectionBox *box;
GtkWidget *button;
box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
box->size_group = g_object_ref (toplevel->size_group);
gtk_size_group_add_widget (box->size_group, GTK_WIDGET (box));
button = 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);
g_object_set_data (G_OBJECT (button), "focus", focus);
g_object_set_data (G_OBJECT (focus), "focus", button);
gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
gtk_widget_show (button);
g_signal_connect (focus, "clicked", G_CALLBACK (open_submenu), item);
g_signal_connect (button, "clicked", G_CALLBACK (close_submenu), item);
gtk_stack_add_named (GTK_STACK (gtk_widget_get_ancestor (GTK_WIDGET (toplevel), GTK_TYPE_STACK)),
GTK_WIDGET (box), gtk_menu_tracker_item_get_label (item));
gtk_widget_show (GTK_WIDGET (box));
box->tracker = gtk_menu_tracker_new_for_item_link (item, G_MENU_LINK_SUBMENU, FALSE,
gtk_menu_section_box_insert_func,
gtk_menu_section_box_remove_func,
box);
}
GtkWidget *
gtk_menu_section_box_new_section (GtkMenuTrackerItem *item,
GtkMenuSectionBox *toplevel)
{
GtkMenuSectionBox *box;
GtkWidget *separator;
const gchar *label;
const gchar *hint;
box = g_object_new (GTK_TYPE_MENU_SECTION_BOX, NULL);
box->size_group = g_object_ref (toplevel->size_group);
box->toplevel = toplevel;
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
label = gtk_menu_tracker_item_get_label (item);
hint = gtk_menu_tracker_item_get_display_hint (item);
if (hint && g_str_equal (hint, "iconic"))
{
gtk_orientable_set_orientation (GTK_ORIENTABLE (box->item_box), GTK_ORIENTATION_HORIZONTAL);
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (box->item_box)), GTK_STYLE_CLASS_LINKED);
box->iconic = TRUE;
}
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);
box->separator = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
g_object_set (box->separator,
"margin-start", 12,
"margin-end", 12,
"margin-top", 6,
"margin-bottom", 3,
NULL);
gtk_container_add (GTK_CONTAINER (box->separator), title);
gtk_container_add (GTK_CONTAINER (box->separator), separator);
gtk_widget_show_all (box->separator);
}
else
{
box->separator = separator;
g_object_set (box->separator,
"margin-start", 12,
"margin-end", 12,
"margin-top", 3,
"margin-bottom", 3,
NULL);
gtk_widget_show (box->separator);
}
box->tracker = gtk_menu_tracker_new_for_item_link (item, G_MENU_LINK_SECTION, FALSE,
gtk_menu_section_box_insert_func,
gtk_menu_section_box_remove_func,
box);
return GTK_WIDGET (box);
}

49
gtk/gtkmenusectionbox.h Normal file
View File

@@ -0,0 +1,49 @@
/*
* Copyright © 2014 Codethink Limited
*
* 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 licence, 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 <http://www.gnu.org/licenses/>.
*
* Author: Ryan Lortie <desrt@desrt.ca>
*/
#ifndef __GTK_MENU_SECTION_BOX_H__
#define __GTK_MENU_SECTION_BOX_H__
#include <gtk/gtkmenutrackeritem.h>
#include <gtk/gtkstack.h>
#include <gtk/gtkbox.h>
G_BEGIN_DECLS
#define GTK_TYPE_MENU_SECTION_BOX (gtk_menu_section_box_get_type ())
#define GTK_MENU_SECTION_BOX(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \
GTK_TYPE_MENU_SECTION_BOX, GtkMenuSectionBox))
#define GTK_MENU_SECTION_BOX_CLASS(class) (G_TYPE_CHECK_CLASS_CAST ((class), \
GTK_TYPE_MENU_SECTION_BOX, GtkMenuSectionBoxClass))
#define GTK_IS_MENU_SECTION_BOX(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \
GTK_TYPE_MENU_SECTION_BOX))
#define GTK_IS_MENU_SECTION_BOX_CLASS(class) (G_TYPE_CHECK_CLASS_TYPE ((class), \
GTK_TYPE_MENU_SECTION_BOX))
#define GTK_MENU_SECTION_BOX_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), \
GTK_TYPE_MENU_SECTION_BOX, GtkMenuSectionBoxClass))
typedef struct _GtkMenuSectionBox GtkMenuSectionBox;
void gtk_menu_section_box_new_toplevel (GtkStack *stack,
GMenuModel *model,
const gchar *action_namespace);
G_END_DECLS
#endif /* __GTK_MENU_SECTION_BOX_H__ */

View File

@@ -2057,7 +2057,7 @@ gtk_menu_shell_tracker_insert_func (GtkMenuTrackerItem *item,
gtk_widget_show (widget);
}
else if (gtk_menu_tracker_item_get_has_submenu (item))
else if (gtk_menu_tracker_item_get_has_link (item, G_MENU_LINK_SUBMENU))
{
GtkMenuShell *submenu;
@@ -2070,10 +2070,11 @@ gtk_menu_shell_tracker_insert_func (GtkMenuTrackerItem *item,
* prevent arbitrary recursion depth. We could also do it
* lazy...
*/
submenu->priv->tracker = gtk_menu_tracker_new_for_item_submenu (item,
gtk_menu_shell_tracker_insert_func,
gtk_menu_shell_tracker_remove_func,
submenu);
submenu->priv->tracker = gtk_menu_tracker_new_for_item_link (item,
G_MENU_LINK_SUBMENU, TRUE,
gtk_menu_shell_tracker_insert_func,
gtk_menu_shell_tracker_remove_func,
submenu);
gtk_menu_item_set_submenu (GTK_MENU_ITEM (widget), GTK_WIDGET (submenu));
if (gtk_menu_tracker_item_get_should_request_show (item))
@@ -2185,7 +2186,7 @@ gtk_menu_shell_bind_model (GtkMenuShell *menu_shell,
if (model)
menu_shell->priv->tracker = gtk_menu_tracker_new (GTK_ACTION_OBSERVABLE (muxer),
model, with_separators, action_namespace,
model, with_separators, FALSE, action_namespace,
gtk_menu_shell_tracker_insert_func,
gtk_menu_shell_tracker_remove_func,
menu_shell);

View File

@@ -60,6 +60,7 @@ typedef struct _GtkMenuTrackerSection GtkMenuTrackerSection;
struct _GtkMenuTracker
{
GtkActionObservable *observable;
gboolean merge_sections;
GtkMenuTrackerInsertFunc insert_func;
GtkMenuTrackerRemoveFunc remove_func;
gpointer user_data;
@@ -308,7 +309,8 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
submenu = g_menu_model_get_item_link (model, position + n_items, G_MENU_LINK_SECTION);
g_assert (submenu != model);
if (submenu != NULL)
if (submenu != NULL && tracker->merge_sections)
{
GtkMenuTrackerSection *subsection;
gchar *action_namespace = NULL;
@@ -340,7 +342,7 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
GtkMenuTrackerItem *item;
item = _gtk_menu_tracker_item_new (tracker->observable, model, position + n_items,
section->action_namespace, FALSE);
section->action_namespace, submenu != NULL);
/* In the case that the item may disappear we handle that by
* treating the item that we just created as being its own
@@ -367,19 +369,19 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
* case that we want to show a separator, but we will never do
* that because separators are not shown for this fake section.
*/
if (_gtk_menu_tracker_item_may_disappear (item))
if (gtk_menu_tracker_item_may_disappear (item))
{
GtkMenuTrackerSection *fake_section;
fake_section = g_slice_new0 (GtkMenuTrackerSection);
fake_section->is_fake = TRUE;
fake_section->model = g_object_ref (item);
fake_section->handler = g_signal_connect (item, "visibility-changed",
fake_section->handler = g_signal_connect (item, "notify::is-visible",
G_CALLBACK (gtk_menu_tracker_item_visibility_changed),
tracker);
*change_point = g_slist_prepend (*change_point, fake_section);
if (_gtk_menu_tracker_item_is_visible (item))
if (gtk_menu_tracker_item_get_is_visible (item))
{
(* tracker->insert_func) (item, offset, tracker->user_data);
fake_section->items = g_slist_prepend (NULL, NULL);
@@ -540,6 +542,7 @@ GtkMenuTracker *
gtk_menu_tracker_new (GtkActionObservable *observable,
GMenuModel *model,
gboolean with_separators,
gboolean merge_sections,
const gchar *action_namespace,
GtkMenuTrackerInsertFunc insert_func,
GtkMenuTrackerRemoveFunc remove_func,
@@ -548,6 +551,7 @@ gtk_menu_tracker_new (GtkActionObservable *observable,
GtkMenuTracker *tracker;
tracker = g_slice_new (GtkMenuTracker);
tracker->merge_sections = merge_sections;
tracker->observable = g_object_ref (observable);
tracker->insert_func = insert_func;
tracker->remove_func = remove_func;
@@ -560,20 +564,22 @@ gtk_menu_tracker_new (GtkActionObservable *observable,
}
GtkMenuTracker *
gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
GtkMenuTrackerInsertFunc insert_func,
GtkMenuTrackerRemoveFunc remove_func,
gpointer user_data)
gtk_menu_tracker_new_for_item_link (GtkMenuTrackerItem *item,
const gchar *link_name,
gboolean merge_sections,
GtkMenuTrackerInsertFunc insert_func,
GtkMenuTrackerRemoveFunc remove_func,
gpointer user_data)
{
GtkMenuTracker *tracker;
GMenuModel *submenu;
gchar *namespace;
submenu = _gtk_menu_tracker_item_get_submenu (item);
namespace = _gtk_menu_tracker_item_get_submenu_namespace (item);
submenu = _gtk_menu_tracker_item_get_link (item, link_name);
namespace = _gtk_menu_tracker_item_get_link_namespace (item);
tracker = gtk_menu_tracker_new (_gtk_menu_tracker_item_get_observable (item), submenu,
TRUE, namespace, insert_func, remove_func, user_data);
TRUE, merge_sections, namespace, insert_func, remove_func, user_data);
g_object_unref (submenu);
g_free (namespace);

View File

@@ -35,12 +35,15 @@ typedef void (* GtkMenuTrackerRemoveFunc) (gint
GtkMenuTracker * gtk_menu_tracker_new (GtkActionObservable *observer,
GMenuModel *model,
gboolean with_separators,
gboolean merge_sections,
const gchar *action_namespace,
GtkMenuTrackerInsertFunc insert_func,
GtkMenuTrackerRemoveFunc remove_func,
gpointer user_data);
GtkMenuTracker * gtk_menu_tracker_new_for_item_submenu (GtkMenuTrackerItem *item,
GtkMenuTracker * gtk_menu_tracker_new_for_item_link (GtkMenuTrackerItem *item,
const gchar *link_name,
gboolean merge_sections,
GtkMenuTrackerInsertFunc insert_func,
GtkMenuTrackerRemoveFunc remove_func,
gpointer user_data);

View File

@@ -106,20 +106,19 @@ struct _GtkMenuTrackerItem
enum {
PROP_0,
PROP_IS_SEPARATOR,
PROP_HAS_SUBMENU,
PROP_LABEL,
PROP_ICON,
PROP_VERB_ICON,
PROP_SENSITIVE,
PROP_VISIBLE,
PROP_ROLE,
PROP_TOGGLED,
PROP_ACCEL,
PROP_SUBMENU_SHOWN,
PROP_IS_VISIBLE,
N_PROPS
};
static GParamSpec *gtk_menu_tracker_item_pspecs[N_PROPS];
static guint gtk_menu_tracker_visibility_changed_signal;
static void gtk_menu_tracker_item_init_observer_iface (GtkActionObserverInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GtkMenuTrackerItem, gtk_menu_tracker_item, G_TYPE_OBJECT,
@@ -161,21 +160,18 @@ gtk_menu_tracker_item_get_property (GObject *object,
case PROP_IS_SEPARATOR:
g_value_set_boolean (value, gtk_menu_tracker_item_get_is_separator (self));
break;
case PROP_HAS_SUBMENU:
g_value_set_boolean (value, gtk_menu_tracker_item_get_has_submenu (self));
break;
case PROP_LABEL:
g_value_set_string (value, gtk_menu_tracker_item_get_label (self));
break;
case PROP_ICON:
g_value_set_object (value, gtk_menu_tracker_item_get_icon (self));
break;
case PROP_VERB_ICON:
g_value_set_object (value, gtk_menu_tracker_item_get_verb_icon (self));
break;
case PROP_SENSITIVE:
g_value_set_boolean (value, gtk_menu_tracker_item_get_sensitive (self));
break;
case PROP_VISIBLE:
g_value_set_boolean (value, gtk_menu_tracker_item_get_visible (self));
break;
case PROP_ROLE:
g_value_set_enum (value, gtk_menu_tracker_item_get_role (self));
break;
@@ -188,6 +184,9 @@ gtk_menu_tracker_item_get_property (GObject *object,
case PROP_SUBMENU_SHOWN:
g_value_set_boolean (value, gtk_menu_tracker_item_get_submenu_shown (self));
break;
case PROP_IS_VISIBLE:
g_value_set_boolean (value, gtk_menu_tracker_item_get_is_visible (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -222,16 +221,14 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class)
gtk_menu_tracker_item_pspecs[PROP_IS_SEPARATOR] =
g_param_spec_boolean ("is-separator", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_HAS_SUBMENU] =
g_param_spec_boolean ("has-submenu", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_LABEL] =
g_param_spec_string ("label", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_ICON] =
g_param_spec_object ("icon", "", "", G_TYPE_ICON, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_VERB_ICON] =
g_param_spec_object ("verb-icon", "", "", G_TYPE_ICON, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_SENSITIVE] =
g_param_spec_boolean ("sensitive", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_VISIBLE] =
g_param_spec_boolean ("visible", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_ROLE] =
g_param_spec_enum ("role", "", "",
GTK_TYPE_MENU_TRACKER_ITEM_ROLE, GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
@@ -242,13 +239,10 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class)
g_param_spec_string ("accel", "", "", NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_SUBMENU_SHOWN] =
g_param_spec_boolean ("submenu-shown", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
gtk_menu_tracker_item_pspecs[PROP_IS_VISIBLE] =
g_param_spec_boolean ("is-visible", "", "", FALSE, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs);
gtk_menu_tracker_visibility_changed_signal = g_signal_new ("visibility-changed", GTK_TYPE_MENU_TRACKER_ITEM,
G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE,
1, G_TYPE_BOOLEAN);
}
/* This syncs up the visibility for the hidden-when='' case. We call it
@@ -282,7 +276,7 @@ gtk_menu_tracker_item_update_visibility (GtkMenuTrackerItem *self)
if (visible != self->is_visible)
{
self->is_visible = visible;
g_signal_emit (self, gtk_menu_tracker_visibility_changed_signal, 0, visible);
g_object_notify (G_OBJECT (self), "is-visible");
}
}
@@ -565,11 +559,12 @@ gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self)
* for #GtkMenuTrackerItem.
*/
gboolean
gtk_menu_tracker_item_get_has_submenu (GtkMenuTrackerItem *self)
gtk_menu_tracker_item_get_has_link (GtkMenuTrackerItem *self,
const gchar *link_name)
{
GMenuModel *link;
link = g_menu_item_get_link (self->item, G_MENU_LINK_SUBMENU);
link = g_menu_item_get_link (self->item, link_name);
if (link)
{
@@ -612,18 +607,34 @@ gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self)
return icon;
}
/*< private >
* gtk_menu_tracker_item_get_verb_icon:
*
* Returns: (transfer full):
*/
GIcon *
gtk_menu_tracker_item_get_verb_icon (GtkMenuTrackerItem *self)
{
GVariant *icon_data;
GIcon *icon;
icon_data = g_menu_item_get_attribute_value (self->item, "verb-icon", NULL);
if (icon_data == NULL)
return NULL;
icon = g_icon_deserialize (icon_data);
g_variant_unref (icon_data);
return icon;
}
gboolean
gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self)
{
return self->sensitive;
}
gboolean
gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self)
{
return TRUE;
}
GtkMenuTrackerItemRole
gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self)
{
@@ -663,14 +674,25 @@ gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self)
return special;
}
GMenuModel *
_gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self)
const gchar *
gtk_menu_tracker_item_get_display_hint (GtkMenuTrackerItem *self)
{
return g_menu_item_get_link (self->item, "submenu");
const gchar *display_hint = NULL;
g_menu_item_get_attribute (self->item, "display-hint", "&s", &display_hint);
return display_hint;
}
GMenuModel *
_gtk_menu_tracker_item_get_link (GtkMenuTrackerItem *self,
const gchar *link_name)
{
return g_menu_item_get_link (self->item, link_name);
}
gchar *
_gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self)
_gtk_menu_tracker_item_get_link_namespace (GtkMenuTrackerItem *self)
{
const gchar *namespace;
@@ -890,14 +912,29 @@ gtk_menu_tracker_item_request_submenu_shown (GtkMenuTrackerItem *self,
gtk_menu_tracker_item_set_submenu_shown (self, shown);
}
/**
* gtk_menu_tracker_item_get_is_visible:
* @self: A #GtkMenuTrackerItem instance
*
* Don't use this unless you're tracking items for yourself -- normally
* the tracker will emit add/remove automatically when this changes.
*
* Returns: if the item should currently be shown
*/
gboolean
_gtk_menu_tracker_item_is_visible (GtkMenuTrackerItem *self)
gtk_menu_tracker_item_get_is_visible (GtkMenuTrackerItem *self)
{
return self->is_visible;
}
/**
* gtk_menu_tracker_item_may_disappear:
* @self: A #GtkMenuTrackerItem instance
*
* Returns: if the item may disappear (ie: is-visible property may change)
*/
gboolean
_gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self)
gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self)
{
return self->hidden_when != HIDDEN_NEVER;
}

View File

@@ -50,19 +50,22 @@ GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActi
const gchar * gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self);
const gchar * gtk_menu_tracker_item_get_display_hint (GtkMenuTrackerItem *self);
GtkActionObservable * _gtk_menu_tracker_item_get_observable (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_is_separator (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_has_submenu (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_has_link (GtkMenuTrackerItem *self,
const gchar *link_name);
const gchar * gtk_menu_tracker_item_get_label (GtkMenuTrackerItem *self);
GIcon * gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self);
GIcon * gtk_menu_tracker_item_get_verb_icon (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self);
GtkMenuTrackerItemRole gtk_menu_tracker_item_get_role (GtkMenuTrackerItem *self);
@@ -70,13 +73,14 @@ gboolean gtk_menu_tracker_item_get_toggled (GtkMenu
const gchar * gtk_menu_tracker_item_get_accel (GtkMenuTrackerItem *self);
GMenuModel * _gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self);
GMenuModel * _gtk_menu_tracker_item_get_link (GtkMenuTrackerItem *self,
const gchar *link_name);
gchar * _gtk_menu_tracker_item_get_submenu_namespace (GtkMenuTrackerItem *self);
gchar * _gtk_menu_tracker_item_get_link_namespace (GtkMenuTrackerItem *self);
gboolean _gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self);
gboolean _gtk_menu_tracker_item_is_visible (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_is_visible (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_should_request_show (GtkMenuTrackerItem *self);

View File

@@ -41,6 +41,7 @@ struct _GtkModelButton
gboolean has_submenu;
gboolean centered;
gboolean inverted;
gboolean iconic;
GtkMenuTrackerItemRole role;
};
@@ -58,7 +59,8 @@ enum
PROP_ACCEL,
PROP_HAS_SUBMENU,
PROP_INVERTED,
PROP_CENTERED
PROP_CENTERED,
PROP_ICONIC
};
static void
@@ -96,21 +98,33 @@ gtk_model_button_set_action_role (GtkModelButton *button,
atk_object_set_role (accessible, a11y_role);
}
static void
update_visibility (GtkModelButton *button)
{
gboolean has_icon;
gboolean has_text;
has_icon = gtk_image_get_storage_type (GTK_IMAGE (button->image)) != GTK_IMAGE_EMPTY;
has_text = gtk_label_get_text (GTK_LABEL (button->label))[0] != '\0';
gtk_widget_set_visible (button->image, has_icon);
gtk_widget_set_visible (button->label, has_text && (!button->iconic || !has_icon));
}
static void
gtk_model_button_set_icon (GtkModelButton *button,
GIcon *icon)
{
gtk_image_set_from_gicon (GTK_IMAGE (button->image), icon, GTK_ICON_SIZE_MENU);
gtk_widget_set_visible (button->image, icon != NULL);
update_visibility (button);
}
static void
gtk_model_button_set_text (GtkModelButton *button,
const gchar *text)
{
if (text != NULL)
gtk_label_set_text_with_mnemonic (GTK_LABEL (button->label), text);
gtk_widget_set_visible (button->label, text != NULL);
gtk_label_set_text_with_mnemonic (GTK_LABEL (button->label), text);
update_visibility (button);
}
static void
@@ -153,6 +167,28 @@ gtk_model_button_set_centered (GtkModelButton *button,
gtk_widget_queue_draw (GTK_WIDGET (button));
}
static void
gtk_model_button_set_iconic (GtkModelButton *button,
gboolean iconic)
{
button->iconic = iconic;
if (iconic)
{
gtk_style_context_remove_class (gtk_widget_get_style_context (GTK_WIDGET (button)),
GTK_STYLE_CLASS_MENUITEM);
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NORMAL);
}
else
{
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (button)),
GTK_STYLE_CLASS_MENUITEM);
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
}
update_visibility (button);
gtk_widget_queue_resize (GTK_WIDGET (button));
}
static void
gtk_model_button_set_property (GObject *object,
guint prop_id,
@@ -195,6 +231,10 @@ gtk_model_button_set_property (GObject *object,
gtk_model_button_set_centered (button, g_value_get_boolean (value));
break;
case PROP_ICONIC:
gtk_model_button_set_iconic (button, g_value_get_boolean (value));
break;
default:
g_assert_not_reached ();
}
@@ -223,7 +263,10 @@ gtk_model_button_get_full_border (GtkModelButton *button,
border->top = border_width + focus_width + focus_pad;
border->bottom = border_width + focus_width + focus_pad;
*indicator = indicator_size + 2 * indicator_spacing;
if (button->iconic)
*indicator = 0;
else
*indicator = indicator_size + 2 * indicator_spacing;
}
static gboolean
@@ -540,12 +583,22 @@ gtk_model_button_draw (GtkWidget *widget,
gint focus_width, focus_pad;
gint baseline;
state = get_button_state (model_button);
context = gtk_widget_get_style_context (widget);
gtk_style_context_save (context);
gtk_style_context_set_state (context, state);
if (model_button->iconic)
{
GTK_WIDGET_CLASS (gtk_model_button_parent_class)->draw (widget, cr);
goto out;
}
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
context = gtk_widget_get_style_context (widget);
baseline = gtk_widget_get_allocated_baseline (widget);
state = get_button_state (model_button);
gtk_widget_style_get (widget,
"focus-line-width", &focus_width,
@@ -566,9 +619,6 @@ gtk_model_button_draw (GtkWidget *widget,
y = CLAMP (baseline - indicator_size * button->priv->baseline_align,
0, height - indicator_size);
gtk_style_context_save (context);
gtk_style_context_set_state (context, state);
gtk_render_background (context, cr,
border_width, border_width,
width - 2 * border_width,
@@ -614,12 +664,13 @@ gtk_model_button_draw (GtkWidget *widget,
height - 2 * (border_width + focus_pad) - border.top - border.bottom);
}
gtk_style_context_restore (context);
child = gtk_bin_get_child (GTK_BIN (widget));
if (child)
gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
out:
gtk_style_context_restore (context);
return FALSE;
}
@@ -640,30 +691,34 @@ gtk_model_button_class_init (GtkModelButtonClass *class)
widget_class->draw = gtk_model_button_draw;
g_object_class_install_property (object_class, PROP_ACTION_ROLE,
g_param_spec_enum ("action-role", "action role", "action role",
g_param_spec_enum ("action-role", "", "",
GTK_TYPE_MENU_TRACKER_ITEM_ROLE,
GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ICON,
g_param_spec_object ("icon", "icon", "icon", G_TYPE_ICON,
g_param_spec_object ("icon", "", "", G_TYPE_ICON,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_TEXT,
g_param_spec_string ("text", "text", "text", NULL,
g_param_spec_string ("text", "", "", NULL,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_TOGGLED,
g_param_spec_boolean ("toggled", "toggled", "toggled", FALSE,
g_param_spec_boolean ("toggled", "", "", FALSE,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ACCEL,
g_param_spec_string ("accel", "accel", "accel", NULL,
g_param_spec_string ("accel", "", "", NULL,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_HAS_SUBMENU,
g_param_spec_boolean ("has-submenu", "has-submenu", "has-submenu", FALSE,
g_param_spec_boolean ("has-submenu", "", "", FALSE,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_INVERTED,
g_param_spec_boolean ("inverted", "inverted", "inverted", FALSE,
g_param_spec_boolean ("inverted", "", "", FALSE,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_CENTERED,
g_param_spec_boolean ("centered", "centered", "centered", FALSE,
g_param_spec_boolean ("centered", "", "", FALSE,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_ICONIC,
g_param_spec_boolean ("iconic", "", "", TRUE,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
gtk_widget_class_set_accessible_role (GTK_WIDGET_CLASS (class), ATK_ROLE_PUSH_BUTTON);

View File

@@ -37,6 +37,40 @@
* is desired on a popover, gtk_popover_set_modal() may be called on it
* to tweak its behavior.
*
* ## GtkPopover as menu replacement
*
* GtkPopover is often used to replace menus. To facilitate this, it
* supports being populated from a #GMenuModel, using
* gtk_popover_new_from_model(). In addition to all the regular menu
* model features, this function supports rendering sections in the
* model in a more compact form, as a row of icon buttons instead of
* menu items.
*
* To use this rendering, set the ”display-hint” attribute
* of the section to ”iconic” and set the icons of your items with
* the ”verb-icon” attribute.
*
* |[
* <section>
* <attribute name="display-hint">iconic</attribute>
* <item>
* <attribute name="label">Cut</attribute>
* <attribute name="action">app.cut</attribute>
* <attribute name="verb-icon">edit-cut-symbolic</attribute>
* </item>
* <item>
* <attribute name="label">Copy</attribute>
* <attribute name="action">app.copy</attribute>
* <attribute name="verb-icon">edit-copy-symbolic</attribute>
* </item>
* <item>
* <attribute name="label">Paste</attribute>
* <attribute name="action">app.paste</attribute>
* <attribute name="verb-icon">edit-paste-symbolic</attribute>
* </item>
* </section>
* ]|
*
* Since: 3.12
*/
@@ -62,6 +96,7 @@
#include "gtkstack.h"
#include "gtksizegroup.h"
#include "a11y/gtkpopoveraccessible.h"
#include "gtkmenusectionbox.h"
#define TAIL_GAP_WIDTH 24
#define TAIL_HEIGHT 12
@@ -88,7 +123,6 @@ struct _GtkPopoverPrivate
GtkScrollable *parent_scrollable;
GtkAdjustment *vadj;
GtkAdjustment *hadj;
GtkMenuTracker *tracker;
GdkRectangle pointing_to;
guint hierarchy_changed_id;
guint size_allocate_id;
@@ -201,8 +235,6 @@ 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));
@@ -1895,221 +1927,6 @@ gtk_popover_get_modal (GtkPopover *popover)
return popover->priv->modal;
}
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;
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)
{
@@ -2158,52 +1975,29 @@ gtk_popover_bind_model (GtkPopover *popover,
GMenuModel *model,
const gchar *action_namespace)
{
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);
gtk_widget_destroy (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);
gtk_menu_section_box_new_toplevel (GTK_STACK (stack), model, action_namespace);
gtk_stack_set_visible_child_name (GTK_STACK (stack), "main");
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,
TRUE,
action_namespace,
gtk_popover_tracker_insert_func,
gtk_popover_tracker_remove_func,
box);
}
}

View File

@@ -1,5 +1,23 @@
<interface>
<menu id="menu">
<section>
<attribute name="display-hint">iconic</attribute>
<item>
<attribute name="label">Cut</attribute>
<attribute name="action">top.cut</attribute>
<attribute name="verb-icon">edit-cut-symbolic</attribute>
</item>
<item>
<attribute name="label">Copy</attribute>
<attribute name="action">top.copy</attribute>
<attribute name="verb-icon">edit-copy-symbolic</attribute>
</item>
<item>
<attribute name="label">Paste</attribute>
<attribute name="action">top.paste</attribute>
<attribute name="verb-icon">edit-paste-symbolic</attribute>
</item>
</section>
<section>
<item>
<attribute name="label">No action</attribute>
@@ -44,6 +62,21 @@
<attribute name="label">Item 5b</attribute>
<attribute name="action">top.action5</attribute>
</item>
<section>
<attribute name="display-hint">iconic</attribute>
<item>
<attribute name="label">List</attribute>
<attribute name="action">top.set-view</attribute>
<attribute name="target">list</attribute>
<attribute name="verb-icon">view-list-symbolic</attribute>
</item>
<item>
<attribute name="label">Grid</attribute>
<attribute name="action">top.set-view</attribute>
<attribute name="target">grid</attribute>
<attribute name="verb-icon">view-grid-symbolic</attribute>
</item>
</section>
<item>
<attribute name="label">Item 5c</attribute>
<attribute name="action">top.action5</attribute>
@@ -53,6 +86,29 @@
<attribute name="action">top.action5</attribute>
</item>
</section>
<section>
<attribute name="display-hint">iconic</attribute>
<attribute name="label">Format</attribute>
<item>
<attribute name="label">Bold</attribute>
<attribute name="action">top.bold</attribute>
</item>
<item>
<attribute name="label">Italic</attribute>
<attribute name="action">top.italic</attribute>
<attribute name="verb-icon">format-text-italic-symbolic</attribute>
</item>
<item>
<attribute name="label">Strikethrough</attribute>
<attribute name="action">top.strikethrough</attribute>
<attribute name="verb-icon">format-text-strikethrough-symbolic</attribute>
</item>
<item>
<attribute name="label">Underline</attribute>
<attribute name="action">top.underline</attribute>
<attribute name="verb-icon">format-text-underline-symbolic</attribute>
</item>
</section>
<section>
<attribute name="label">6666</attribute>
<item>

View File

@@ -9,6 +9,14 @@ activate (GSimpleAction *action,
}
static GActionEntry entries[] = {
{ "cut", activate, NULL, NULL, NULL },
{ "copy", activate, NULL, NULL, NULL },
{ "paste", activate, NULL, NULL, NULL },
{ "bold", NULL, NULL, "false", NULL },
{ "italic", NULL, NULL, "false", NULL },
{ "strikethrough", NULL, NULL, "false", NULL },
{ "underline", NULL, NULL, "false", NULL },
{ "set-view", NULL, "s", "'list'", NULL },
{ "action1", activate, NULL, NULL, NULL },
{ "action2", NULL, NULL, "true", NULL },
{ "action2a", NULL, NULL, "false", NULL },