[gnome-builder] layout: preserve focus ordering of layout views in stack



commit 70997144bbc8c533cc18229e587f2da137f6a50c
Author: Christian Hergert <chergert redhat com>
Date:   Thu Nov 1 12:14:14 2018 -0700

    layout: preserve focus ordering of layout views in stack
    
    This helps fix a situation where removing a GtkStack child focuses the
    wrong "next view". That can be rather jarring when quickly moving between
    files.

 src/libide/layout/ide-layout-stack-wrapper.c | 124 +++++++++++++++++++++++++++
 src/libide/layout/ide-layout-stack-wrapper.h |  31 +++++++
 src/libide/layout/ide-layout-stack.c         |   2 +
 src/libide/layout/ide-layout-stack.ui        |   2 +-
 src/libide/layout/meson.build                |   2 +
 5 files changed, 160 insertions(+), 1 deletion(-)
---
diff --git a/src/libide/layout/ide-layout-stack-wrapper.c b/src/libide/layout/ide-layout-stack-wrapper.c
new file mode 100644
index 000000000..0436f20be
--- /dev/null
+++ b/src/libide/layout/ide-layout-stack-wrapper.c
@@ -0,0 +1,124 @@
+/* ide-layout-stack-wrapper.c
+ *
+ * Copyright 2018 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#define G_LOG_DOMAIN "ide-layout-stack-wrapper"
+
+#include "ide-layout-stack-wrapper.h"
+
+/*
+ * This is just a GtkStack wrapper that allows us to override
+ * GtkContainer::remove() so that we can transition to the previously
+ * focused child first.
+ */
+
+struct _IdeLayoutStackWrapper
+{
+  GtkStack parent_instance;
+  GQueue   history;
+};
+
+G_DEFINE_TYPE (IdeLayoutStackWrapper, ide_layout_stack_wrapper, GTK_TYPE_STACK)
+
+static void
+ide_layout_stack_wrapper_add (GtkContainer *container,
+                              GtkWidget    *widget)
+{
+  IdeLayoutStackWrapper *self = (IdeLayoutStackWrapper *)container;
+
+  g_assert (IDE_IS_LAYOUT_STACK_WRAPPER (container));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  if (gtk_widget_get_visible (widget))
+    g_queue_push_head (&self->history, widget);
+  else
+    g_queue_push_tail (&self->history, widget);
+
+  GTK_CONTAINER_CLASS (ide_layout_stack_wrapper_parent_class)->add (container, widget);
+}
+
+static void
+ide_layout_stack_wrapper_remove (GtkContainer *container,
+                                 GtkWidget    *widget)
+{
+  IdeLayoutStackWrapper *self = (IdeLayoutStackWrapper *)container;
+
+  g_assert (IDE_IS_LAYOUT_STACK_WRAPPER (container));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  /* Remove the widget from our history chain, and then see if we need to
+   * first change the visible child before removing. If we don't we risk,
+   * focusing the wrong "next" widget as part of the removal.
+   */
+
+  g_queue_remove (&self->history, widget);
+
+  if (self->history.length > 0)
+    {
+      GtkWidget *new_fg = g_queue_peek_head (&self->history);
+
+      if (new_fg != gtk_stack_get_visible_child (GTK_STACK (self)))
+        gtk_stack_set_visible_child (GTK_STACK (self), new_fg);
+    }
+
+  GTK_CONTAINER_CLASS (ide_layout_stack_wrapper_parent_class)->remove (container, widget);
+}
+
+static void
+ide_layout_stack_wrapper_notify_visible_child (IdeLayoutStackWrapper *self,
+                                               GParamSpec            *pspec)
+{
+  GtkWidget *visible_child;
+
+  g_assert (IDE_IS_LAYOUT_STACK_WRAPPER (self));
+  g_assert (pspec != NULL);
+
+  if ((visible_child = gtk_stack_get_visible_child (GTK_STACK (self))))
+    {
+      if (visible_child != g_queue_peek_head (&self->history))
+        {
+          GList *link_ = g_queue_find (&self->history, visible_child);
+
+          g_assert (link_ != NULL);
+
+          g_queue_unlink (&self->history, link_);
+          g_queue_push_head_link (&self->history, link_);
+        }
+    }
+}
+
+static void
+ide_layout_stack_wrapper_class_init (IdeLayoutStackWrapperClass *klass)
+{
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  container_class->add = ide_layout_stack_wrapper_add;
+  container_class->remove = ide_layout_stack_wrapper_remove;
+}
+
+static void
+ide_layout_stack_wrapper_init (IdeLayoutStackWrapper *self)
+{
+  g_signal_connect (self,
+                    "notify::visible-child",
+                    G_CALLBACK (ide_layout_stack_wrapper_notify_visible_child),
+                    NULL);
+}
diff --git a/src/libide/layout/ide-layout-stack-wrapper.h b/src/libide/layout/ide-layout-stack-wrapper.h
new file mode 100644
index 000000000..b8f4d193c
--- /dev/null
+++ b/src/libide/layout/ide-layout-stack-wrapper.h
@@ -0,0 +1,31 @@
+/* ide-layout-stack-wrapper.h
+ *
+ * Copyright 2018 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LAYOUT_STACK_WRAPPER (ide_layout_stack_wrapper_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeLayoutStackWrapper, ide_layout_stack_wrapper, IDE, LAYOUT_STACK_WRAPPER, GtkStack)
+
+G_END_DECLS
diff --git a/src/libide/layout/ide-layout-stack.c b/src/libide/layout/ide-layout-stack.c
index c6533571a..c2ab9dc21 100644
--- a/src/libide/layout/ide-layout-stack.c
+++ b/src/libide/layout/ide-layout-stack.c
@@ -29,6 +29,7 @@
 #include "layout/ide-layout-stack.h"
 #include "layout/ide-layout-stack-addin.h"
 #include "layout/ide-layout-stack-header.h"
+#include "layout/ide-layout-stack-wrapper.h"
 #include "layout/ide-layout-private.h"
 #include "layout/ide-shortcut-label.h"
 #include "threading/ide-task.h"
@@ -874,6 +875,7 @@ ide_layout_stack_class_init (IdeLayoutStackClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, IdeLayoutStack, event_box);
 
   g_type_ensure (IDE_TYPE_LAYOUT_STACK_HEADER);
+  g_type_ensure (IDE_TYPE_LAYOUT_STACK_WRAPPER);
   g_type_ensure (IDE_TYPE_SHORTCUT_LABEL);
 }
 
diff --git a/src/libide/layout/ide-layout-stack.ui b/src/libide/layout/ide-layout-stack.ui
index be87e443a..fa7cf2d70 100644
--- a/src/libide/layout/ide-layout-stack.ui
+++ b/src/libide/layout/ide-layout-stack.ui
@@ -127,7 +127,7 @@
               </object>
             </child>
             <child>
-              <object class="GtkStack" id="stack">
+              <object class="IdeLayoutStackWrapper" id="stack">
                 <property name="expand">true</property>
                 <property name="homogeneous">false</property>
                 <property name="interpolate-size">false</property>
diff --git a/src/libide/layout/meson.build b/src/libide/layout/meson.build
index 90c49365f..b24658220 100644
--- a/src/libide/layout/meson.build
+++ b/src/libide/layout/meson.build
@@ -28,6 +28,8 @@ layout_private_sources = [
   'ide-layout-private.h',
   'ide-layout-stack-actions.c',
   'ide-layout-stack-shortcuts.c',
+  'ide-layout-stack-wrapper.c',
+  'ide-layout-stack-wrapper.h',
   'ide-shortcut-label.c',
   'ide-shortcut-label.h',
 ]


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