Compare commits

...

8 Commits

Author SHA1 Message Date
Matthias Clasen
31c6c96876 Add a paned example to gtk3-widget-factory 2014-04-27 10:28:43 -04:00
Matthias Clasen
5f0398a688 Add support for scale widgets to model-based popovers
This allows to use scales for actions with double state in model-based
popovers. While the value for the scale is kept in the action, the
static properties such as minimum, maximum, step, marks, are provided
by attributes of the menuitem.

The testpopover testcase includes an example of this.

https://bugzilla.gnome.org/show_bug.cgi?id=727477
2014-04-26 20:19:46 -04:00
Matthias Clasen
2ab13aeeaf Document iconic section support 2014-04-26 20:04:15 -04:00
Matthias Clasen
8d79d3fd44 Add an example of iconic rendering
testpopover now shows several examples of icon buttons.
2014-04-26 20:04:09 -04:00
Matthias Clasen
12ee00e861 GtkPopover: Use display-hint for menus
When an 'iconic' display-hint is specified on a section,
render the actions as a row of iconic model buttons, instead
of like menu items. We're using the verb-icon attribute
here, to keep noun and verb icons cleanly separated.

This works with regular actions, as well as with toggle and
radio actions.
2014-04-26 20:03:14 -04:00
Matthias Clasen
3d3dd296ff Add a display-hint to menu tracker sections and items
This section attribute will be used to indicate that iconic
rendering is preferred in the following commits.
2014-04-26 20:02:39 -04:00
Matthias Clasen
3dc111c9d1 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.
2014-04-26 20:02:39 -04:00
Matthias Clasen
b7fe489267 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.
2014-04-26 20:02:38 -04:00
9 changed files with 540 additions and 49 deletions

View File

@@ -1806,14 +1806,20 @@ Suspendisse feugiat quam quis dolor accumsan cursus. </property>
<property name="orientation">vertical</property>
<property name="adjustment">adjustment2</property>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="verticalspin2">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="orientation">vertical</property>
<property name="adjustment">adjustment1</property>
<property name="adjustment">adjustment2</property>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
<child>
<object class="GtkVolumeButton">
@@ -1822,15 +1828,92 @@ Suspendisse feugiat quam quis dolor accumsan cursus. </property>
<property name="valign">center</property>
<property name="use-symbolic">True</property>
<property name="value">.5</property>
<property name="halign">center</property>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
<child>
<object class="GtkVolumeButton">
<property name="visible">True</property>
<property name="orientation">horizontal</property>
<property name="valign">center</property>
<property name="use-symbolic">True</property>
<property name="value">.5</property>
<property name="halign">center</property>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
<child>
<object class="GtkAspectFrame" id="panedframe1">
<property name="visible">True</property>
<property name="obey-child">False</property>
<property name="ratio">1.0</property>
<child>
<object class="GtkPaned">
<property name="visible">True</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label">Left</property>
<style>
<class name="view"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label">Right</property>
<style>
<class name="view"/>
</style>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
<child>
<object class="GtkAspectFrame" id="panedframe2">
<property name="visible">True</property>
<property name="obey-child">False</property>
<property name="ratio">1.0</property>
<child>
<object class="GtkPaned">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label">Top</property>
<style>
<class name="view"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="label">Bottom</property>
<style>
<class name="view"/>
</style>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
</packing>
</child>
</object>
<packing>
@@ -2025,4 +2108,11 @@ Suspendisse feugiat quam quis dolor accumsan cursus. </property>
<widget name="listboxrow3"/>
</widgets>
</object>
<object class="GtkSizeGroup">
<property name="mode">both</property>
<widgets>
<widget name="panedframe1"/>
<widget name="panedframe2"/>
</widgets>
</object>
</interface>

View File

@@ -72,6 +72,7 @@ struct _GtkMenuTrackerSection
gpointer model; /* may be a GtkMenuTrackerItem or a GMenuModel */
GSList *items;
gchar *action_namespace;
gchar *display_hint;
guint separator_label : 1;
guint with_separators : 1;
@@ -86,7 +87,8 @@ static GtkMenuTrackerSection * gtk_menu_tracker_section_new (GtkMenuTracker
gboolean with_separators,
gboolean separator_label,
gint offset,
const gchar *action_namespace);
const gchar *action_namespace,
const gchar *display_hint);
static void gtk_menu_tracker_section_free (GtkMenuTrackerSection *section);
static GtkMenuTrackerSection *
@@ -199,7 +201,7 @@ gtk_menu_tracker_section_sync_separators (GtkMenuTrackerSection *section,
/* Add a separator */
GtkMenuTrackerItem *item;
item = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE);
item = _gtk_menu_tracker_item_new (tracker->observable, parent_model, parent_index, NULL, TRUE, NULL);
(* tracker->insert_func) (item, offset, tracker->user_data);
g_object_unref (item);
@@ -312,11 +314,15 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
{
GtkMenuTrackerSection *subsection;
gchar *action_namespace = NULL;
gchar *display_hint = NULL;
gboolean has_label;
has_label = g_menu_model_get_item_attribute (model, position + n_items,
G_MENU_ATTRIBUTE_LABEL, "s", NULL);
g_menu_model_get_item_attribute (model, position + n_items,
"display-hint", "s", &display_hint);
g_menu_model_get_item_attribute (model, position + n_items,
G_MENU_ATTRIBUTE_ACTION_NAMESPACE, "s", &action_namespace);
@@ -325,14 +331,15 @@ gtk_menu_tracker_add_items (GtkMenuTracker *tracker,
gchar *namespace;
namespace = g_strjoin (".", section->action_namespace, action_namespace, NULL);
subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, namespace);
subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, namespace, display_hint);
g_free (namespace);
}
else
subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, action_namespace);
subsection = gtk_menu_tracker_section_new (tracker, submenu, FALSE, has_label, offset, action_namespace, display_hint);
*change_point = g_slist_prepend (*change_point, subsection);
g_free (action_namespace);
g_free (display_hint);
g_object_unref (submenu);
}
else
@@ -340,7 +347,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, FALSE, section->display_hint);
/* In the case that the item may disappear we handle that by
* treating the item that we just created as being its own
@@ -459,6 +466,7 @@ gtk_menu_tracker_section_free (GtkMenuTrackerSection *section)
g_signal_handler_disconnect (section->model, section->handler);
g_slist_free_full (section->items, (GDestroyNotify) gtk_menu_tracker_section_free);
g_free (section->action_namespace);
g_free (section->display_hint);
g_object_unref (section->model);
g_slice_free (GtkMenuTrackerSection, section);
}
@@ -469,7 +477,8 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
gboolean with_separators,
gboolean separator_label,
gint offset,
const gchar *action_namespace)
const gchar *action_namespace,
const gchar *display_hint)
{
GtkMenuTrackerSection *section;
@@ -477,6 +486,7 @@ gtk_menu_tracker_section_new (GtkMenuTracker *tracker,
section->model = g_object_ref (model);
section->with_separators = with_separators;
section->action_namespace = g_strdup (action_namespace);
section->display_hint = g_strdup (display_hint);
section->separator_label = separator_label;
gtk_menu_tracker_add_items (tracker, section, &section->items, offset, model, 0, g_menu_model_get_n_items (model));
@@ -553,7 +563,7 @@ gtk_menu_tracker_new (GtkActionObservable *observable,
tracker->remove_func = remove_func;
tracker->user_data = user_data;
tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, FALSE, 0, action_namespace);
tracker->toplevel = gtk_menu_tracker_section_new (tracker, model, with_separators, FALSE, 0, action_namespace, NULL);
gtk_menu_tracker_section_sync_separators (tracker->toplevel, tracker, 0, FALSE, NULL, 0);
return tracker;

View File

@@ -87,6 +87,7 @@ struct _GtkMenuTrackerItem
GtkActionObservable *observable;
gchar *action_namespace;
gchar *action_and_target;
gchar *display_hint;
GMenuItem *item;
GtkMenuTrackerItemRole role : 4;
guint is_separator : 1;
@@ -109,12 +110,14 @@ enum {
PROP_HAS_SUBMENU,
PROP_LABEL,
PROP_ICON,
PROP_VERB_ICON,
PROP_SENSITIVE,
PROP_VISIBLE,
PROP_ROLE,
PROP_TOGGLED,
PROP_ACCEL,
PROP_SUBMENU_SHOWN,
PROP_STATE,
N_PROPS
};
@@ -136,6 +139,7 @@ gtk_menu_tracker_item_role_get_type (void)
{ GTK_MENU_TRACKER_ITEM_ROLE_NORMAL, "GTK_MENU_TRACKER_ITEM_ROLE_NORMAL", "normal" },
{ GTK_MENU_TRACKER_ITEM_ROLE_CHECK, "GTK_MENU_TRACKER_ITEM_ROLE_CHECK", "check" },
{ GTK_MENU_TRACKER_ITEM_ROLE_RADIO, "GTK_MENU_TRACKER_ITEM_ROLE_RADIO", "radio" },
{ GTK_MENU_TRACKER_ITEM_ROLE_SCALE, "GTK_MENU_TRACKER_ITEM_ROLE_SCALE", "scale" },
{ 0, NULL, NULL }
};
GType type;
@@ -170,6 +174,9 @@ gtk_menu_tracker_item_get_property (GObject *object,
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;
@@ -188,6 +195,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_STATE:
g_value_set_variant (value, gtk_menu_tracker_item_get_state (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -200,6 +210,7 @@ gtk_menu_tracker_item_finalize (GObject *object)
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (object);
g_free (self->action_namespace);
g_free (self->display_hint);
if (self->observable)
g_object_unref (self->observable);
@@ -228,6 +239,8 @@ gtk_menu_tracker_item_class_init (GtkMenuTrackerItemClass *class)
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] =
@@ -242,6 +255,8 @@ 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_STATE] =
g_param_spec_variant ("state", "", "", G_VARIANT_TYPE_ANY, NULL, G_PARAM_STATIC_STRINGS | G_PARAM_READABLE);
g_object_class_install_properties (class, N_PROPS, gtk_menu_tracker_item_pspecs);
@@ -286,6 +301,18 @@ gtk_menu_tracker_item_update_visibility (GtkMenuTrackerItem *self)
}
}
static GtkMenuTrackerItemRole
get_initial_role (GMenuItem *item)
{
const gchar *type;
if (g_menu_item_get_attribute (item, "type", "&s", &type) &&
g_strcmp0 (type, "scale") == 0)
return GTK_MENU_TRACKER_ITEM_ROLE_SCALE;
else
return GTK_MENU_TRACKER_ITEM_ROLE_NORMAL;
}
static void
gtk_menu_tracker_item_action_added (GtkActionObserver *observer,
GtkActionObservable *observable,
@@ -296,6 +323,9 @@ gtk_menu_tracker_item_action_added (GtkActionObserver *observer,
{
GtkMenuTrackerItem *self = GTK_MENU_TRACKER_ITEM (observer);
GVariant *action_target;
GtkMenuTrackerItemRole old_role;
old_role = self->role;
action_target = g_menu_item_get_attribute_value (self->item, G_MENU_ATTRIBUTE_TARGET, NULL);
@@ -323,6 +353,11 @@ gtk_menu_tracker_item_action_added (GtkActionObserver *observer,
self->toggled = g_variant_get_boolean (state);
self->role = GTK_MENU_TRACKER_ITEM_ROLE_CHECK;
}
else if (state != NULL && g_variant_is_of_type (state, G_VARIANT_TYPE_DOUBLE))
{
self->toggled = FALSE;
self->role = GTK_MENU_TRACKER_ITEM_ROLE_SCALE;
}
g_object_freeze_notify (G_OBJECT (self));
@@ -332,7 +367,7 @@ gtk_menu_tracker_item_action_added (GtkActionObserver *observer,
if (self->toggled)
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
if (self->role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
if (self->role != old_role)
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]);
g_object_thaw_notify (G_OBJECT (self));
@@ -398,6 +433,8 @@ gtk_menu_tracker_item_action_state_changed (GtkActionObserver *observer,
if (self->toggled != was_toggled)
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_STATE]);
}
static void
@@ -419,7 +456,7 @@ gtk_menu_tracker_item_action_removed (GtkActionObserver *observer,
self->can_activate = FALSE;
self->sensitive = FALSE;
self->toggled = FALSE;
self->role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL;
self->role = get_initial_role (self->item);
/* Backwards from adding: we want to remove ourselves from the menu
* -before- thrashing the properties.
@@ -434,7 +471,7 @@ gtk_menu_tracker_item_action_removed (GtkActionObserver *observer,
if (was_toggled)
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_TOGGLED]);
if (old_role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL)
if (old_role != self->role)
g_object_notify_by_pspec (G_OBJECT (self), gtk_menu_tracker_item_pspecs[PROP_ROLE]);
g_object_thaw_notify (G_OBJECT (self));
@@ -467,7 +504,8 @@ _gtk_menu_tracker_item_new (GtkActionObservable *observable,
GMenuModel *model,
gint item_index,
const gchar *action_namespace,
gboolean is_separator)
gboolean is_separator,
const gchar *display_hint)
{
GtkMenuTrackerItem *self;
const gchar *action_name;
@@ -481,6 +519,8 @@ _gtk_menu_tracker_item_new (GtkActionObservable *observable,
self->action_namespace = g_strdup (action_namespace);
self->observable = g_object_ref (observable);
self->is_separator = is_separator;
self->display_hint = g_strdup (display_hint);
self->role = get_initial_role (self->item);
if (!is_separator && g_menu_item_get_attribute (self->item, "hidden-when", "&s", &hidden_when))
{
@@ -612,6 +652,28 @@ 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)
{
@@ -663,6 +725,12 @@ gtk_menu_tracker_item_get_special (GtkMenuTrackerItem *self)
return special;
}
const gchar *
gtk_menu_tracker_item_get_display_hint (GtkMenuTrackerItem *self)
{
return self->display_hint;
}
GMenuModel *
_gtk_menu_tracker_item_get_submenu (GtkMenuTrackerItem *self)
{
@@ -901,3 +969,28 @@ _gtk_menu_tracker_item_may_disappear (GtkMenuTrackerItem *self)
{
return self->hidden_when != HIDDEN_NEVER;
}
GMenuItem *
gtk_menu_tracker_item_get_item (GtkMenuTrackerItem *self)
{
return self->item;
}
GVariant *
gtk_menu_tracker_item_get_state (GtkMenuTrackerItem *self)
{
const gchar *action_name;
action_name = strrchr (self->action_and_target, '|') + 1;
return g_action_group_get_action_state (G_ACTION_GROUP (self->observable), action_name);
}
void
gtk_menu_tracker_item_change_state (GtkMenuTrackerItem *self,
GVariant *value)
{
const gchar *action_name;
action_name = strrchr (self->action_and_target, '|') + 1;
g_action_group_change_action_state (G_ACTION_GROUP (self->observable), action_name, value);
}

View File

@@ -36,6 +36,7 @@ typedef enum {
GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
GTK_MENU_TRACKER_ITEM_ROLE_CHECK,
GTK_MENU_TRACKER_ITEM_ROLE_RADIO,
GTK_MENU_TRACKER_ITEM_ROLE_SCALE
} GtkMenuTrackerItemRole;
GType gtk_menu_tracker_item_get_type (void) G_GNUC_CONST;
@@ -46,9 +47,11 @@ GtkMenuTrackerItem * _gtk_menu_tracker_item_new (GtkActi
GMenuModel *model,
gint item_index,
const gchar *action_namespace,
gboolean is_separator);
gboolean is_separator,
const gchar *display_hint);
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);
@@ -60,6 +63,8 @@ const gchar * gtk_menu_tracker_item_get_label (GtkMenu
GIcon * gtk_menu_tracker_item_get_icon (GtkMenuTrackerItem *self);
GIcon * gtk_menu_tracker_item_get_verb_icon (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_sensitive (GtkMenuTrackerItem *self);
gboolean gtk_menu_tracker_item_get_visible (GtkMenuTrackerItem *self);
@@ -87,4 +92,11 @@ void gtk_menu_tracker_item_request_submenu_shown (GtkMenu
gboolean gtk_menu_tracker_item_get_submenu_shown (GtkMenuTrackerItem *self);
GMenuItem *gtk_menu_tracker_item_get_item (GtkMenuTrackerItem *self);
GVariant *gtk_menu_tracker_item_get_state (GtkMenuTrackerItem *self);
void gtk_menu_tracker_item_change_state (GtkMenuTrackerItem *self,
GVariant *value);
#endif

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,26 @@ 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)), "modelbutton");
gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NORMAL);
}
else
{
gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (button)), "modelbutton");
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 +229,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 +261,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 +581,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 +617,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 +662,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 +689,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

@@ -96,6 +96,12 @@ gtk_model_menu_item_set_action_role (GtkModelMenuItem *item,
AtkObject *accessible;
AtkRole a11y_role;
if (role == GTK_MENU_TRACKER_ITEM_ROLE_SCALE)
{
g_warning ("GtkMenu does not support scales");
role = GTK_MENU_TRACKER_ITEM_ROLE_NORMAL;
}
if (role == item->role)
return;

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
*/
@@ -59,6 +93,7 @@
#include "gtkwidgetprivate.h"
#include "gtkactionmuxer.h"
#include "gtkmenutracker.h"
#include "gtkmenutrackeritem.h"
#include "gtkstack.h"
#include "gtksizegroup.h"
#include "a11y/gtkpopoveraccessible.h"
@@ -1986,6 +2021,62 @@ open_submenu (GtkWidget *button,
gtk_widget_grab_focus (focus);
}
static GtkWidget *
scale_from_item (GMenuItem *item)
{
GtkWidget *scale;
gdouble min, max, step;
gint32 marks;
gint i;
if (!g_menu_item_get_attribute (item, "minimum", "d", &min))
min = 0.0;
if (!g_menu_item_get_attribute (item, "maximum", "d", &max))
max = 10.0;
if (!g_menu_item_get_attribute (item, "step", "d", &step))
step = 1.0;
if (!g_menu_item_get_attribute (item, "marks", "i", &marks))
marks = 0;
scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, min, max, step);
gtk_widget_set_margin_top (scale, 6);
gtk_widget_set_margin_bottom (scale, 6);
gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
gtk_scale_set_has_origin (GTK_SCALE (scale), TRUE);
if (marks > 1)
{
for (i = 0; i < marks; i++)
gtk_scale_add_mark (GTK_SCALE (scale), min + i * (max - min) / (gdouble) (marks - 1), GTK_POS_BOTTOM, NULL);
}
gtk_style_context_remove_class (gtk_widget_get_style_context (scale),
GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
return scale;
}
static void
gtk_popover_scale_changed (GtkRange *range,
GtkMenuTrackerItem *item)
{
gtk_menu_tracker_item_change_state (item, g_variant_new_double (gtk_range_get_value (range)));
}
static void
gtk_popover_update_scale (GtkMenuTrackerItem *item, GParamSpec *pspec, GtkRange *range)
{
GVariant *state;
g_signal_handlers_block_by_func (range, gtk_popover_scale_changed, item);
state = gtk_menu_tracker_item_get_state (item);
gtk_range_set_value (range, g_variant_get_double (state));
g_variant_unref (state);
g_signal_handlers_unblock_by_func (range, gtk_popover_scale_changed, item);
}
static void
gtk_popover_tracker_insert_func (GtkMenuTrackerItem *item,
gint position,
@@ -2004,10 +2095,10 @@ gtk_popover_tracker_insert_func (GtkMenuTrackerItem *item,
GtkWidget *separator;
const gchar *label;
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
label = gtk_menu_tracker_item_get_label (item);
separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
if (label != NULL)
{
GtkWidget *title;
@@ -2084,26 +2175,78 @@ gtk_popover_tracker_insert_func (GtkMenuTrackerItem *item,
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
else if (gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_SCALE)
{
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);
widget = scale_from_item (gtk_menu_tracker_item_get_item (item));
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_signal_connect (item, "notify::state", G_CALLBACK (gtk_popover_update_scale), widget);
g_signal_connect (widget, "value-changed", G_CALLBACK (gtk_popover_scale_changed), item);
}
else
{
const gchar *hint;
GtkWidget *icon_box = NULL;
GtkWidget *button;
hint = gtk_menu_tracker_item_get_display_hint (item);
if (g_strcmp0 (hint, "iconic") == 0)
{
GList *l;
GtkWidget *sibling = NULL;
l = gtk_container_get_children (GTK_CONTAINER (box));
sibling = g_list_nth_data (l, position);
g_list_free (l);
if (sibling)
icon_box = GTK_WIDGET (g_object_get_data (G_OBJECT (sibling), "iconic-section"));
if (GTK_IS_BOX (icon_box))
{
widget = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
gtk_widget_set_no_show_all (widget, TRUE);
g_object_set_data (G_OBJECT (widget), "iconic-section", icon_box);
}
else
{
icon_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_set_halign (icon_box, GTK_ALIGN_FILL);
gtk_style_context_add_class (gtk_widget_get_style_context (icon_box), GTK_STYLE_CLASS_LINKED);
gtk_widget_show (icon_box);
g_object_set_data (G_OBJECT (icon_box), "iconic-section", icon_box);
widget = icon_box;
}
}
button = gtk_model_button_new ();
g_object_bind_property (item, "label", button, "text", G_BINDING_SYNC_CREATE);
if (g_strcmp0 (hint, "iconic") == 0)
g_object_bind_property (item, "verb-icon", button, "icon", G_BINDING_SYNC_CREATE);
else
g_object_bind_property (item, "icon", button, "icon", G_BINDING_SYNC_CREATE);
g_object_bind_property (item, "sensitive", button, "sensitive", G_BINDING_SYNC_CREATE);
g_object_bind_property (item, "visible", button, "visible", G_BINDING_SYNC_CREATE);
g_object_bind_property (item, "role", button, "action-role", G_BINDING_SYNC_CREATE);
g_object_bind_property (item, "toggled", button, "toggled", G_BINDING_SYNC_CREATE);
g_object_bind_property (item, "accel", button, "accel", G_BINDING_SYNC_CREATE);
g_signal_connect (button, "clicked", G_CALLBACK (gtk_popover_item_activate), item);
if (icon_box)
{
g_object_set (button, "iconic", TRUE, "centered", TRUE, NULL);
g_object_set_data_full (G_OBJECT (button), "GtkMenuTrackerItem", g_object_ref (item), g_object_unref);
gtk_box_pack_end (GTK_BOX (icon_box), button, TRUE, TRUE, 0);
}
else
{
widget = button;
}
}
g_object_set_data_full (G_OBJECT (widget), "GtkMenuTrackerItem", g_object_ref (item), g_object_unref);
gtk_container_add (GTK_CONTAINER (box), widget);

View File

@@ -1,5 +1,31 @@
<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>
<item>
<attribute name="type">scale</attribute>
<attribute name="action">top.zoom</attribute>
<attribute name="minimum" type="d">0.0</attribute>
<attribute name="maximum" type="d">10.0</attribute>
<attribute name="step" type="d">1.0</attribute>
<attribute name="marks" type="i">3</attribute>
</item>
</section>
<section>
<item>
<attribute name="label">No action</attribute>
@@ -44,6 +70,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 +94,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

@@ -8,7 +8,24 @@ activate (GSimpleAction *action,
g_print ("%s activated\n", g_action_get_name (G_ACTION (action)));
}
static void
set_zoom (GSimpleAction *action,
GVariant *value,
gpointer user_data)
{
g_print ("setting zoom to %f\n", g_variant_get_double (value));
g_simple_action_set_state (action, value);
}
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 },
@@ -19,7 +36,12 @@ static GActionEntry entries[] = {
{ "action7", activate, NULL, NULL, NULL },
{ "action8", activate, NULL, NULL, NULL },
{ "action9", activate, NULL, NULL, NULL },
{ "action10", activate, NULL, NULL, NULL }
{ "action10", activate, NULL, NULL, NULL },
{ "set-view", NULL, "s", "'list'", NULL },
{ "cut", activate, NULL, NULL, NULL },
{ "copy", activate, NULL, NULL, NULL },
{ "paste", activate, NULL, NULL, NULL },
{ "zoom", NULL, NULL, "5.0", set_zoom }
};
int main (int argc, char *argv[])
@@ -38,7 +60,6 @@ int main (int argc, char *argv[])
GtkWidget *align;
gtk_init (&argc, &argv);
win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (win), 400, 600);
actions = g_simple_action_group_new ();
@@ -62,8 +83,8 @@ int main (int argc, char *argv[])
model = (GMenuModel *)gtk_builder_get_object (builder, "menu");
button = gtk_menu_button_new ();
gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), model);
gtk_menu_button_set_use_popover (GTK_MENU_BUTTON (button), TRUE);
gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), model);
popover = GTK_WIDGET (gtk_menu_button_get_popover (GTK_MENU_BUTTON (button)));
@@ -125,7 +146,6 @@ int main (int argc, char *argv[])
gtk_grid_attach (GTK_GRID (grid), label , 1, 5, 1, 1);
gtk_grid_attach (GTK_GRID (grid), combo, 2, 5, 1, 1);
gtk_widget_show_all (win);
gtk_main ();