 26b03332a4
			
		
	
	26b03332a4
	
	
	
		
			
			Synchronizing the visible child with the switcher's toggle buttons can lead to GtkButton::clicked being emitted twice. Once for the button that was active before, and once for the button that we just activated. This leads to notify::visible-child being called twice and one of them is with the wrong child. Let's deal with this in the same way we handle the visible child changing underneath us. https://bugzilla.gnome.org/show_bug.cgi?id=749021
		
			
				
	
	
		
			545 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			545 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2013 Red Hat, Inc.
 | |
|  *
 | |
|  * This program 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 program 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 program; if not, write to the Free Software Foundation,
 | |
|  * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| #include "gtkstackswitcher.h"
 | |
| #include "gtkradiobutton.h"
 | |
| #include "gtklabel.h"
 | |
| #include "gtkorientable.h"
 | |
| #include "gtkprivate.h"
 | |
| #include "gtkintl.h"
 | |
| 
 | |
| /**
 | |
|  * SECTION:gtkstackswitcher
 | |
|  * @Short_description: A controller for GtkStack
 | |
|  * @Title: GtkStackSwitcher
 | |
|  * @See_also: #GtkStack
 | |
|  *
 | |
|  * The GtkStackSwitcher widget acts as a controller for a
 | |
|  * #GtkStack; it shows a row of buttons to switch between
 | |
|  * the various pages of the associated stack widget.
 | |
|  *
 | |
|  * All the content for the buttons comes from the child properties
 | |
|  * of the #GtkStack.
 | |
|  *
 | |
|  * It is possible to associate multiple #GtkStackSwitcher widgets
 | |
|  * with the same stack widget.
 | |
|  *
 | |
|  * The GtkStackSwitcher widget was added in 3.10.
 | |
|  */
 | |
| 
 | |
| typedef struct _GtkStackSwitcherPrivate GtkStackSwitcherPrivate;
 | |
| struct _GtkStackSwitcherPrivate
 | |
| {
 | |
|   GtkStack *stack;
 | |
|   GHashTable *buttons;
 | |
|   gboolean in_child_changed;
 | |
| };
 | |
| 
 | |
| enum {
 | |
|   PROP_0,
 | |
|   PROP_STACK
 | |
| };
 | |
| 
 | |
| G_DEFINE_TYPE_WITH_PRIVATE (GtkStackSwitcher, gtk_stack_switcher, GTK_TYPE_BOX)
 | |
| 
 | |
| static void
 | |
| gtk_stack_switcher_init (GtkStackSwitcher *switcher)
 | |
| {
 | |
|   GtkStyleContext *context;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (switcher);
 | |
| 
 | |
|   priv->stack = NULL;
 | |
|   priv->buttons = g_hash_table_new (g_direct_hash, g_direct_equal);
 | |
| 
 | |
|   context = gtk_widget_get_style_context (GTK_WIDGET (switcher));
 | |
|   gtk_style_context_add_class (context, "stack-switcher");
 | |
|   gtk_style_context_add_class (context, GTK_STYLE_CLASS_LINKED);
 | |
| 
 | |
|   gtk_orientable_set_orientation (GTK_ORIENTABLE (switcher), GTK_ORIENTATION_HORIZONTAL);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_button_clicked (GtkWidget        *widget,
 | |
|                    GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkWidget *child;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
| 
 | |
|   if (!priv->in_child_changed)
 | |
|     {
 | |
|       child = g_object_get_data (G_OBJECT (widget), "stack-child");
 | |
|       gtk_stack_set_visible_child (priv->stack, child);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| rebuild_child (GtkWidget   *self,
 | |
|                const gchar *icon_name,
 | |
|                const gchar *title)
 | |
| {
 | |
|   GtkStyleContext *context;
 | |
|   GtkWidget *button_child;
 | |
| 
 | |
|   button_child = gtk_bin_get_child (GTK_BIN (self));
 | |
|   if (button_child != NULL)
 | |
|     gtk_widget_destroy (button_child);
 | |
| 
 | |
|   button_child = NULL;
 | |
|   context = gtk_widget_get_style_context (GTK_WIDGET (self));
 | |
| 
 | |
|   if (icon_name != NULL)
 | |
|     {
 | |
|       button_child = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
 | |
|       if (title != NULL)
 | |
|         gtk_widget_set_tooltip_text (GTK_WIDGET (self), title);
 | |
| 
 | |
|       gtk_style_context_remove_class (context, "text-button");
 | |
|       gtk_style_context_add_class (context, "image-button");
 | |
|     }
 | |
|   else if (title != NULL)
 | |
|     {
 | |
|       button_child = gtk_label_new (title);
 | |
| 
 | |
|       gtk_widget_set_tooltip_text (GTK_WIDGET (self), NULL);
 | |
| 
 | |
|       gtk_style_context_remove_class (context, "image-button");
 | |
|       gtk_style_context_add_class (context, "text-button");
 | |
|     }
 | |
| 
 | |
|   if (button_child)
 | |
|     {
 | |
|       gtk_widget_set_halign (GTK_WIDGET (button_child), GTK_ALIGN_CENTER);
 | |
|       gtk_widget_show_all (button_child);
 | |
|       gtk_container_add (GTK_CONTAINER (self), button_child);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| update_needs_attention (GtkWidget *widget, GtkWidget *button, gpointer data)
 | |
| {
 | |
|   GtkContainer *container;
 | |
|   gboolean needs_attention;
 | |
|   GtkStyleContext *context;
 | |
| 
 | |
|   container = GTK_CONTAINER (data);
 | |
|   gtk_container_child_get (container, widget,
 | |
|                            "needs-attention", &needs_attention,
 | |
|                            NULL);
 | |
| 
 | |
|   context = gtk_widget_get_style_context (button);
 | |
|   if (needs_attention)
 | |
|     gtk_style_context_add_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION);
 | |
|   else
 | |
|     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION);
 | |
| }
 | |
| 
 | |
| static void
 | |
| update_button (GtkStackSwitcher *self,
 | |
|                GtkWidget        *widget,
 | |
|                GtkWidget        *button)
 | |
| {
 | |
|   gchar *title;
 | |
|   gchar *icon_name;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
| 
 | |
|   gtk_container_child_get (GTK_CONTAINER (priv->stack), widget,
 | |
|                            "title", &title,
 | |
|                            "icon-name", &icon_name,
 | |
|                            NULL);
 | |
| 
 | |
|   rebuild_child (button, icon_name, title);
 | |
| 
 | |
|   gtk_widget_set_visible (button, gtk_widget_get_visible (widget) && (title != NULL || icon_name != NULL));
 | |
| 
 | |
|   if (icon_name != NULL)
 | |
|     gtk_widget_set_size_request (button, -1, -1);
 | |
|   else
 | |
|     gtk_widget_set_size_request (button, 100, -1);
 | |
| 
 | |
|   g_free (title);
 | |
|   g_free (icon_name);
 | |
| 
 | |
|   update_needs_attention (widget, button, priv->stack);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_title_icon_visible_updated (GtkWidget        *widget,
 | |
|                                GParamSpec       *pspec,
 | |
|                                GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkWidget *button;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
| 
 | |
|   button = g_hash_table_lookup (priv->buttons, widget);
 | |
|   update_button (self, widget, button);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_position_updated (GtkWidget        *widget,
 | |
|                      GParamSpec       *pspec,
 | |
|                      GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkWidget *button;
 | |
|   gint position;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
| 
 | |
|   button = g_hash_table_lookup (priv->buttons, widget);
 | |
| 
 | |
|   gtk_container_child_get (GTK_CONTAINER (priv->stack), widget,
 | |
|                            "position", &position,
 | |
|                            NULL);
 | |
| 
 | |
|   gtk_box_reorder_child (GTK_BOX (self), button, position);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_needs_attention_updated (GtkWidget        *widget,
 | |
|                             GParamSpec       *pspec,
 | |
|                             GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkWidget *button;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
| 
 | |
|   button = g_hash_table_lookup (priv->buttons, widget);
 | |
|   update_button (self, widget, button);
 | |
| }
 | |
| 
 | |
| static void
 | |
| add_child (GtkWidget        *widget,
 | |
|            GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkWidget *button;
 | |
|   GList *group;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
| 
 | |
|   button = gtk_radio_button_new (NULL);
 | |
|   gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE);
 | |
|   gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE);
 | |
| 
 | |
|   update_button (self, widget, button);
 | |
| 
 | |
|   group = gtk_container_get_children (GTK_CONTAINER (self));
 | |
|   if (group != NULL)
 | |
|     {
 | |
|       gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (group->data));
 | |
|       g_list_free (group);
 | |
|     }
 | |
| 
 | |
|   gtk_container_add (GTK_CONTAINER (self), button);
 | |
| 
 | |
|   g_object_set_data (G_OBJECT (button), "stack-child", widget);
 | |
|   g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), self);
 | |
|   g_signal_connect (widget, "notify::visible", G_CALLBACK (on_title_icon_visible_updated), self);
 | |
|   g_signal_connect (widget, "child-notify::title", G_CALLBACK (on_title_icon_visible_updated), self);
 | |
|   g_signal_connect (widget, "child-notify::icon-name", G_CALLBACK (on_title_icon_visible_updated), self);
 | |
|   g_signal_connect (widget, "child-notify::position", G_CALLBACK (on_position_updated), self);
 | |
|   g_signal_connect (widget, "child-notify::needs-attention", G_CALLBACK (on_needs_attention_updated), self);
 | |
| 
 | |
|   g_hash_table_insert (priv->buttons, widget, button);
 | |
| }
 | |
| 
 | |
| static void
 | |
| remove_child (GtkWidget        *widget,
 | |
|               GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkWidget *button;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
| 
 | |
|   g_signal_handlers_disconnect_by_func (widget, on_title_icon_visible_updated, self);
 | |
|   g_signal_handlers_disconnect_by_func (widget, on_position_updated, self);
 | |
|   g_signal_handlers_disconnect_by_func (widget, on_needs_attention_updated, self);
 | |
| 
 | |
|   button = g_hash_table_lookup (priv->buttons, widget);
 | |
|   gtk_container_remove (GTK_CONTAINER (self), button);
 | |
|   g_hash_table_remove (priv->buttons, widget);
 | |
| }
 | |
| 
 | |
| static void
 | |
| populate_switcher (GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
|   GtkWidget *widget, *button;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
|   gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback)add_child, self);
 | |
| 
 | |
|   widget = gtk_stack_get_visible_child (priv->stack);
 | |
|   if (widget)
 | |
|     {
 | |
|       button = g_hash_table_lookup (priv->buttons, widget);
 | |
|       priv->in_child_changed = TRUE;
 | |
|       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
 | |
|       priv->in_child_changed = FALSE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| clear_switcher (GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
|   gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback)remove_child, self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_child_changed (GtkWidget        *widget,
 | |
|                   GParamSpec       *pspec,
 | |
|                   GtkStackSwitcher *self)
 | |
| {
 | |
|   GtkWidget *child;
 | |
|   GtkWidget *button;
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (self);
 | |
| 
 | |
|   child = gtk_stack_get_visible_child (GTK_STACK (widget));
 | |
|   button = g_hash_table_lookup (priv->buttons, child);
 | |
|   if (button != NULL)
 | |
|     {
 | |
|       priv->in_child_changed = TRUE;
 | |
|       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
 | |
|       priv->in_child_changed = FALSE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_stack_child_added (GtkContainer     *container,
 | |
|                       GtkWidget        *widget,
 | |
|                       GtkStackSwitcher *self)
 | |
| {
 | |
|   add_child (widget, self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| on_stack_child_removed (GtkContainer     *container,
 | |
|                         GtkWidget        *widget,
 | |
|                         GtkStackSwitcher *self)
 | |
| {
 | |
|   remove_child (widget, self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| disconnect_stack_signals (GtkStackSwitcher *switcher)
 | |
| {
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (switcher);
 | |
|   g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_added, switcher);
 | |
|   g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_removed, switcher);
 | |
|   g_signal_handlers_disconnect_by_func (priv->stack, on_child_changed, switcher);
 | |
|   g_signal_handlers_disconnect_by_func (priv->stack, disconnect_stack_signals, switcher);
 | |
| }
 | |
| 
 | |
| static void
 | |
| connect_stack_signals (GtkStackSwitcher *switcher)
 | |
| {
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (switcher);
 | |
|   g_signal_connect_after (priv->stack, "add",
 | |
|                           G_CALLBACK (on_stack_child_added), switcher);
 | |
|   g_signal_connect_after (priv->stack, "remove",
 | |
|                           G_CALLBACK (on_stack_child_removed), switcher);
 | |
|   g_signal_connect (priv->stack, "notify::visible-child",
 | |
|                     G_CALLBACK (on_child_changed), switcher);
 | |
|   g_signal_connect_swapped (priv->stack, "destroy",
 | |
|                             G_CALLBACK (disconnect_stack_signals), switcher);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_stack_switcher_set_stack:
 | |
|  * @switcher: a #GtkStackSwitcher
 | |
|  * @stack: (allow-none): a #GtkStack
 | |
|  *
 | |
|  * Sets the stack to control.
 | |
|  *
 | |
|  * Since: 3.10
 | |
|  */
 | |
| void
 | |
| gtk_stack_switcher_set_stack (GtkStackSwitcher *switcher,
 | |
|                               GtkStack         *stack)
 | |
| {
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   g_return_if_fail (GTK_IS_STACK_SWITCHER (switcher));
 | |
|   g_return_if_fail (GTK_IS_STACK (stack) || stack == NULL);
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (switcher);
 | |
| 
 | |
|   if (priv->stack == stack)
 | |
|     return;
 | |
| 
 | |
|   if (priv->stack)
 | |
|     {
 | |
|       disconnect_stack_signals (switcher);
 | |
|       clear_switcher (switcher);
 | |
|       g_clear_object (&priv->stack);
 | |
|     }
 | |
|   if (stack)
 | |
|     {
 | |
|       priv->stack = g_object_ref (stack);
 | |
|       populate_switcher (switcher);
 | |
|       connect_stack_signals (switcher);
 | |
|     }
 | |
| 
 | |
|   gtk_widget_queue_resize (GTK_WIDGET (switcher));
 | |
| 
 | |
|   g_object_notify (G_OBJECT (switcher), "stack");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_stack_switcher_get_stack:
 | |
|  * @switcher: a #GtkStackSwitcher
 | |
|  *
 | |
|  * Retrieves the stack.
 | |
|  * See gtk_stack_switcher_set_stack().
 | |
|  *
 | |
|  * Returns: (transfer none): the stack, or %NULL if
 | |
|  *    none has been set explicitly.
 | |
|  *
 | |
|  * Since: 3.10
 | |
|  */
 | |
| GtkStack *
 | |
| gtk_stack_switcher_get_stack (GtkStackSwitcher *switcher)
 | |
| {
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
|   g_return_val_if_fail (GTK_IS_STACK_SWITCHER (switcher), NULL);
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (switcher);
 | |
|   return priv->stack;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_stack_switcher_get_property (GObject      *object,
 | |
|                                  guint         prop_id,
 | |
|                                  GValue       *value,
 | |
|                                  GParamSpec   *pspec)
 | |
| {
 | |
|   GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (switcher);
 | |
|   switch (prop_id)
 | |
|     {
 | |
|     case PROP_STACK:
 | |
|       g_value_set_object (value, priv->stack);
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_stack_switcher_set_property (GObject      *object,
 | |
|                                  guint         prop_id,
 | |
|                                  const GValue *value,
 | |
|                                  GParamSpec   *pspec)
 | |
| {
 | |
|   GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
 | |
| 
 | |
|   switch (prop_id)
 | |
|     {
 | |
|     case PROP_STACK:
 | |
|       gtk_stack_switcher_set_stack (switcher, g_value_get_object (value));
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
 | |
|       break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_stack_switcher_dispose (GObject *object)
 | |
| {
 | |
|   GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
 | |
| 
 | |
|   gtk_stack_switcher_set_stack (switcher, NULL);
 | |
| 
 | |
|   G_OBJECT_CLASS (gtk_stack_switcher_parent_class)->dispose (object);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_stack_switcher_finalize (GObject *object)
 | |
| {
 | |
|   GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
 | |
|   GtkStackSwitcherPrivate *priv;
 | |
| 
 | |
|   priv = gtk_stack_switcher_get_instance_private (switcher);
 | |
| 
 | |
|   g_hash_table_destroy (priv->buttons);
 | |
| 
 | |
|   G_OBJECT_CLASS (gtk_stack_switcher_parent_class)->finalize (object);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_stack_switcher_class_init (GtkStackSwitcherClass *class)
 | |
| {
 | |
|   GObjectClass *object_class = G_OBJECT_CLASS (class);
 | |
| 
 | |
|   object_class->get_property = gtk_stack_switcher_get_property;
 | |
|   object_class->set_property = gtk_stack_switcher_set_property;
 | |
|   object_class->dispose = gtk_stack_switcher_dispose;
 | |
|   object_class->finalize = gtk_stack_switcher_finalize;
 | |
| 
 | |
|   g_object_class_install_property (object_class,
 | |
|                                    PROP_STACK,
 | |
|                                    g_param_spec_object ("stack",
 | |
|                                                         P_("Stack"),
 | |
|                                                         P_("Stack"),
 | |
|                                                         GTK_TYPE_STACK,
 | |
|                                                         GTK_PARAM_READWRITE |
 | |
|                                                         G_PARAM_CONSTRUCT));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * gtk_stack_switcher_new:
 | |
|  *
 | |
|  * Create a new #GtkStackSwitcher.
 | |
|  *
 | |
|  * Returns: a new #GtkStackSwitcher.
 | |
|  *
 | |
|  * Since: 3.10
 | |
|  */
 | |
| GtkWidget *
 | |
| gtk_stack_switcher_new (void)
 | |
| {
 | |
|   return g_object_new (GTK_TYPE_STACK_SWITCHER, NULL);
 | |
| }
 |