[gnome-builder/editor-layout] wip on new tab design



commit 5bd5794a867ed8d2410ed98fe5c11c0ac176eb7b
Author: Christian Hergert <christian hergert me>
Date:   Mon Nov 24 20:35:15 2014 -0800

    wip on new tab design

 src/editor/gb-editor-workspace.c        |    2 +
 src/gnome-builder.mk                    |    4 +
 src/resources/css/builder.Adwaita.css   |   14 +-
 src/resources/ui/gb-editor-workspace.ui |    7 +
 src/resources/ui/gb-tab.ui              |   32 +-
 src/tabs/gb-tab-grid.c                  |  441 +++++++++++++++++++
 src/tabs/gb-tab-grid.h                  |   62 +++
 src/tabs/gb-tab-stack.c                 |  713 +++++++++++++++++++++++++++++++
 src/tabs/gb-tab-stack.h                 |   69 +++
 src/tabs/gb-tab.c                       |    6 +
 src/tabs/gb-tab.h                       |    1 +
 11 files changed, 1330 insertions(+), 21 deletions(-)
---
diff --git a/src/editor/gb-editor-workspace.c b/src/editor/gb-editor-workspace.c
index eeaeed0..78d6adc 100644
--- a/src/editor/gb-editor-workspace.c
+++ b/src/editor/gb-editor-workspace.c
@@ -22,6 +22,7 @@
 #include "gb-editor-workspace.h"
 #include "gb-editor-workspace-private.h"
 #include "gb-multi-notebook.h"
+#include "gb-tab-grid.h"
 #include "gb-tree.h"
 
 enum {
@@ -111,6 +112,7 @@ gb_editor_workspace_class_init (GbEditorWorkspaceClass *klass)
   gtk_widget_class_bind_template_child_private (widget_class, GbEditorWorkspace, paned);
 
   g_type_ensure (GB_TYPE_MULTI_NOTEBOOK);
+  g_type_ensure (GB_TYPE_TAB_GRID);
   g_type_ensure (GB_TYPE_TREE);
 }
 
diff --git a/src/gnome-builder.mk b/src/gnome-builder.mk
index 18e42f7..e68d797 100644
--- a/src/gnome-builder.mk
+++ b/src/gnome-builder.mk
@@ -145,6 +145,10 @@ libgnome_builder_la_SOURCES = \
        src/tabs/gb-tab-label-private.h \
        src/tabs/gb-tab.c \
        src/tabs/gb-tab.h \
+src/tabs/gb-tab-grid.c \
+src/tabs/gb-tab-grid.h \
+src/tabs/gb-tab-stack.c \
+src/tabs/gb-tab-stack.h \
        src/theatrics/gb-box-theatric.c \
        src/theatrics/gb-box-theatric.h \
        src/tree/gb-tree.c \
diff --git a/src/resources/css/builder.Adwaita.css b/src/resources/css/builder.Adwaita.css
index 8675466..2fc5ad8 100644
--- a/src/resources/css/builder.Adwaita.css
+++ b/src/resources/css/builder.Adwaita.css
@@ -172,16 +172,18 @@ GbWorkbench GtkHeaderBar {
 /*
  * Tab header styling.
  */
-GbTab .tab-close-button {
-    border-right: none;
-    border-top-right-radius: 0px;
-    border-bottom-right-radius: 0px;
-}
-GbTab .tab-drag-button {
+GtkComboBox.tab-header-first .button {
     border-left: none;
     border-top-left-radius: 0px;
     border-bottom-left-radius: 0px;
 }
+.tab-header-last {
+    border-right: none;
+    border-top-right-radius: 0px;
+    border-bottom-right-radius: 0px;
+}
+
+
 
 #project-title {
     border-left: none;
diff --git a/src/resources/ui/gb-editor-workspace.ui b/src/resources/ui/gb-editor-workspace.ui
index 083c1ce..9b7eb13 100644
--- a/src/resources/ui/gb-editor-workspace.ui
+++ b/src/resources/ui/gb-editor-workspace.ui
@@ -37,8 +37,15 @@
           </object>
         </child>
         <child>
+          <object class="GbTabGrid" id="tab_grid">
+            <property name="visible">True</property>
+            <property name="hexpand">True</property>
+          </object>
+        </child>
+        <child>
           <object class="GbMultiNotebook" id="multi_notebook">
             <property name="group-name">GB_EDITOR_WORKSPACE</property>
+            <property name="hexpand">True</property>
             <property name="visible">True</property>
             <property name="show-tabs">False</property>
           </object>
diff --git a/src/resources/ui/gb-tab.ui b/src/resources/ui/gb-tab.ui
index c879154..2c43c83 100644
--- a/src/resources/ui/gb-tab.ui
+++ b/src/resources/ui/gb-tab.ui
@@ -11,12 +11,26 @@
           <class name="linked"/>
         </style>
         <child>
+          <object class="GtkComboBox" id="documents_combo">
+            <property name="visible">True</property>
+            <property name="hexpand">True</property>
+            <style>
+              <class name="tab-header-first"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkComboBox" id="symbols_combo">
+            <property name="visible">True</property>
+            <property name="hexpand">True</property>
+          </object>
+        </child>
+        <child>
           <object class="GtkButton" id="drag_button">
             <property name="visible">True</property>
             <property name="hexpand">False</property>
             <style>
               <class name="image-button"/>
-              <class name="tab-drag-button"/>
             </style>
             <child>
               <object class="GtkImage">
@@ -28,23 +42,12 @@
           </object>
         </child>
         <child>
-          <object class="GtkComboBox" id="documents_combo">
-            <property name="visible">True</property>
-            <property name="hexpand">True</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkComboBox" id="symbols_combo">
-            <property name="visible">True</property>
-            <property name="hexpand">True</property>
-          </object>
-        </child>
-        <child>
           <object class="GtkButton" id="split_button">
             <property name="visible">True</property>
             <property name="hexpand">False</property>
             <style>
               <class name="image-button"/>
+              <class name="tab-header-last"/>
             </style>
             <child>
               <object class="GtkImage">
@@ -57,10 +60,9 @@
         </child>
         <child>
           <object class="GtkButton" id="close_button">
-            <property name="visible">True</property>
+            <property name="visible">False</property>
             <property name="hexpand">False</property>
             <style>
-              <class name="tab-close-button"/>
               <class name="image-button"/>
             </style>
             <child>
diff --git a/src/tabs/gb-tab-grid.c b/src/tabs/gb-tab-grid.c
new file mode 100644
index 0000000..c04372d
--- /dev/null
+++ b/src/tabs/gb-tab-grid.c
@@ -0,0 +1,441 @@
+/* gb-tab-grid.c
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gb-log.h"
+#include "gb-tab.h"
+#include "gb-tab-stack.h"
+#include "gb-tab-grid.h"
+
+struct _GbTabGridPrivate
+{
+  GtkWidget *top_hpaned;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbTabGrid, gb_tab_grid, GTK_TYPE_BIN)
+
+static GtkWidget *
+gb_tab_grid_get_first_stack (GbTabGrid*);
+
+GtkWidget *
+gb_tab_grid_new (void)
+{
+  return g_object_new (GB_TYPE_TAB_GRID, NULL);
+}
+
+static void
+gb_tab_grid_remove_empty (GbTabGrid *self)
+{
+  GbTabGridPrivate *priv;
+  GtkWidget *paned;
+  GtkWidget *stack;
+  GtkWidget *parent;
+  GtkWidget *child;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TAB_GRID (self));
+
+  priv = self->priv;
+
+  paned = gtk_paned_get_child2 (GTK_PANED (priv->top_hpaned));
+  g_assert (GTK_IS_PANED (paned));
+
+  while (paned)
+    {
+      stack = gtk_paned_get_child1 (GTK_PANED (paned));
+      g_assert (GB_IS_TAB_STACK (stack));
+
+      if (!gb_tab_stack_get_n_tabs (GB_TAB_STACK (stack)))
+        {
+          child = gtk_paned_get_child2 (GTK_PANED (paned));
+          g_object_ref (child);
+          parent = gtk_widget_get_parent (paned);
+          gtk_container_remove (GTK_CONTAINER (paned), child);
+          gtk_container_remove (GTK_CONTAINER (parent), paned);
+          gtk_paned_add2 (GTK_PANED (parent), child);
+          g_object_unref (child);
+          paned = parent;
+        }
+
+      paned = gtk_paned_get_child2 (GTK_PANED (paned));
+    }
+
+  /*
+   * If everything got removed, re-add a default stack.
+   */
+  if (!gtk_paned_get_child2 (GTK_PANED (priv->top_hpaned)))
+    (void)gb_tab_grid_get_first_stack (self);
+
+  EXIT;
+}
+
+static GtkWidget *
+gb_tab_grid_get_first_stack (GbTabGrid *self)
+{
+  GbTabGridPrivate *priv;
+  GtkWidget *child;
+  GtkWidget *paned;
+
+  ENTRY;
+
+  g_return_val_if_fail (GB_IS_TAB_GRID (self), NULL);
+
+  priv = self->priv;
+
+  if (!(paned = gtk_paned_get_child2 (GTK_PANED (priv->top_hpaned))))
+    {
+      paned = g_object_new (GTK_TYPE_PANED,
+                            "orientation", GTK_ORIENTATION_HORIZONTAL,
+                            "visible", TRUE,
+                            NULL);
+      gtk_paned_add2 (GTK_PANED (priv->top_hpaned), paned);
+      gtk_container_child_set (GTK_CONTAINER (priv->top_hpaned), paned,
+                               "resize", TRUE,
+                               "shrink", FALSE,
+                               NULL);
+      child = g_object_new (GB_TYPE_TAB_STACK,
+                            "visible", TRUE,
+                            NULL);
+      g_signal_connect_swapped (child, "changed",
+                                G_CALLBACK (gb_tab_grid_remove_empty),
+                                self);
+      gtk_paned_add1 (GTK_PANED (paned), child);
+    }
+
+  child = gtk_paned_get_child1 (GTK_PANED (paned));
+
+  RETURN (child);
+}
+
+static void
+gb_tab_grid_add (GtkContainer *container,
+                 GtkWidget    *child)
+{
+  GbTabGridPrivate *priv;
+  GbTabGrid *self = (GbTabGrid *) container;
+  GtkWidget *stack = NULL;
+  GtkWidget *toplevel;
+
+  g_return_if_fail (GB_IS_TAB_GRID (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+
+  priv = self->priv;
+
+  if (GB_IS_TAB (child))
+    {
+      /*
+       * Try to find the currently focused view.
+       */
+      toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+      if (toplevel && GTK_IS_WINDOW (toplevel))
+        {
+          if ((stack = gtk_window_get_focus (GTK_WINDOW (toplevel))))
+            while (stack && !GB_IS_TAB_STACK (stack))
+              stack = gtk_widget_get_parent (stack);
+
+        }
+
+      if (!stack)
+        stack = gb_tab_grid_get_first_stack (self);
+
+      gtk_container_add (GTK_CONTAINER (stack), child);
+    }
+  else
+    gtk_paned_add1 (GTK_PANED (priv->top_hpaned), child);
+}
+
+static GList *
+gb_tab_grid_get_stacks (GbTabGrid *self)
+{
+  GbTabGridPrivate *priv;
+  GtkWidget *child;
+  GtkWidget *paned;
+  GList *list = NULL;
+
+  ENTRY;
+
+  g_return_val_if_fail (GB_IS_TAB_GRID (self), NULL);
+
+  priv = self->priv;
+
+  paned = priv->top_hpaned;
+
+  for (; paned; paned = gtk_paned_get_child2 (GTK_PANED (paned)))
+    {
+      child = gtk_paned_get_child1 (GTK_PANED (paned));
+      if (GB_IS_TAB_STACK (child))
+        list = g_list_append (list, child);
+    }
+
+  RETURN (list);
+}
+
+GList *
+gb_tab_grid_get_tabs (GbTabGrid *self)
+{
+  GList *stacks;
+  GList *iter;
+  GList *ret = NULL;
+
+  ENTRY;
+
+  g_return_val_if_fail (GB_IS_TAB_GRID (self), NULL);
+
+  stacks = gb_tab_grid_get_stacks (self);
+  for (iter = stacks; iter; iter = iter->next)
+    ret = g_list_concat (ret, gb_tab_stack_get_tabs (iter->data));
+  g_list_free (stacks);
+
+  RETURN (ret);
+}
+
+static void
+gb_tab_grid_realign (GbTabGrid *self)
+{
+  GbTabGridPrivate *priv;
+  GtkAllocation alloc;
+  GtkWidget *paned;
+  guint n_paneds = 0;
+  guint width;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TAB_GRID (self));
+
+  priv = self->priv;
+
+  paned = gtk_paned_get_child2 (GTK_PANED (priv->top_hpaned));
+  do
+    n_paneds++;
+  while ((paned = gtk_paned_get_child2 (GTK_PANED (paned))));
+  g_assert_cmpint (n_paneds, >, 0);
+
+  paned = gtk_paned_get_child2 (GTK_PANED (priv->top_hpaned));
+  gtk_widget_get_allocation (paned, &alloc);
+  width = alloc.width / n_paneds;
+
+  do
+    gtk_paned_set_position (GTK_PANED (paned), width);
+  while ((paned = gtk_paned_get_child2 (GTK_PANED (paned))));
+
+  EXIT;
+}
+
+static GbTabStack *
+gb_tab_grid_add_stack (GbTabGrid *self)
+{
+  GbTabGridPrivate *priv;
+  GtkWidget *stack_paned;
+  GtkWidget *stack = NULL;
+  GtkWidget *paned;
+
+  ENTRY;
+
+  g_return_val_if_fail (GB_IS_TAB_GRID (self), NULL);
+
+  priv = self->priv;
+
+  stack = g_object_new (GB_TYPE_TAB_STACK,
+                        "visible", TRUE,
+                        NULL);
+  g_signal_connect_swapped (stack, "changed",
+                            G_CALLBACK (gb_tab_grid_remove_empty),
+                            self);
+
+  paned = priv->top_hpaned;
+  while (gtk_paned_get_child2 (GTK_PANED (paned)))
+    paned = gtk_paned_get_child2 (GTK_PANED (paned));
+
+  stack_paned = g_object_new (GTK_TYPE_PANED,
+                              "orientation", GTK_ORIENTATION_HORIZONTAL,
+                              "visible", TRUE,
+                              NULL);
+  gtk_paned_add1 (GTK_PANED (stack_paned), stack);
+  gtk_container_child_set (GTK_CONTAINER (stack_paned), stack,
+                           "resize", TRUE,
+                           "shrink", FALSE,
+                           NULL);
+
+  gtk_paned_add2 (GTK_PANED (paned), stack_paned);
+  gtk_container_child_set (GTK_CONTAINER (paned), stack_paned,
+                           "resize", TRUE,
+                           "shrink", FALSE,
+                           NULL);
+
+  gb_tab_grid_realign (self);
+
+  RETURN (GB_TAB_STACK (stack));
+}
+
+void
+gb_tab_grid_move_tab_right (GbTabGrid *self,
+                            GbTab     *tab)
+{
+  GbTabStack *stack;
+  GList *iter;
+  GList *stacks;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TAB_GRID (self));
+  g_return_if_fail (GB_IS_TAB (tab));
+
+  stacks = gb_tab_grid_get_stacks (self);
+
+  for (iter = stacks; iter; iter = iter->next)
+    {
+      if (gb_tab_stack_contains_tab (iter->data, tab))
+        {
+          g_object_ref (tab);
+          gb_tab_stack_remove_tab (iter->data, tab);
+          if (!iter->next)
+            stack = gb_tab_grid_add_stack (self);
+          else
+            stack = iter->next->data;
+#if 0
+          gb_tab_stack_add_tab (stack, tab);
+#endif
+          gtk_container_add (GTK_CONTAINER (stack), GTK_WIDGET (tab));
+          g_object_unref (tab);
+          break;
+        }
+    }
+
+  g_list_free (stacks);
+
+  gb_tab_grid_remove_empty (self);
+
+  EXIT;
+}
+
+void
+gb_tab_grid_focus_next_view (GbTabGrid *self,
+                             GbTab     *tab)
+{
+  GList *iter;
+  GList *stacks;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TAB_GRID (self));
+  g_return_if_fail (GB_IS_TAB (tab));
+
+  /* TODO: track focus so we can drop @tab parameter */
+
+  stacks = gb_tab_grid_get_stacks (self);
+  for (iter = stacks; iter; iter = iter->next)
+    {
+      if (gb_tab_stack_contains_tab (iter->data, tab))
+        {
+          if (!gb_tab_stack_focus_next (iter->data))
+            if (iter->next)
+              gb_tab_stack_focus_first (iter->next->data);
+
+          break;
+        }
+    }
+  g_list_free (stacks);
+
+  EXIT;
+}
+
+void
+gb_tab_grid_focus_previous_view (GbTabGrid *self,
+                                 GbTab     *view)
+{
+  GList *iter;
+  GList *stacks;
+
+  ENTRY;
+
+  g_return_if_fail (GB_IS_TAB_GRID (self));
+  g_return_if_fail (GB_IS_TAB (view));
+
+  /* TODO: track focus so we can drop @tab parameter */
+
+  stacks = gb_tab_grid_get_stacks (self);
+  stacks = g_list_reverse (stacks);
+  for (iter = stacks; iter; iter = iter->next)
+    {
+      if (gb_tab_stack_contains_tab (iter->data, view))
+        {
+          gb_tab_stack_focus_previous (iter->data);
+          break;
+        }
+    }
+  g_list_free (stacks);
+
+  EXIT;
+}
+
+/**
+ * gb_tab_grid_class_init:
+ * @klass: (in): A #GbTabGridClass.
+ *
+ * Initializes the #GbTabGridClass and prepares the vtable.
+ */
+static void
+gb_tab_grid_class_init (GbTabGridClass *klass)
+{
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  container_class->add = gb_tab_grid_add;
+}
+
+/**
+ * gb_tab_grid_init:
+ * @self: (in): A #GbTabGrid.
+ *
+ * Initializes the newly created #GbTabGrid instance.
+ */
+static void
+gb_tab_grid_init (GbTabGrid *self)
+{
+  GtkWidget *paned;
+  GtkWidget *stack;
+
+  self->priv = gb_tab_grid_get_instance_private (self);
+
+  self->priv->top_hpaned =
+    g_object_new (GTK_TYPE_PANED,
+                  "orientation", GTK_ORIENTATION_HORIZONTAL,
+                  "visible", TRUE,
+                  NULL);
+  GTK_CONTAINER_CLASS (gb_tab_grid_parent_class)->add (GTK_CONTAINER (self),
+                                                       self->priv->top_hpaned);
+
+  paned = g_object_new (GTK_TYPE_PANED,
+                        "orientation", GTK_ORIENTATION_HORIZONTAL,
+                        "visible", TRUE,
+                        NULL);
+  gtk_paned_add2 (GTK_PANED (self->priv->top_hpaned), paned);
+
+  stack = g_object_new (GB_TYPE_TAB_STACK,
+                        "visible", TRUE,
+                        NULL);
+  g_signal_connect_swapped (stack, "changed",
+                            G_CALLBACK (gb_tab_grid_remove_empty),
+                            self);
+  gtk_paned_add1 (GTK_PANED (paned), stack);
+  gtk_container_child_set (GTK_CONTAINER (paned), stack,
+                           "resize", TRUE,
+                           "shrink", FALSE,
+                           NULL);
+}
diff --git a/src/tabs/gb-tab-grid.h b/src/tabs/gb-tab-grid.h
new file mode 100644
index 0000000..cebb5a0
--- /dev/null
+++ b/src/tabs/gb-tab-grid.h
@@ -0,0 +1,62 @@
+/* gb-tab-grid.h
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#ifndef GB_TAB_GRID_H
+#define GB_TAB_GRID_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_TAB_GRID            (gb_tab_grid_get_type())
+#define GB_TAB_GRID(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TAB_GRID, GbTabGrid))
+#define GB_TAB_GRID_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TAB_GRID, GbTabGrid const))
+#define GB_TAB_GRID_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GB_TYPE_TAB_GRID, GbTabGridClass))
+#define GB_IS_TAB_GRID(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_TAB_GRID))
+#define GB_IS_TAB_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GB_TYPE_TAB_GRID))
+#define GB_TAB_GRID_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GB_TYPE_TAB_GRID, GbTabGridClass))
+
+typedef struct _GbTabGrid        GbTabGrid;
+typedef struct _GbTabGridClass   GbTabGridClass;
+typedef struct _GbTabGridPrivate GbTabGridPrivate;
+
+struct _GbTabGrid
+{
+   GtkBin parent;
+
+   /*< private >*/
+   GbTabGridPrivate *priv;
+};
+
+struct _GbTabGridClass
+{
+   GtkBinClass parent_class;
+};
+
+GtkWidget *gb_tab_grid_new                (void);
+GType      gb_tab_grid_get_type           (void) G_GNUC_CONST;
+void       gb_tab_grid_move_tab_right     (GbTabGrid *grid,
+                                           GbTab     *tab);
+void       gb_tab_grid_focus_next_tab     (GbTabGrid *grid,
+                                           GbTab     *tab);
+void       gb_tab_grid_focus_previous_tab (GbTabGrid *grid,
+                                           GbTab     *tab);
+
+G_END_DECLS
+
+#endif /* GB_TAB_GRID_H */
diff --git a/src/tabs/gb-tab-stack.c b/src/tabs/gb-tab-stack.c
new file mode 100644
index 0000000..16f6039
--- /dev/null
+++ b/src/tabs/gb-tab-stack.c
@@ -0,0 +1,713 @@
+/* gb-tab-stack.c
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gb-log.h"
+#include "gb-tab.h"
+#include "gb-tab-stack.h"
+
+struct _GbTabStackPrivate
+{
+   GtkListStore *tabs;
+   GtkWidget    *combo;
+   GtkWidget    *controls;
+   GtkWidget    *close;
+   GtkWidget    *notebook;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbTabStack, gb_tab_stack, GTK_TYPE_BOX)
+
+enum
+{
+   PROP_0,
+   LAST_PROP
+};
+
+enum
+{
+   CHANGED,
+   LAST_SIGNAL
+};
+
+//static GParamSpec *gParamSpecs[LAST_PROP];
+static guint gSignals[LAST_SIGNAL];
+
+guint
+gb_tab_stack_get_n_tabs (GbTabStack *stack)
+{
+   guint ret;
+
+   ENTRY;
+   g_return_val_if_fail(GB_IS_TAB_STACK(stack), 0);
+   ret = gtk_tree_model_iter_n_children(GTK_TREE_MODEL(stack->priv->tabs), NULL);
+   RETURN(ret);
+}
+
+gboolean
+gb_tab_stack_focus_first (GbTabStack *stack)
+{
+   GbTabStackPrivate *priv;
+   GtkTreeIter iter;
+
+   ENTRY;
+
+   g_return_val_if_fail(GB_IS_TAB_STACK(stack), FALSE);
+
+   priv = stack->priv;
+
+   if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(priv->tabs), &iter)) {
+      gtk_combo_box_set_active_iter(GTK_COMBO_BOX(priv->combo), &iter);
+      RETURN(TRUE);
+   }
+
+   RETURN(FALSE);
+}
+
+gboolean
+gb_tab_stack_focus_next (GbTabStack *stack)
+{
+   GbTabStackPrivate *priv;
+   guint n_tabs;
+   gint idx;
+
+   ENTRY;
+
+   g_return_val_if_fail(GB_IS_TAB_STACK(stack), FALSE);
+
+   priv = stack->priv;
+
+   if ((idx = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combo))) >= 0) {
+      n_tabs = gb_tab_stack_get_n_tabs(stack);
+      if ((idx + 1) < n_tabs) {
+         gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combo), idx + 1);
+         RETURN(TRUE);
+      }
+   }
+
+   RETURN(FALSE);
+}
+
+gboolean
+gb_tab_stack_focus_previous (GbTabStack *stack)
+{
+   GbTabStackPrivate *priv;
+   gint idx;
+
+   ENTRY;
+
+   g_return_val_if_fail(GB_IS_TAB_STACK(stack), FALSE);
+
+   priv = stack->priv;
+
+   if ((idx = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combo))) > 0) {
+      gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combo), idx - 1);
+      RETURN(TRUE);
+   }
+
+   RETURN(FALSE);
+}
+
+gboolean
+gb_tab_stack_focus_view (GbTabStack *stack,
+                          GbTab      *view)
+{
+   GbTabStackPrivate *priv;
+   GtkTreeModel *model;
+   GtkTreeIter iter;
+   GObject *object;
+
+   ENTRY;
+
+   g_return_val_if_fail(GB_IS_TAB_STACK(stack), FALSE);
+
+   priv = stack->priv;
+
+   model = gtk_combo_box_get_model(GTK_COMBO_BOX(priv->combo));
+
+   if (gtk_tree_model_get_iter_first(model, &iter)) {
+      do {
+         gtk_tree_model_get(model, &iter, 0, &object, -1);
+         g_object_unref(object);
+         if (object == (GObject *)view) {
+            gtk_combo_box_set_active_iter(GTK_COMBO_BOX(priv->combo), &iter);
+            RETURN(TRUE);
+         }
+      } while (gtk_tree_model_iter_next(model, &iter));
+   }
+
+   RETURN(FALSE);
+}
+
+gboolean
+gb_tab_stack_contains_tab (GbTabStack *stack,
+                           GbTab      *view)
+{
+   GbTabStackPrivate *priv;
+   GtkTreeModel *model;
+   GtkTreeIter iter;
+   GObject *object;
+
+   ENTRY;
+
+   g_return_val_if_fail(GB_IS_TAB_STACK(stack), FALSE);
+   g_return_val_if_fail(GB_IS_TAB(view), FALSE);
+
+   priv = stack->priv;
+
+   model = GTK_TREE_MODEL(priv->tabs);
+
+   if (gtk_tree_model_get_iter_first(model, &iter)) {
+      do {
+         gtk_tree_model_get(model, &iter, 0, &object, -1);
+         g_object_unref(object);
+         if (object == (GObject *)view) {
+            RETURN(TRUE);
+         }
+      } while (gtk_tree_model_iter_next(model, &iter));
+   }
+
+   RETURN(FALSE);
+}
+
+static void
+gb_tab_stack_set_page (GbTabStack *stack,
+                        gint         page)
+{
+   GbTabStackPrivate *priv;
+   GtkWidget *controls;
+   GtkWidget *view;
+   gint current;
+
+   ENTRY;
+
+   g_return_if_fail(GB_IS_TAB_STACK(stack));
+   g_return_if_fail(page >= 0);
+
+   priv = stack->priv;
+
+   current = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->controls));
+   if (current >= 0) {
+      controls = gtk_notebook_get_nth_page(GTK_NOTEBOOK(priv->controls), current);
+      gtk_widget_hide(controls);
+   }
+
+   gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), page);
+   gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->controls), page);
+
+   if ((controls = gtk_notebook_get_nth_page(GTK_NOTEBOOK(priv->controls), page))) {
+      gtk_widget_show(controls);
+   }
+
+   if ((view = gtk_notebook_get_nth_page(GTK_NOTEBOOK(priv->notebook), page))) {
+      gtk_widget_grab_focus(view);
+   }
+
+   EXIT;
+}
+
+void
+gb_tab_stack_remove_tab (GbTabStack *stack,
+                         GbTab      *view)
+{
+   GbTabStackPrivate *priv;
+   GtkTreeModel *model;
+   GtkTreeIter iter;
+   GtkWidget *controls;
+   GObject *object;
+   gint page = 0;
+   gboolean active = FALSE;
+
+   ENTRY;
+
+   g_return_if_fail(GB_IS_TAB_STACK(stack));
+   g_return_if_fail(GB_IS_TAB(view));
+
+   priv = stack->priv;
+
+   if (!gb_tab_stack_contains_tab (stack, view)) {
+      g_warning ("%s(): view is missing from stack.", G_STRFUNC);
+      EXIT;
+   }
+
+   /*
+    * TODO: Disconnect signals.
+    */
+
+   /*
+    * Remove the view from the drop down list.
+    */
+   model = GTK_TREE_MODEL(priv->tabs);
+   if (gtk_tree_model_get_iter_first(model, &iter)) {
+      do {
+         gtk_tree_model_get(model, &iter, 0, &object, -1);
+         g_object_unref(object);
+         if (object == (GObject *)view) {
+            active = (page == gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combo)));
+            gtk_list_store_remove(priv->tabs, &iter);
+            break;
+         }
+         page++;
+      } while (gtk_tree_model_iter_next(model, &iter));
+   }
+
+   /*
+    * Remove the controls from the notebook.
+    */
+   if ((controls = gb_tab_get_controls(view))) {
+      gtk_container_remove(GTK_CONTAINER(priv->controls), controls);
+   }
+
+   /*
+    * Remove the view from the notebook.
+    */
+   gtk_container_remove(GTK_CONTAINER(priv->notebook), GTK_WIDGET(view));
+
+   /*
+    * Hide the close button if there are no views left.
+    */
+   if (!gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook))) {
+      gtk_widget_hide(priv->close);
+   }
+
+   /*
+    * Try to set the page to the new item in the same slot if we can,
+    * otherwise the item previous.
+    */
+   if (active) {
+      page = MIN(page, gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)) - 1);
+      if (page >= 0) {
+         gtk_combo_box_set_active(GTK_COMBO_BOX(priv->combo), page);
+      }
+   }
+
+   g_signal_emit(stack, gSignals[CHANGED], 0);
+
+   EXIT;
+}
+
+static void
+gb_tab_stack_remove (GtkContainer *container,
+                      GtkWidget    *child)
+{
+   GbTabStack *stack = (GbTabStack *)container;
+
+   ENTRY;
+
+   g_return_if_fail(GB_IS_TAB_STACK(stack));
+   g_return_if_fail(GTK_IS_WIDGET(child));
+
+   if (GB_IS_TAB(child)) {
+      gb_tab_stack_remove_tab (stack, GB_TAB(child));
+   } else {
+      GTK_CONTAINER_CLASS(gb_tab_stack_parent_class)->remove(container, child);
+   }
+
+   EXIT;
+}
+
+/**
+ * gb_tab_stack_add_view:
+ * @stack: (in): A #GbTabStack.
+ * @view: (in): A #GbTab.
+ *
+ * Adds a view to the stack. An item is added to the #GtkComboBox
+ * for the view. When selected from the combo box, the view will be
+ * raised in the stack.
+ *
+ * The stack will take ownership of any of the controls provided by
+ * the view. In the case the view is removed from the stack, the
+ * controls will no longer be children of the stack. TODO
+ */
+static void
+gb_tab_stack_add_view (GbTabStack *stack,
+                        GbTab      *view)
+{
+   GbTabStackPrivate *priv;
+   GtkTreeIter iter;
+   gint page;
+
+   ENTRY;
+
+   g_return_if_fail(GB_IS_TAB_STACK(stack));
+   g_return_if_fail(GB_IS_TAB(view));
+
+   priv = stack->priv;
+
+   gtk_container_add(GTK_CONTAINER(priv->notebook), GTK_WIDGET(view));
+
+   gtk_list_store_append(priv->tabs, &iter);
+   gtk_list_store_set(priv->tabs, &iter, 0, view, -1);
+   gtk_combo_box_set_active_iter(GTK_COMBO_BOX(priv->combo), &iter);
+   gtk_container_add(GTK_CONTAINER(priv->controls), gb_tab_get_controls(view));
+
+   g_signal_connect_swapped(view, "notify::can-save",
+                            G_CALLBACK(gtk_widget_queue_draw),
+                            priv->combo);
+   g_signal_connect_swapped(view, "notify::name",
+                            G_CALLBACK(gtk_widget_queue_draw),
+                            priv->combo);
+   g_signal_connect_swapped(view, "closed",
+                            G_CALLBACK(gb_tab_stack_remove_tab),
+                            stack);
+
+   if ((page = gtk_notebook_get_n_pages(GTK_NOTEBOOK(priv->notebook)))) {
+      gb_tab_stack_set_page(stack, page - 1);
+   }
+
+   gtk_widget_show(priv->close);
+
+   g_signal_emit(stack, gSignals[CHANGED], 0);
+
+   EXIT;
+}
+
+/**
+ * gb_tab_stack_add:
+ * @container: (in): A #GbTabStack.
+ * @child: (in): A #GtkWidget.
+ *
+ * Handle the addition of a child to the view. If the child is a view,
+ * then we pack it into our internal notebook.
+ */
+static void
+gb_tab_stack_add (GtkContainer *container,
+                   GtkWidget    *child)
+{
+   GbTabStack *stack = (GbTabStack *)container;
+
+   ENTRY;
+
+   g_return_if_fail(GB_IS_TAB_STACK(stack));
+   g_return_if_fail(GTK_IS_WIDGET(child));
+
+   if (GB_IS_TAB(child)) {
+      gb_tab_stack_add_view(stack, GB_TAB(child));
+   } else {
+      GTK_CONTAINER_CLASS(gb_tab_stack_parent_class)->add(container, child);
+   }
+
+   EXIT;
+}
+
+/**
+ * gb_tab_stack_get_active:
+ * @stack: (in): A #GbTabStack.
+ *
+ * Gets the active view based on the current focus.
+ *
+ * Returns: (transfer none): A #GtkWidget or %NULL.
+ */
+GtkWidget *
+gb_tab_stack_get_active (GbTabStack *stack)
+{
+   GtkWidget *toplevel;
+   GtkWidget *focus = NULL;
+
+   ENTRY;
+
+   g_return_val_if_fail(GB_IS_TAB_STACK(stack), NULL);
+
+   if ((toplevel = gtk_widget_get_toplevel(GTK_WIDGET(stack)))) {
+      if ((focus = gtk_window_get_focus(GTK_WINDOW(toplevel)))) {
+         while (focus && !GB_IS_TAB(focus)) {
+            focus = gtk_widget_get_parent(focus);
+         }
+      }
+   }
+
+   RETURN(focus);
+}
+
+static void
+gb_tab_stack_icon_name_func (GtkCellLayout   *cell_layout,
+                              GtkCellRenderer *cell,
+                              GtkTreeModel    *tree_model,
+                              GtkTreeIter     *iter,
+                              gpointer         user_data)
+{
+   GbTab *view;
+
+   gtk_tree_model_get(tree_model, iter, 0, &view, -1);
+   g_assert(GB_IS_TAB(view));
+   g_object_set(cell, "icon-name", gb_tab_get_icon_name(view), NULL);
+   g_object_unref(view);
+}
+
+static void
+gb_tab_stack_name_func (GtkCellLayout   *cell_layout,
+                         GtkCellRenderer *cell,
+                         GtkTreeModel    *tree_model,
+                         GtkTreeIter     *iter,
+                         gpointer         user_data)
+{
+   gchar *text;
+   GbTab *view;
+
+   gtk_tree_model_get(tree_model, iter, 0, &view, -1);
+   g_assert(GB_IS_TAB(view));
+   text = g_strdup_printf("%s%s",
+                          gb_tab_get_title (view),
+                          gb_tab_get_dirty (view) ? " •" : "");
+   g_object_set(cell, "text", text, NULL);
+   g_object_unref(view);
+   g_free(text);
+}
+
+static void
+gb_tab_stack_combo_changed (GbTabStack *stack,
+                             GtkComboBox *combo)
+{
+   gint page;
+
+   g_return_if_fail(GB_IS_TAB_STACK(stack));
+   g_return_if_fail(GTK_IS_COMBO_BOX(combo));
+
+   if ((page = gtk_combo_box_get_active(combo)) >= 0) {
+      gb_tab_stack_set_page(stack, page);
+      gtk_widget_grab_focus(stack->priv->notebook);
+   }
+}
+
+static void
+gb_tab_stack_grab_focus (GtkWidget *widget)
+{
+   GbTabStack *stack = (GbTabStack *)widget;
+   g_return_if_fail(GB_IS_TAB_STACK(stack));
+   gtk_widget_grab_focus(stack->priv->notebook);
+}
+
+static void
+gb_tab_stack_close_current (GbTabStack *stack,
+                             GtkButton   *button)
+{
+   GbTabStackPrivate *priv;
+   GtkWidget *child;
+   gint page;
+
+   ENTRY;
+
+   g_return_if_fail(GB_IS_TAB_STACK(stack));
+
+   priv = stack->priv;
+
+   page = gtk_notebook_get_current_page(GTK_NOTEBOOK(priv->notebook));
+   if (page >= 0) {
+      child = gtk_notebook_get_nth_page(GTK_NOTEBOOK(priv->notebook), page);
+      if (GB_IS_TAB(child)) {
+         gb_tab_close(GB_TAB(child));
+      }
+   }
+
+   EXIT;
+}
+
+/**
+ * gb_tab_stack_get_tabs:
+ * @stack: (in): A #GbTabStack.
+ *
+ * Get all views in the stack.
+ *
+ * Returns: (transfer container) (element-type GbTab*): A #GList.
+ */
+GList *
+gb_tab_stack_get_tabs (GbTabStack *stack)
+{
+   GtkTreeModel *model;
+   GtkTreeIter iter;
+   GbTab *view;
+   GList *ret = NULL;
+
+   ENTRY;
+
+   g_return_val_if_fail(GB_IS_TAB_STACK(stack), NULL);
+
+   model = GTK_TREE_MODEL(stack->priv->tabs);
+   if (gtk_tree_model_get_iter_first(model, &iter)) {
+      do {
+         /*
+          * The code below looks unsafe. However, the model holds our
+          * reference to the view, so it is fine.
+          */
+         gtk_tree_model_get(model, &iter, 0, &view, -1);
+         ret = g_list_append(ret, view);
+         g_object_unref(view);
+      } while (gtk_tree_model_iter_next(model, &iter));
+   }
+
+   RETURN(ret);
+}
+
+/**
+ * gb_tab_stack_finalize:
+ * @object: (in): A #GbTabStack.
+ *
+ * Finalizer for a #GbTabStack instance. Frees any resources held by
+ * the instance.
+ */
+static void
+gb_tab_stack_finalize (GObject *object)
+{
+   GbTabStackPrivate *priv;
+
+   ENTRY;
+
+   priv = GB_TAB_STACK(object)->priv;
+
+   g_clear_object(&priv->tabs);
+
+   G_OBJECT_CLASS(gb_tab_stack_parent_class)->finalize(object);
+
+   EXIT;
+}
+
+/**
+ * gb_tab_stack_class_init:
+ * @klass: (in): A #GbTabStackClass.
+ *
+ * Initializes the #GbTabStackClass and prepares the vtable.
+ */
+static void
+gb_tab_stack_class_init (GbTabStackClass *klass)
+{
+   GObjectClass *object_class;
+   GtkWidgetClass *widget_class;
+   GtkContainerClass *container_class;
+
+   object_class = G_OBJECT_CLASS(klass);
+   object_class->finalize = gb_tab_stack_finalize;
+
+   widget_class = GTK_WIDGET_CLASS(klass);
+   widget_class->grab_focus = gb_tab_stack_grab_focus;
+
+   container_class = GTK_CONTAINER_CLASS(klass);
+   container_class->add = gb_tab_stack_add;
+   container_class->remove = gb_tab_stack_remove;
+
+   /**
+    * GbTabStack::changed:
+    *
+    * The "changed" signal is emitted when the children of the stack have
+    * changed.
+    */
+   gSignals[CHANGED] = g_signal_new("changed",
+                                    GB_TYPE_TAB_STACK,
+                                    G_SIGNAL_RUN_LAST,
+                                    0,
+                                    NULL,
+                                    NULL,
+                                    g_cclosure_marshal_VOID__VOID,
+                                    G_TYPE_NONE,
+                                    0);
+}
+
+/**
+ * gb_tab_stack_init:
+ * @stack: (in): A #GbTabStack.
+ *
+ * Initializes the newly created #GbTabStack instance.
+ */
+static void
+gb_tab_stack_init (GbTabStack *stack)
+{
+   GbTabStackPrivate *priv;
+   GtkCellRenderer *cell;
+   GtkWidget *hbox;
+
+   stack->priv = priv = gb_tab_stack_get_instance_private (stack);
+
+   gtk_orientable_set_orientation (GTK_ORIENTABLE (stack),
+                                   GTK_ORIENTATION_VERTICAL);
+
+   priv->tabs = gtk_list_store_new(1, GB_TYPE_TAB);
+
+   hbox = g_object_new(GTK_TYPE_BOX,
+                       "orientation", GTK_ORIENTATION_HORIZONTAL,
+                       "visible", TRUE,
+                       NULL);
+   gtk_container_add_with_properties(GTK_CONTAINER(stack), hbox,
+                                     "expand", FALSE,
+                                     NULL);
+
+   priv->combo = g_object_new(GTK_TYPE_COMBO_BOX,
+                              "has-frame", FALSE,
+                              "height-request", 30,
+                              "model", priv->tabs,
+                              "visible", TRUE,
+                              NULL);
+   gtk_container_add_with_properties(GTK_CONTAINER(hbox), priv->combo,
+                                     "expand", TRUE,
+                                     NULL);
+   g_signal_connect_swapped(priv->combo, "changed",
+                            G_CALLBACK(gb_tab_stack_combo_changed),
+                            stack);
+
+   priv->controls = g_object_new(GTK_TYPE_NOTEBOOK,
+                                 "visible", TRUE,
+                                 "show-border", FALSE,
+                                 "show-tabs", FALSE,
+                                 NULL);
+   gtk_container_add_with_properties(GTK_CONTAINER(hbox), priv->controls,
+                                     "expand", FALSE,
+                                     NULL);
+
+   priv->close = g_object_new(GTK_TYPE_BUTTON,
+                              "child", g_object_new(GTK_TYPE_IMAGE,
+                                                    "icon-name", "window-close-symbolic",
+                                                    "icon-size", GTK_ICON_SIZE_MENU,
+                                                    "visible", TRUE,
+                                                    "tooltip-text", _("Close the current view."),
+                                                    NULL),
+                              "visible", FALSE,
+                              NULL);
+   g_signal_connect_swapped(priv->close, "clicked",
+                            G_CALLBACK(gb_tab_stack_close_current),
+                            stack);
+   gtk_container_add_with_properties(GTK_CONTAINER(hbox), priv->close,
+                                     "expand", FALSE,
+                                     NULL);
+
+   cell = g_object_new(GTK_TYPE_CELL_RENDERER_PIXBUF,
+                       "icon-name", "text-x-generic",
+                       "width", 24,
+                       "xalign", 0.5f,
+                       "xpad", 3,
+                       NULL);
+   gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combo), cell, FALSE);
+   gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(priv->combo), cell,
+                                      gb_tab_stack_icon_name_func,
+                                      NULL, NULL);
+
+   cell = g_object_new(GTK_TYPE_CELL_RENDERER_TEXT,
+                       "size-points", 9.0,
+                       "xpad", 3,
+                       NULL);
+   gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(priv->combo), cell, TRUE);
+   gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(priv->combo), cell,
+                                      gb_tab_stack_name_func,
+                                      NULL, NULL);
+
+   priv->notebook = g_object_new(GTK_TYPE_NOTEBOOK,
+                                 "visible", TRUE,
+                                 "show-border", FALSE,
+                                 "show-tabs", FALSE,
+                                 NULL);
+   gtk_container_add_with_properties(GTK_CONTAINER(stack), priv->notebook,
+                                     "expand", TRUE,
+                                     NULL);
+}
diff --git a/src/tabs/gb-tab-stack.h b/src/tabs/gb-tab-stack.h
new file mode 100644
index 0000000..6d13fa5
--- /dev/null
+++ b/src/tabs/gb-tab-stack.h
@@ -0,0 +1,69 @@
+/* gb-tab-stack.h
+ *
+ * Copyright (C) 2011 Christian Hergert <chris dronelabs 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/>.
+ */
+
+#ifndef GB_TAB_STACK_H
+#define GB_TAB_STACK_H
+
+#include <gtk/gtk.h>
+
+#include "gb-tab.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_TAB_STACK            (gb_tab_stack_get_type())
+#define GB_TAB_STACK(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TAB_STACK, GbTabStack))
+#define GB_TAB_STACK_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_TAB_STACK, GbTabStack 
const))
+#define GB_TAB_STACK_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  GB_TYPE_TAB_STACK, GbTabStackClass))
+#define GB_IS_TAB_STACK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_TAB_STACK))
+#define GB_IS_TAB_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  GB_TYPE_TAB_STACK))
+#define GB_TAB_STACK_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  GB_TYPE_TAB_STACK, GbTabStackClass))
+
+typedef struct _GbTabStack        GbTabStack;
+typedef struct _GbTabStackClass   GbTabStackClass;
+typedef struct _GbTabStackPrivate GbTabStackPrivate;
+
+struct _GbTabStack
+{
+   GtkVBox parent;
+
+   /*< private >*/
+   GbTabStackPrivate *priv;
+};
+
+struct _GbTabStackClass
+{
+   GtkVBoxClass parent_class;
+};
+
+GType      gb_tab_stack_get_type       (void) G_GNUC_CONST;
+GtkWidget *gb_tab_stack_get_active     (GbTabStack *stack);
+gboolean   gb_tab_stack_contains_tab   (GbTabStack *stack,
+                                        GbTab      *tab);
+void       gb_tab_stack_remove_tab     (GbTabStack *stack,
+                                        GbTab      *tab);
+guint      gb_tab_stack_get_n_tabs     (GbTabStack *stack);
+gboolean   gb_tab_stack_focus_first    (GbTabStack *stack);
+gboolean   gb_tab_stack_focus_next     (GbTabStack *stack);
+gboolean   gb_tab_stack_focus_previous (GbTabStack *stack);
+gboolean   gb_tab_stack_focus_tab      (GbTabStack *stack,
+                                        GbTab      *tab);
+GList     *gb_tab_stack_get_tabs       (GbTabStack *stack);
+
+G_END_DECLS
+
+#endif /* GB_TAB_STACK_H */
diff --git a/src/tabs/gb-tab.c b/src/tabs/gb-tab.c
index 01ef233..508c669 100644
--- a/src/tabs/gb-tab.c
+++ b/src/tabs/gb-tab.c
@@ -74,6 +74,12 @@ gb_tab_get_header_area (GbTab *tab)
 }
 
 GtkWidget *
+gb_tab_get_controls(GbTab *tab)
+{
+  return tab->priv->header_box;
+}
+
+GtkWidget *
 gb_tab_get_footer_area (GbTab *tab)
 {
   g_return_val_if_fail (GB_IS_TAB (tab), NULL);
diff --git a/src/tabs/gb-tab.h b/src/tabs/gb-tab.h
index f23a9b0..316a389 100644
--- a/src/tabs/gb-tab.h
+++ b/src/tabs/gb-tab.h
@@ -65,6 +65,7 @@ void         gb_tab_set_dirty        (GbTab       *tab,
 void         gb_tab_freeze_drag      (GbTab       *tab);
 void         gb_tab_thaw_drag        (GbTab       *tab);
 void         gb_tab_close            (GbTab       *tab);
+GtkWidget   *gb_tab_get_controls     (GbTab       *tab);
 GtkWidget   *gb_tab_get_header_area  (GbTab       *tab);
 GtkWidget   *gb_tab_get_footer_area  (GbTab       *tab);
 GtkWidget   *gb_tab_get_content_area (GbTab       *tab);



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