[gtk+/popover-menu-buttons] Add support for scale widgets to model-based popovers



commit 5f0398a688c588256568dd3975f40745c8dfe6e7
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Apr 16 16:01:56 2014 -0700

    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

 gtk/gtkmenutrackeritem.c |   61 +++++++++++++++++++++++++++++++++++++++++--
 gtk/gtkmenutrackeritem.h |    8 +++++
 gtk/gtkmodelmenuitem.c   |    6 ++++
 gtk/gtkpopover.c         |   65 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/popover.ui         |    8 +++++
 tests/testpopover.c      |   20 +++++++++++---
 6 files changed, 161 insertions(+), 7 deletions(-)
---
diff --git a/gtk/gtkmenutrackeritem.c b/gtk/gtkmenutrackeritem.c
index ca3b944..7b1df09 100644
--- a/gtk/gtkmenutrackeritem.c
+++ b/gtk/gtkmenutrackeritem.c
@@ -117,6 +117,7 @@ enum {
   PROP_TOGGLED,
   PROP_ACCEL,
   PROP_SUBMENU_SHOWN,
+  PROP_STATE,
   N_PROPS
 };
 
@@ -138,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;
@@ -193,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;
@@ -250,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);
 
@@ -294,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,
@@ -304,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);
 
@@ -331,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));
 
@@ -340,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));
@@ -406,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
@@ -427,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.
@@ -442,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));
@@ -491,6 +520,7 @@ _gtk_menu_tracker_item_new (GtkActionObservable *observable,
   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))
     {
@@ -939,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);
+}
diff --git a/gtk/gtkmenutrackeritem.h b/gtk/gtkmenutrackeritem.h
index b97db84..da8b530 100644
--- a/gtk/gtkmenutrackeritem.h
+++ b/gtk/gtkmenutrackeritem.h
@@ -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;
@@ -91,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
diff --git a/gtk/gtkmodelmenuitem.c b/gtk/gtkmodelmenuitem.c
index bb5210c..d8aa306 100644
--- a/gtk/gtkmodelmenuitem.c
+++ b/gtk/gtkmodelmenuitem.c
@@ -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;
 
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index 63b6f3d..45f5469 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -2021,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,
@@ -2119,12 +2175,21 @@ 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 if (gtk_menu_tracker_item_get_role (item) == GTK_MENU_TRACKER_ITEM_ROLE_SCALE)
+    {
+      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_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;
diff --git a/tests/popover.ui b/tests/popover.ui
index 14d8236..590cf64 100644
--- a/tests/popover.ui
+++ b/tests/popover.ui
@@ -17,6 +17,14 @@
         <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>
diff --git a/tests/testpopover.c b/tests/testpopover.c
index 2f80e31..ff248a3 100644
--- a/tests/testpopover.c
+++ b/tests/testpopover.c
@@ -8,6 +8,15 @@ 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 },
@@ -27,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[])
@@ -46,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 ();
@@ -70,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)));
 
@@ -133,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 ();


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]