433 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			433 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* GTK - The GIMP Toolkit
 | |
|  * Copyright © 2014 Red Hat, Inc.
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Lesser General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public
 | |
|  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| #include "gtkpopovermenu.h"
 | |
| #include "gtkstack.h"
 | |
| #include "gtkstylecontext.h"
 | |
| #include "gtkintl.h"
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * SECTION:gtkpopovermenu
 | |
|  * @Short_description: Popovers to use as menus
 | |
|  * @Title: GtkPopoverMenu
 | |
|  *
 | |
|  * GtkPopoverMenu is a subclass of #GtkPopover that treats its
 | |
|  * childen like menus and allows switching between them. It is
 | |
|  * meant to be used primarily together with #GtkModelButton, but
 | |
|  * any widget can be used, such as #GtkSpinButton or #GtkScale.
 | |
|  * In this respect, GtkPopoverMenu is more flexible than popovers
 | |
|  * that are created from a #GMenuModel with gtk_popover_new_from_model().
 | |
|  *
 | |
|  * To add a child as a submenu, set the #GtkPopoverMenu:submenu
 | |
|  * child property to the name of the submenu. To let the user open
 | |
|  * this submenu, add a #GtkModelButton whose #GtkModelButton:menu-name
 | |
|  * property is set to the name you've given to the submenu.
 | |
|  *
 | |
|  * By convention, the first child of a submenu should be a #GtkModelButton
 | |
|  * to switch back to the parent menu. Such a button should use the
 | |
|  * #GtkModelButton:inverted and #GtkModelButton:centered properties
 | |
|  * to achieve a title-like appearance and place the submenu indicator
 | |
|  * at the opposite side. To switch back to the main menu, use "main"
 | |
|  * as the menu name.
 | |
|  *
 | |
|  * # Example
 | |
|  *
 | |
|  * |[
 | |
|  * <object class="GtkPopoverMenu">
 | |
|  *   <child>
 | |
|  *     <object class="GtkBox">
 | |
|  *       <property name="visible">True</property>
 | |
|  *       <property name="margin">10</property>
 | |
|  *       <child>
 | |
|  *         <object class="GtkModelButton">
 | |
|  *           <property name="visible">True</property>
 | |
|  *           <property name="action-name">win.frob</property>
 | |
|  *           <property name="text" translatable="yes">Frob</property>
 | |
|  *         </object>
 | |
|  *       </child>
 | |
|  *       <child>
 | |
|  *         <object class="GtkModelButton">
 | |
|  *           <property name="visible">True</property>
 | |
|  *           <property name="menu-name">more</property>
 | |
|  *           <property name="text" translatable="yes">More</property>
 | |
|  *         </object>
 | |
|  *       </child>
 | |
|  *     </object>
 | |
|  *   </child>
 | |
|  *   <child>
 | |
|  *     <object class="GtkBox">
 | |
|  *       <property name="visible">True</property>
 | |
|  *       <property name="margin">10</property>
 | |
|  *       <child>
 | |
|  *         <object class="GtkModelButton">
 | |
|  *           <property name="visible">True</property>
 | |
|  *           <property name="action-name">win.foo</property>
 | |
|  *           <property name="text" translatable="yes">Foo</property>
 | |
|  *         </object>
 | |
|  *       </child>
 | |
|  *       <child>
 | |
|  *         <object class="GtkModelButton">
 | |
|  *           <property name="visible">True</property>
 | |
|  *           <property name="action-name">win.bar</property>
 | |
|  *           <property name="text" translatable="yes">Bar</property>
 | |
|  *         </object>
 | |
|  *       </child>
 | |
|  *     </object>
 | |
|  *     <packing>
 | |
|  *       <property name="submenu">more</property>
 | |
|  *     </packing>
 | |
|  *   </child>
 | |
|  * </object>
 | |
|  * ]|
 | |
|  *
 | |
|  * Just like normal popovers created using gtk_popover_new_from_model,
 | |
|  * #GtkPopoverMenu instances have a single css node called "popover"
 | |
|  * and get the .menu style class.
 | |
|  */
 | |
| 
 | |
| struct _GtkPopoverMenu
 | |
| {
 | |
|   GtkPopover parent_instance;
 | |
| };
 | |
| 
 | |
| enum {
 | |
|   PROP_VISIBLE_SUBMENU = 1
 | |
| };
 | |
| 
 | |
| enum {
 | |
|   CHILD_PROP_SUBMENU = 1,
 | |
|   CHILD_PROP_POSITION
 | |
| };
 | |
| 
 | |
| G_DEFINE_TYPE (GtkPopoverMenu, gtk_popover_menu, GTK_TYPE_POPOVER)
 | |
| 
 | |
| static void
 | |
| visible_submenu_changed (GObject        *object,
 | |
|                          GParamSpec     *pspec,
 | |
|                          GtkPopoverMenu *popover)
 | |
| {
 | |
|   g_object_notify (G_OBJECT (popover), "visible-submenu");
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_init (GtkPopoverMenu *popover)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
|   GtkStyleContext *style_context;
 | |
| 
 | |
|   stack = gtk_stack_new ();
 | |
|   gtk_stack_set_vhomogeneous (GTK_STACK (stack), FALSE);
 | |
|   gtk_stack_set_transition_type (GTK_STACK (stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
 | |
|   gtk_stack_set_interpolate_size (GTK_STACK (stack), TRUE);
 | |
|   gtk_widget_show (stack);
 | |
|   gtk_container_add (GTK_CONTAINER (popover), stack);
 | |
|   g_signal_connect (stack, "notify::visible-child-name",
 | |
|                     G_CALLBACK (visible_submenu_changed), popover);
 | |
| 
 | |
|   style_context = gtk_widget_get_style_context (GTK_WIDGET (popover));
 | |
|   gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_MENU);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_map (GtkWidget *widget)
 | |
| {
 | |
|   GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->map (widget);
 | |
|   gtk_popover_menu_open_submenu (GTK_POPOVER_MENU (widget), "main");
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_unmap (GtkWidget *widget)
 | |
| {
 | |
|   gtk_popover_menu_open_submenu (GTK_POPOVER_MENU (widget), "main");
 | |
|   GTK_WIDGET_CLASS (gtk_popover_menu_parent_class)->unmap (widget);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_add (GtkContainer *container,
 | |
|                       GtkWidget    *child)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
| 
 | |
|   stack = gtk_bin_get_child (GTK_BIN (container));
 | |
| 
 | |
|   if (stack == NULL)
 | |
|     {
 | |
|       gtk_widget_set_parent (child, GTK_WIDGET (container));
 | |
|       _gtk_bin_set_child (GTK_BIN (container), child);
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       gchar *name;
 | |
| 
 | |
|       if (gtk_stack_get_child_by_name (GTK_STACK (stack), "main"))
 | |
|         name = "submenu";
 | |
|       else
 | |
|         name = "main";
 | |
| 
 | |
|       gtk_stack_add_named (GTK_STACK (stack), child, name);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_remove (GtkContainer *container,
 | |
|                          GtkWidget    *child)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
| 
 | |
|   stack = gtk_bin_get_child (GTK_BIN (container));
 | |
| 
 | |
|   if (child == stack)
 | |
|     GTK_CONTAINER_CLASS (gtk_popover_menu_parent_class)->remove (container, child);
 | |
|   else
 | |
|     gtk_container_remove (GTK_CONTAINER (stack), child);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_forall (GtkContainer *container,
 | |
|                          gboolean      include_internals,
 | |
|                          GtkCallback   callback,
 | |
|                          gpointer      callback_data)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
| 
 | |
|   stack = gtk_bin_get_child (GTK_BIN (container));
 | |
| 
 | |
|   if (include_internals)
 | |
|     (* callback) (stack, callback_data);
 | |
| 
 | |
|   gtk_container_forall (GTK_CONTAINER (stack), callback, callback_data);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_get_child_property (GtkContainer *container,
 | |
|                                      GtkWidget    *child,
 | |
|                                      guint         property_id,
 | |
|                                      GValue       *value,
 | |
|                                      GParamSpec   *pspec)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
| 
 | |
|   stack = gtk_bin_get_child (GTK_BIN (container));
 | |
| 
 | |
|   if (child == stack)
 | |
|     return;
 | |
| 
 | |
|   switch (property_id)
 | |
|     {
 | |
|     case CHILD_PROP_SUBMENU:
 | |
|       {
 | |
|         gchar *name;
 | |
|         gtk_container_child_get (GTK_CONTAINER (stack), child, "name", &name, NULL);
 | |
|         g_value_set_string (value, name);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case CHILD_PROP_POSITION:
 | |
|       {
 | |
|         gint position;
 | |
|         gtk_container_child_get (GTK_CONTAINER (stack), child, "position", &position, NULL);
 | |
|         g_value_set_int (value, position);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); 
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_set_child_property (GtkContainer *container,
 | |
|                                      GtkWidget    *child,
 | |
|                                      guint         property_id,
 | |
|                                      const GValue *value,
 | |
|                                      GParamSpec   *pspec)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
| 
 | |
|   stack = gtk_bin_get_child (GTK_BIN (container));
 | |
| 
 | |
|   if (child == stack)
 | |
|     return;
 | |
| 
 | |
|   switch (property_id)
 | |
|     {
 | |
|     case CHILD_PROP_SUBMENU:
 | |
|       {
 | |
|         const gchar *name;
 | |
|         name = g_value_get_string (value);
 | |
|         gtk_container_child_set (GTK_CONTAINER (stack), child, "name", name, NULL);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case CHILD_PROP_POSITION:
 | |
|       {
 | |
|         gint position;
 | |
|         position = g_value_get_int (value);
 | |
|         gtk_container_child_set (GTK_CONTAINER (stack), child, "position", position, NULL);
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); 
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_get_property (GObject    *object,
 | |
|                                guint       property_id,
 | |
|                                GValue     *value,
 | |
|                                GParamSpec *pspec)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
| 
 | |
|   stack = gtk_bin_get_child (GTK_BIN (object));
 | |
| 
 | |
|   switch (property_id)
 | |
|     {
 | |
|     case PROP_VISIBLE_SUBMENU:
 | |
|       g_value_set_string (value, gtk_stack_get_visible_child_name (GTK_STACK (stack)));
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_set_property (GObject      *object,
 | |
|                                guint         property_id,
 | |
|                                const GValue *value,
 | |
|                                GParamSpec   *pspec)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
| 
 | |
|   stack = gtk_bin_get_child (GTK_BIN (object));
 | |
| 
 | |
|   switch (property_id)
 | |
|     {
 | |
|     case PROP_VISIBLE_SUBMENU:
 | |
|       gtk_stack_set_visible_child_name (GTK_STACK (stack), g_value_get_string (value));
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_popover_menu_class_init (GtkPopoverMenuClass *klass)
 | |
| {
 | |
|   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
 | |
|   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 | |
|   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 | |
| 
 | |
|   object_class->set_property = gtk_popover_menu_set_property;
 | |
|   object_class->get_property = gtk_popover_menu_get_property;
 | |
| 
 | |
|   widget_class->map = gtk_popover_menu_map;
 | |
|   widget_class->unmap = gtk_popover_menu_unmap;
 | |
| 
 | |
|   container_class->add = gtk_popover_menu_add;
 | |
|   container_class->remove = gtk_popover_menu_remove;
 | |
|   container_class->forall = gtk_popover_menu_forall;
 | |
|   container_class->set_child_property = gtk_popover_menu_set_child_property;
 | |
|   container_class->get_child_property = gtk_popover_menu_get_child_property;
 | |
| 
 | |
|   g_object_class_install_property (object_class,
 | |
|                                    PROP_VISIBLE_SUBMENU,
 | |
|                                    g_param_spec_string ("visible-submenu",
 | |
|                                                         P_("Visible submenu"),
 | |
|                                                         P_("The name of the visible submenu"),
 | |
|                                                         NULL,
 | |
|                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 | |
| 
 | |
|   /**
 | |
|    * GtkPopoverMenu:submenu:
 | |
|    *
 | |
|    * The submenu child property specifies the name of the submenu
 | |
|    * If it is %NULL or "main", the child is used as the main menu,
 | |
|    * which is shown initially when the popover is mapped.
 | |
|    *
 | |
|    * Since: 3.16
 | |
|    */
 | |
|   gtk_container_class_install_child_property (container_class,
 | |
|                                               CHILD_PROP_SUBMENU,
 | |
|                                               g_param_spec_string ("submenu",
 | |
|                                                                    P_("Submenu"),
 | |
|                                                                    P_("The name of the submenu"),
 | |
|                                                                    NULL,
 | |
|                                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 | |
| 
 | |
|   gtk_container_class_install_child_property (container_class,
 | |
|                                               CHILD_PROP_POSITION,
 | |
|                                               g_param_spec_int ("position",
 | |
|                                                                 P_("Position"),
 | |
|                                                                 P_("The index of the child in the parent"),
 | |
|                                                                 -1, G_MAXINT, 0,
 | |
|                                                                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_popover_menu_new:
 | |
|  *
 | |
|  * Creates a new popover menu.
 | |
|  *
 | |
|  * Returns: a new #GtkPopoverMenu
 | |
|  *
 | |
|  * Since: 3.16
 | |
|  */
 | |
| GtkWidget *
 | |
| gtk_popover_menu_new (void)
 | |
| {
 | |
|   return g_object_new (GTK_TYPE_POPOVER_MENU, NULL);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_popover_menu_open_submenu:
 | |
|  * @popover: a #GtkPopoverMenu
 | |
|  * @name: the name of the menu to switch to
 | |
|  *
 | |
|  * Opens a submenu of the @popover. The @name
 | |
|  * must be one of the names given to the submenus
 | |
|  * of @popover with #GtkPopoverMenu:submenu, or
 | |
|  * "main" to switch back to the main menu.
 | |
|  *
 | |
|  * #GtkModelButton will open submenus automatically
 | |
|  * when the #GtkModelButton:menu-name property is set,
 | |
|  * so this function is only needed when you are using
 | |
|  * other kinds of widgets to initiate menu changes.
 | |
|  *
 | |
|  * Since: 3.16
 | |
|  */
 | |
| void
 | |
| gtk_popover_menu_open_submenu (GtkPopoverMenu *popover,
 | |
|                                const gchar    *name)
 | |
| {
 | |
|   GtkWidget *stack;
 | |
| 
 | |
|   g_return_if_fail (GTK_IS_POPOVER_MENU (popover));
 | |
| 
 | |
|   stack = gtk_bin_get_child (GTK_BIN (popover));
 | |
|   gtk_stack_set_visible_child_name (GTK_STACK (stack), name);
 | |
| }
 | 
