[gnome-builder] documents: start importing document abstraction
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] documents: start importing document abstraction
- Date: Thu, 11 Dec 2014 00:08:26 +0000 (UTC)
commit 41cc8086b768ece5ae73b5b5fdaa2d94a4008c79
Author: Christian Hergert <christian hergert me>
Date: Sun Dec 7 02:39:24 2014 -0800
documents: start importing document abstraction
src/documents/gb-document-grid.c | 614 ++++++++++++++++++++++++++++
src/documents/gb-document-grid.h | 73 ++++
src/documents/gb-document-manager.c | 306 ++++++++-------
src/documents/gb-document-manager.h | 33 +-
src/documents/gb-document-menu-button.c | 650 ++++++++++++++++++++++++++++++
src/documents/gb-document-menu-button.h | 67 +++
src/documents/gb-document-split.c | 37 ++
src/documents/gb-document-split.h | 38 ++
src/documents/gb-document-stack.c | 667 +++++++++++++++++++++++++++++++
src/documents/gb-document-stack.h | 77 ++++
src/documents/gb-document-view.c | 202 ++++++++++
src/documents/gb-document-view.h | 64 +++
src/documents/gb-document.h | 12 +-
13 files changed, 2674 insertions(+), 166 deletions(-)
---
diff --git a/src/documents/gb-document-grid.c b/src/documents/gb-document-grid.c
new file mode 100644
index 0000000..c2c5bb2
--- /dev/null
+++ b/src/documents/gb-document-grid.c
@@ -0,0 +1,614 @@
+/* gb-document-grid.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "document-manager"
+
+#include <glib/gi18n.h>
+
+#include "gb-document-grid.h"
+
+struct _GbDocumentGridPrivate
+{
+ GbDocumentManager *document_manager;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbDocumentGrid, gb_document_grid, GTK_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_DOCUMENT_MANAGER,
+ LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void gb_document_grid_reposition (GbDocumentGrid *grid);
+
+GtkWidget *
+gb_document_grid_new (void)
+{
+ return g_object_new (GB_TYPE_DOCUMENT_GRID, NULL);
+}
+
+static void
+gb_document_grid_create_view (GbDocumentGrid *grid,
+ GbDocument *document,
+ GbDocumentSplit split,
+ GbDocumentStack *stack)
+{
+ GtkWidget *target_stack = NULL;
+
+ g_return_if_fail (GB_IS_DOCUMENT_GRID (grid));
+ g_return_if_fail (GB_IS_DOCUMENT (document));
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+
+ switch (split)
+ {
+ case GB_DOCUMENT_SPLIT_LEFT:
+ target_stack = gb_document_grid_get_stack_before (grid, stack);
+ if (!target_stack)
+ target_stack = gb_document_grid_add_stack_before (grid, stack);
+ break;
+
+ case GB_DOCUMENT_SPLIT_RIGHT:
+ target_stack = gb_document_grid_get_stack_after (grid, stack);
+ if (!target_stack)
+ target_stack = gb_document_grid_add_stack_after (grid, stack);
+ break;
+
+ case GB_DOCUMENT_SPLIT_NONE:
+ break;
+
+ default:
+ g_return_if_reached ();
+ }
+
+ gb_document_stack_focus_document (GB_DOCUMENT_STACK (target_stack), document);
+}
+
+static void
+gb_document_grid_remove_stack (GbDocumentGrid *grid,
+ GbDocumentStack *stack)
+{
+ GList *stacks;
+ GList *iter;
+
+ g_return_if_fail (GB_IS_DOCUMENT_GRID (grid));
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+
+ stacks = gb_document_grid_get_stacks (grid);
+
+ /* refuse to remove the stack if there is only one */
+ if (g_list_length (stacks) == 1)
+ return;
+
+ for (iter = stacks; iter; iter = iter->next)
+ {
+ GbDocumentStack *item = GB_DOCUMENT_STACK (iter->data);
+
+ if (item == stack)
+ {
+ if (!iter->prev)
+ {
+ GtkWidget *paned;
+ GtkWidget *child2;
+
+ /*
+ * This is the first stack in the grid. All we need to do to get
+ * to a consistent state is to take the child2 paned and replace
+ * our toplevel paned with it.
+ */
+ paned = gtk_bin_get_child (GTK_BIN (grid));
+ child2 = gtk_paned_get_child2 (GTK_PANED (paned));
+ g_object_ref (child2);
+ gtk_container_remove (GTK_CONTAINER (paned), child2);
+ gtk_container_remove (GTK_CONTAINER (grid), paned);
+ gtk_container_add (GTK_CONTAINER (grid), child2);
+ g_object_unref (child2);
+ }
+ else if (!iter->next)
+ {
+ GtkWidget *paned;
+ GtkWidget *grandparent;
+
+ /*
+ * This is the last stack in the grid. All we need to do to get
+ * to a consistent state is remove our parent paned from the
+ * grandparent.
+ */
+ paned = gtk_widget_get_parent (GTK_WIDGET (stack));
+ grandparent = gtk_widget_get_parent (paned);
+ gtk_container_remove (GTK_CONTAINER (grandparent), paned);
+ }
+ else if (iter->next && iter->prev)
+ {
+ GtkWidget *grandparent;
+ GtkWidget *paned;
+ GtkWidget *child2;
+
+ /*
+ * This stack is somewhere in the middle. All we need to do to
+ * get into a consistent state is take our parent paneds child2
+ * and put it in our parent's location.
+ */
+ paned = gtk_widget_get_parent (GTK_WIDGET (stack));
+ grandparent = gtk_widget_get_parent (paned);
+ child2 = gtk_paned_get_child2 (GTK_PANED (paned));
+ g_object_ref (child2);
+ gtk_container_remove (GTK_CONTAINER (paned), child2);
+ gtk_container_remove (GTK_CONTAINER (grandparent), paned);
+ gtk_container_add (GTK_CONTAINER (grandparent), child2);
+ g_object_unref (child2);
+ }
+ else
+ g_assert_not_reached ();
+
+ gb_document_grid_reposition (grid);
+
+ break;
+ }
+ }
+
+ g_list_free (stacks);
+}
+
+static void
+gb_document_grid_stack_empty (GbDocumentGrid *grid,
+ GbDocumentStack *stack)
+{
+ GList *stacks;
+
+ g_return_if_fail (GB_IS_DOCUMENT_GRID (grid));
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+
+ stacks = gb_document_grid_get_stacks (grid);
+
+ g_assert (stacks != NULL);
+
+ if (g_list_length (stacks) == 1)
+ goto cleanup;
+
+ gb_document_grid_remove_stack (grid, stack);
+
+cleanup:
+ g_list_free (stacks);
+}
+
+static GtkPaned *
+gb_document_grid_create_paned (GbDocumentGrid *grid)
+{
+ return g_object_new (GTK_TYPE_PANED,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "visible", TRUE,
+ NULL);
+}
+
+static GbDocumentStack *
+gb_document_grid_create_stack (GbDocumentGrid *grid)
+{
+ GbDocumentStack *stack;
+
+ stack = g_object_new (GB_TYPE_DOCUMENT_STACK,
+ "document-manager", grid->priv->document_manager,
+ "visible", TRUE,
+ NULL);
+
+ g_signal_connect_object (stack,
+ "create-view",
+ G_CALLBACK (gb_document_grid_create_view),
+ grid,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (stack,
+ "empty",
+ G_CALLBACK (gb_document_grid_stack_empty),
+ grid,
+ G_CONNECT_SWAPPED);
+
+ return stack;
+}
+
+GbDocumentManager *
+gb_document_grid_get_document_manager (GbDocumentGrid *grid)
+{
+ g_return_val_if_fail (GB_IS_DOCUMENT_GRID (grid), NULL);
+
+ return grid->priv->document_manager;
+}
+
+void
+gb_document_grid_set_document_manager (GbDocumentGrid *grid,
+ GbDocumentManager *document_manager)
+{
+ GbDocumentGridPrivate *priv;
+
+ g_return_if_fail (GB_IS_DOCUMENT_GRID (grid));
+ g_return_if_fail (!document_manager ||
+ GB_IS_DOCUMENT_MANAGER (document_manager));
+
+ priv = grid->priv;
+
+ if (priv->document_manager != document_manager)
+ {
+ g_clear_object (&priv->document_manager);
+
+ if (document_manager)
+ {
+ GList *list;
+ GList *iter;
+
+ priv->document_manager = g_object_ref (document_manager);
+
+ list = gb_document_grid_get_stacks (grid);
+
+ for (iter = list; iter; iter = iter->next)
+ gb_document_stack_set_document_manager (iter->data,
+ document_manager);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (grid),
+ gParamSpecs [PROP_DOCUMENT_MANAGER]);
+ }
+}
+
+static void
+gb_document_grid_reposition (GbDocumentGrid *grid)
+{
+ GtkAllocation alloc;
+ GtkWidget *paned;
+ GtkWidget *stack;
+ guint count = 0;
+ guint position;
+
+ g_return_if_fail (GB_IS_DOCUMENT_GRID (grid));
+
+ gtk_widget_get_allocation (GTK_WIDGET (grid), &alloc);
+
+ paned = gtk_bin_get_child (GTK_BIN (grid));
+
+ if (!GTK_IS_PANED (paned))
+ return;
+
+ stack = gtk_paned_get_child1 (GTK_PANED (paned));
+ do
+ {
+ count++;
+ stack = gb_document_grid_get_stack_after (grid,
+ GB_DOCUMENT_STACK (stack));
+ }
+ while (stack);
+
+ position = alloc.width / count;
+
+ stack = gtk_paned_get_child1 (GTK_PANED (paned));
+ do
+ {
+ paned = gtk_widget_get_parent (stack);
+ gtk_paned_set_position (GTK_PANED (paned), position);
+ stack = gb_document_grid_get_stack_after (grid,
+ GB_DOCUMENT_STACK (stack));
+ }
+ while (stack);
+}
+
+/**
+ * gb_document_grid_get_stacks:
+ *
+ * Fetches all of the stacks in the grid. The resulting #GList should be
+ * freed with g_list_free().
+ *
+ * Returns: (transfer container) (element-type GbDocumentStack*): A #GList.
+ */
+GList *
+gb_document_grid_get_stacks (GbDocumentGrid *grid)
+{
+ GtkWidget *paned;
+ GList *list = NULL;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_GRID (grid), NULL);
+
+ paned = gtk_bin_get_child (GTK_BIN (grid));
+
+ while (paned)
+ {
+ GtkWidget *stack;
+
+ stack = gtk_paned_get_child1 (GTK_PANED (paned));
+
+ if (GB_IS_DOCUMENT_STACK (stack))
+ list = g_list_append (list, stack);
+
+ paned = gtk_paned_get_child2 (GTK_PANED (paned));
+ }
+
+ return list;
+}
+
+GtkWidget *
+gb_document_grid_add_stack_before (GbDocumentGrid *grid,
+ GbDocumentStack *stack)
+{
+ GbDocumentStack *new_stack;
+ GtkWidget *parent;
+ GtkWidget *grandparent;
+ GtkPaned *new_paned;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_GRID (grid), NULL);
+
+ new_paned = gb_document_grid_create_paned (grid);
+ new_stack = gb_document_grid_create_stack (grid);
+ gtk_container_add (GTK_CONTAINER (new_paned), GTK_WIDGET (new_stack));
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (stack));
+ grandparent = gtk_widget_get_parent (GTK_WIDGET (parent));
+
+ if (GTK_IS_PANED (grandparent))
+ {
+ g_object_ref (parent);
+ gtk_container_remove (GTK_CONTAINER (grandparent), GTK_WIDGET (parent));
+ gtk_container_add_with_properties (GTK_CONTAINER (grandparent),
+ GTK_WIDGET (new_paned),
+ "shrink", FALSE,
+ "resize", TRUE,
+ NULL);
+ gtk_container_add_with_properties (GTK_CONTAINER (new_paned),
+ GTK_WIDGET (parent),
+ "shrink", FALSE,
+ "resize", TRUE,
+ NULL);
+ g_object_unref (parent);
+ }
+ else if (GB_IS_DOCUMENT_GRID (grandparent))
+ {
+ g_object_ref (parent);
+ gtk_container_remove (GTK_CONTAINER (grandparent), GTK_WIDGET (parent));
+ gtk_container_add (GTK_CONTAINER (grandparent), GTK_WIDGET (new_paned));
+ gtk_container_add_with_properties (GTK_CONTAINER (new_paned), parent,
+ "shrink", FALSE,
+ "resize", TRUE,
+ NULL);
+ g_object_unref (parent);
+ }
+ else
+ g_assert_not_reached ();
+
+ gb_document_grid_reposition (grid);
+
+ return GTK_WIDGET (new_stack);
+}
+
+GtkWidget *
+gb_document_grid_add_stack_after (GbDocumentGrid *grid,
+ GbDocumentStack *stack)
+{
+ GbDocumentStack *new_stack;
+ GtkWidget *parent;
+ GtkPaned *new_paned;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_GRID (grid), NULL);
+
+ new_paned = gb_document_grid_create_paned (grid);
+ new_stack = gb_document_grid_create_stack (grid);
+ gtk_container_add (GTK_CONTAINER (new_paned), GTK_WIDGET (new_stack));
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (stack));
+
+ if (GTK_IS_PANED (parent))
+ {
+ GtkWidget *child2;
+
+ child2 = gtk_paned_get_child2 (GTK_PANED (parent));
+
+ if (child2)
+ {
+ g_object_ref (child2);
+ gtk_container_remove (GTK_CONTAINER (parent), child2);
+ }
+
+ gtk_container_add_with_properties (GTK_CONTAINER (parent),
+ GTK_WIDGET (new_paned),
+ "shrink", FALSE,
+ "resize", TRUE,
+ NULL);
+
+ if (child2)
+ {
+ gtk_container_add_with_properties (GTK_CONTAINER (new_paned), child2,
+ "shrink", FALSE,
+ "resize", TRUE,
+ NULL);
+ g_object_unref (child2);
+ }
+ }
+ else
+ g_assert_not_reached ();
+
+ gb_document_grid_reposition (grid);
+
+ return GTK_WIDGET (new_stack);
+}
+
+GtkWidget *
+gb_document_grid_get_stack_before (GbDocumentGrid *grid,
+ GbDocumentStack *stack)
+{
+ GtkWidget *parent;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_GRID (grid), NULL);
+ g_return_val_if_fail (GB_IS_DOCUMENT_STACK (stack), NULL);
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (stack));
+
+ if (GTK_IS_PANED (parent))
+ {
+ parent = gtk_widget_get_parent (parent);
+ if (GTK_IS_PANED (parent))
+ return gtk_paned_get_child1 (GTK_PANED (parent));
+ }
+
+ return NULL;
+}
+
+GtkWidget *
+gb_document_grid_get_stack_after (GbDocumentGrid *grid,
+ GbDocumentStack *stack)
+{
+ GtkWidget *parent;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_GRID (grid), NULL);
+ g_return_val_if_fail (GB_IS_DOCUMENT_STACK (stack), NULL);
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (stack));
+
+ if (GTK_IS_PANED (parent))
+ {
+ GtkWidget *child2;
+
+ child2 = gtk_paned_get_child2 (GTK_PANED (parent));
+ if (GTK_IS_PANED (child2))
+ return gtk_paned_get_child1 (GTK_PANED (child2));
+ }
+
+ return NULL;
+}
+
+void
+gb_document_grid_focus_document (GbDocumentGrid *grid,
+ GbDocument *document)
+{
+ GList *stacks;
+ GList *iter;
+
+ g_return_if_fail (GB_IS_DOCUMENT_GRID (grid));
+ g_return_if_fail (GB_IS_DOCUMENT (document));
+
+ stacks = gb_document_grid_get_stacks (grid);
+
+ for (iter = stacks; iter; iter = iter->next)
+ {
+ GbDocumentStack *stack = iter->data;
+ GtkWidget *view;
+
+ view = gb_document_stack_find_with_document (stack, document);
+
+ if (view)
+ {
+ gb_document_stack_focus_document (stack, document);
+ goto cleanup;
+ }
+ }
+
+ g_assert (stacks);
+
+ gb_document_stack_focus_document (stacks->data, document);
+
+cleanup:
+ g_list_free (stacks);
+}
+
+static void
+gb_document_grid_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbDocumentGrid *self = GB_DOCUMENT_GRID(object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT_MANAGER:
+ g_value_set_object (value, gb_document_grid_get_document_manager (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_document_grid_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbDocumentGrid *self = GB_DOCUMENT_GRID(object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT_MANAGER:
+ gb_document_grid_set_document_manager (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_document_grid_finalize (GObject *object)
+{
+ GbDocumentGridPrivate *priv = GB_DOCUMENT_GRID (object)->priv;
+
+ g_clear_object (&priv->document_manager);
+
+ G_OBJECT_CLASS (gb_document_grid_parent_class)->finalize (object);
+}
+
+static void
+gb_document_grid_class_init (GbDocumentGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gb_document_grid_finalize;
+ object_class->get_property = gb_document_grid_get_property;
+ object_class->set_property = gb_document_grid_set_property;
+
+ /**
+ * GbDocumentGrid:document-manager:
+ *
+ * The "document-manager" property contains the manager for all open
+ * "buffers" (known as #GbDocument within Builder).
+ */
+ gParamSpecs [PROP_DOCUMENT_MANAGER] =
+ g_param_spec_object ("document-manager",
+ _("Document Manager"),
+ _("The document manager for the document grid."),
+ GB_TYPE_DOCUMENT_MANAGER,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_DOCUMENT_MANAGER,
+ gParamSpecs [PROP_DOCUMENT_MANAGER]);
+}
+
+static void
+gb_document_grid_init (GbDocumentGrid *self)
+{
+ GbDocumentStack *stack;
+ GtkPaned *paned;
+
+ self->priv = gb_document_grid_get_instance_private (self);
+
+ paned = gb_document_grid_create_paned (self);
+ stack = gb_document_grid_create_stack (self);
+
+ gtk_container_add_with_properties (GTK_CONTAINER (paned), GTK_WIDGET (stack),
+ "shrink", FALSE,
+ "resize", TRUE,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (paned));
+}
diff --git a/src/documents/gb-document-grid.h b/src/documents/gb-document-grid.h
new file mode 100644
index 0000000..fd0f268
--- /dev/null
+++ b/src/documents/gb-document-grid.h
@@ -0,0 +1,73 @@
+/* gb-document-grid.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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_DOCUMENT_GRID_H
+#define GB_DOCUMENT_GRID_H
+
+#include <gtk/gtk.h>
+
+#include "gb-document-manager.h"
+#include "gb-document-stack.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_DOCUMENT_GRID (gb_document_grid_get_type())
+#define GB_DOCUMENT_GRID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_DOCUMENT_GRID,
GbDocumentGrid))
+#define GB_DOCUMENT_GRID_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_DOCUMENT_GRID,
GbDocumentGrid const))
+#define GB_DOCUMENT_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GB_TYPE_DOCUMENT_GRID,
GbDocumentGridClass))
+#define GB_IS_DOCUMENT_GRID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_DOCUMENT_GRID))
+#define GB_IS_DOCUMENT_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GB_TYPE_DOCUMENT_GRID))
+#define GB_DOCUMENT_GRID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GB_TYPE_DOCUMENT_GRID,
GbDocumentGridClass))
+
+typedef struct _GbDocumentGrid GbDocumentGrid;
+typedef struct _GbDocumentGridClass GbDocumentGridClass;
+typedef struct _GbDocumentGridPrivate GbDocumentGridPrivate;
+
+struct _GbDocumentGrid
+{
+ GtkBin parent;
+
+ /*< private >*/
+ GbDocumentGridPrivate *priv;
+};
+
+struct _GbDocumentGridClass
+{
+ GtkBinClass parent;
+};
+
+GType gb_document_grid_get_type (void);
+GtkWidget *gb_document_grid_new (void);
+void gb_document_grid_set_document_manager (GbDocumentGrid *grid,
+ GbDocumentManager *manager);
+GbDocumentManager *gb_document_grid_get_document_manager (GbDocumentGrid *grid);
+GtkWidget *gb_document_grid_add_stack_after (GbDocumentGrid *grid,
+ GbDocumentStack *stack);
+GtkWidget *gb_document_grid_add_stack_before (GbDocumentGrid *grid,
+ GbDocumentStack *stack);
+GtkWidget *gb_document_grid_get_stack_after (GbDocumentGrid *grid,
+ GbDocumentStack *stack);
+GtkWidget *gb_document_grid_get_stack_before (GbDocumentGrid *grid,
+ GbDocumentStack *stack);
+GList *gb_document_grid_get_stacks (GbDocumentGrid *grid);
+void gb_document_grid_focus_document (GbDocumentGrid *grid,
+ GbDocument *document);
+
+G_END_DECLS
+
+#endif /* GB_DOCUMENT_GRID_H */
diff --git a/src/documents/gb-document-manager.c b/src/documents/gb-document-manager.c
index 704fc99..4d7554e 100644
--- a/src/documents/gb-document-manager.c
+++ b/src/documents/gb-document-manager.c
@@ -16,31 +16,36 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#define G_LOG_DOMAIN "document-manager"
+
#include <gtksourceview/gtksource.h>
#include "gb-document-manager.h"
#include "gb-editor-document.h"
-G_DEFINE_TYPE (GbDocumentManager, gb_document_manager, GTK_TYPE_LIST_STORE)
+struct _GbDocumentManagerPrivate
+{
+ GPtrArray *documents;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbDocumentManager, gb_document_manager,
+ G_TYPE_OBJECT)
enum {
- COLUMN_DOCUMENT,
- LAST_COLUMN
+ DOCUMENT_ADDED,
+ DOCUMENT_REMOVED,
+ DOCUMENT_MODIFIED_CHANGED,
+ LAST_SIGNAL
};
+static guint gSignals [LAST_SIGNAL];
+
GbDocumentManager *
gb_document_manager_new (void)
{
return g_object_new (GB_TYPE_DOCUMENT_MANAGER, NULL);
}
-/**
- * gb_document_manager_get_default:
- *
- * Retrieves the singleton instance of #GbDocumentManager.
- *
- * Returns: (transfer none): A #GbDocumentManager.
- */
GbDocumentManager *
gb_document_manager_get_default (void)
{
@@ -52,206 +57,211 @@ gb_document_manager_get_default (void)
return instance;
}
-/**
- * gb_document_manager_find_by_file:
- * @manager: A #GbDocumentManager.
- * @file: A #GFile.
- *
- * This function will attempt to locate a previously stored buffer that matches
- * the requsted file.
- *
- * If located, that buffer will be returned. Otherwise, %NULL is returned.
- *
- * Returns: (transfer none): A #GbDocument or %NULL.
- */
+guint
+gb_document_manager_get_count (GbDocumentManager *manager)
+{
+ g_return_val_if_fail (GB_IS_DOCUMENT_MANAGER (manager), 0);
+
+ return manager->priv->documents->len;
+}
+
GbDocument *
-gb_document_manager_find_by_file (GbDocumentManager *manager,
- GFile *file)
+gb_document_manager_find_with_file (GbDocumentManager *manager,
+ GFile *file)
{
- GtkTreeIter iter;
+ guint i;
g_return_val_if_fail (GB_IS_DOCUMENT_MANAGER (manager), NULL);
- g_return_val_if_fail (G_IS_FILE (file), NULL);
- if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (manager), &iter))
+ for (i = 0; i < manager->priv->documents->len; i++)
{
- do
- {
- GbEditorDocument *document;
- GtkSourceFile *source_file;
- GFile *location;
- GValue value = { 0 };
+ GbDocument *document;
- gtk_tree_model_get_value (GTK_TREE_MODEL (manager), &iter,
- COLUMN_DOCUMENT, &value);
+ document = g_ptr_array_index (manager->priv->documents, i);
- if (G_VALUE_HOLDS (&value, GB_TYPE_EDITOR_DOCUMENT))
- {
- document = g_value_get_object (&value);
-
- source_file = gb_editor_document_get_file (document);
- location = gtk_source_file_get_location (source_file);
+ if (GB_IS_EDITOR_DOCUMENT (document))
+ {
+ GtkSourceFile *sfile;
+ GFile *location;
- if (g_file_equal (location, file))
- {
- g_value_unset (&value);
- return GB_DOCUMENT (document);
- }
- }
+ sfile = gb_editor_document_get_file (GB_EDITOR_DOCUMENT (document));
+ location = gtk_source_file_get_location (sfile);
- g_value_unset (&value);
+ if (g_file_equal (location, file))
+ return document;
}
- while (gtk_tree_model_iter_next (GTK_TREE_MODEL (manager), &iter));
}
return NULL;
}
-static gboolean
-gb_document_manager_find_document (GbDocumentManager *manager,
- GbDocument *document,
- GtkTreeIter *iter)
+/**
+ * gb_document_manager_get_documents:
+ *
+ * Fetches a #GList of all the documents loaded by #GbDocumentManager.
+ *
+ * Returns: (transfer container) (element-type GbDocument*): #GList of
+ * #GbDocument. Free list with g_list_free().
+ */
+GList *
+gb_document_manager_get_documents (GbDocumentManager *manager)
{
- g_return_val_if_fail (GB_IS_DOCUMENT_MANAGER (manager), FALSE);
- g_return_val_if_fail (GB_IS_DOCUMENT (document), FALSE);
-
- if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (manager), iter))
- {
- do
- {
- GValue value = { 0 };
+ GList *list = NULL;
+ guint i;
- gtk_tree_model_get_value (GTK_TREE_MODEL (manager), iter,
- COLUMN_DOCUMENT, &value);
+ g_return_val_if_fail (GB_IS_DOCUMENT_MANAGER (manager), NULL);
- if (G_VALUE_HOLDS_OBJECT (&value) &&
- (g_value_get_object (&value) == (void *)document))
- {
- g_value_unset (&value);
- return TRUE;
- }
+ for (i = 0; i < manager->priv->documents->len; i++)
+ {
+ GbDocument *document;
- g_value_unset (&value);
- }
- while (gtk_tree_model_iter_next (GTK_TREE_MODEL (manager), iter));
+ document = g_ptr_array_index (manager->priv->documents, i);
+ list = g_list_prepend (list, document);
}
- return FALSE;
+ return list;
}
static void
-gb_document_manager_document_changed (GbDocumentManager *manager,
- GbDocument *document)
+gb_document_manager_document_modified (GbDocumentManager *manager,
+ GParamSpec *pspec,
+ GbDocument *document)
{
- GtkTreeIter iter;
-
g_return_if_fail (GB_IS_DOCUMENT_MANAGER (manager));
g_return_if_fail (GB_IS_DOCUMENT (document));
- if (gb_document_manager_find_document (manager, document, &iter))
- {
- GtkTreePath *tree_path;
-
- tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (manager), &iter);
- gtk_tree_model_row_changed (GTK_TREE_MODEL (manager), tree_path, &iter);
- gtk_tree_path_free (tree_path);
- }
+ g_signal_emit (manager, gSignals [DOCUMENT_MODIFIED_CHANGED], 0, document);
}
-static void
-gb_document_manager_on_notify_can_save (GbDocumentManager *manager,
- GParamSpec *pspec,
- GbDocument *document)
+void
+gb_document_manager_add (GbDocumentManager *manager,
+ GbDocument *document)
{
+ guint i;
+
g_return_if_fail (GB_IS_DOCUMENT_MANAGER (manager));
g_return_if_fail (GB_IS_DOCUMENT (document));
- gb_document_manager_document_changed (manager, document);
-}
+ for (i = 0; i < manager->priv->documents->len; i++)
+ {
+ GbDocument *item;
-static void
-gb_document_manager_on_notify_title (GbDocumentManager *manager,
- GParamSpec *pspec,
- GbDocument *document)
-{
- g_return_if_fail (GB_IS_DOCUMENT_MANAGER (manager));
- g_return_if_fail (GB_IS_DOCUMENT (document));
+ item = g_ptr_array_index (manager->priv->documents, i);
- gb_document_manager_document_changed (manager, document);
+ if (item == document)
+ {
+ g_warning ("GbDocumentManager already contains document \"%s\"",
+ gb_document_get_title (document));
+ return;
+ }
+ }
+
+ g_signal_connect_object (document,
+ "notify::modified",
+ G_CALLBACK (gb_document_manager_document_modified),
+ manager,
+ G_CONNECT_SWAPPED);
+
+ g_ptr_array_add (manager->priv->documents, g_object_ref (document));
+
+ g_signal_emit (manager, gSignals [DOCUMENT_ADDED], 0, document);
}
-/**
- * gb_document_manager_add_document:
- * @manager: A #GbDocumentManager.
- * @document: A #GbDocument.
- *
- * Adds A #GbDocument to the collection of buffers managed by the
- * #GbDocumentManager instance.
- */
void
-gb_document_manager_add_document (GbDocumentManager *manager,
- GbDocument *document)
+gb_document_manager_remove (GbDocumentManager *manager,
+ GbDocument *document)
{
- GtkTreeIter iter;
+ guint i;
g_return_if_fail (GB_IS_DOCUMENT_MANAGER (manager));
g_return_if_fail (GB_IS_DOCUMENT (document));
- g_signal_connect_object (document,
- "notify::title",
- G_CALLBACK (gb_document_manager_on_notify_title),
- manager,
- G_CONNECT_SWAPPED);
+ for (i = 0; i < manager->priv->documents->len; i++)
+ {
+ GbDocument *item;
- g_signal_connect_object (document,
- "notify::can-save",
- G_CALLBACK (gb_document_manager_on_notify_can_save),
- manager,
- G_CONNECT_SWAPPED);
+ item = g_ptr_array_index (manager->priv->documents, i);
- gtk_list_store_append (GTK_LIST_STORE (manager), &iter);
- gtk_list_store_set (GTK_LIST_STORE (manager), &iter,
- COLUMN_DOCUMENT, document,
- -1);
+ if (item == document)
+ {
+ g_signal_handlers_disconnect_by_func (item,
+ gb_document_manager_document_modified,
+ manager);
+ g_ptr_array_remove_index_fast (manager->priv->documents, i);
+ g_signal_emit (manager, gSignals [DOCUMENT_REMOVED], 0, item);
+ g_object_unref (item);
+ break;
+ }
+ }
}
-/**
- * gb_document_manager_remove_document:
- *
- * Removes the #GbDocument instance @document from being managed by
- * the #GbDocumentManager instance.
- *
- * Returns: %TRUE if the document was removed.
- */
-gboolean
-gb_document_manager_remove_document (GbDocumentManager *manager,
- GbDocument *document)
+static void
+gb_document_manager_finalize (GObject *object)
{
- GtkTreeIter iter;
+ GbDocumentManagerPrivate *priv = GB_DOCUMENT_MANAGER (object)->priv;
+ GbDocumentManager *manager = (GbDocumentManager *)object;
- g_return_val_if_fail (GB_IS_DOCUMENT_MANAGER (manager), FALSE);
- g_return_val_if_fail (GB_IS_DOCUMENT (document), FALSE);
-
- if (gb_document_manager_find_document (manager, document, &iter))
+ while (priv->documents->len)
{
- gtk_list_store_remove (GTK_LIST_STORE (manager), &iter);
- return TRUE;
+ GbDocument *document;
+
+ document = GB_DOCUMENT (g_ptr_array_index (priv->documents, 0));
+ gb_document_manager_remove (manager, document);
}
- return FALSE;
+ g_clear_pointer (&priv->documents, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (gb_document_manager_parent_class)->finalize (object);
}
static void
gb_document_manager_class_init (GbDocumentManagerClass *klass)
{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gb_document_manager_finalize;
+
+ gSignals [DOCUMENT_ADDED] =
+ g_signal_new ("document-added",
+ GB_TYPE_DOCUMENT_MANAGER,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GbDocumentManagerClass, document_added),
+ NULL,
+ NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 1,
+ GB_TYPE_DOCUMENT);
+
+ gSignals [DOCUMENT_MODIFIED_CHANGED] =
+ g_signal_new ("document-modified-changed",
+ GB_TYPE_DOCUMENT_MANAGER,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GbDocumentManagerClass,
+ document_modified_changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 1,
+ GB_TYPE_DOCUMENT);
+
+ gSignals [DOCUMENT_REMOVED] =
+ g_signal_new ("document-removed",
+ GB_TYPE_DOCUMENT_MANAGER,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GbDocumentManagerClass, document_removed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 1,
+ GB_TYPE_DOCUMENT);
}
static void
gb_document_manager_init (GbDocumentManager *self)
{
- GType column_types[] = { GB_TYPE_DOCUMENT };
-
- gtk_list_store_set_column_types (GTK_LIST_STORE (self),
- G_N_ELEMENTS (column_types),
- column_types);
+ self->priv = gb_document_manager_get_instance_private (self);
+ self->priv->documents = g_ptr_array_new ();
}
diff --git a/src/documents/gb-document-manager.h b/src/documents/gb-document-manager.h
index 4ca5962..6c01fde 100644
--- a/src/documents/gb-document-manager.h
+++ b/src/documents/gb-document-manager.h
@@ -19,7 +19,7 @@
#ifndef GB_DOCUMENT_MANAGER_H
#define GB_DOCUMENT_MANAGER_H
-#include <gtk/gtk.h>
+#include <gio/gio.h>
#include "gb-document.h"
@@ -39,7 +39,7 @@ typedef struct _GbDocumentManagerPrivate GbDocumentManagerPrivate;
struct _GbDocumentManager
{
- GtkListStore parent;
+ GObject parent;
/*< private >*/
GbDocumentManagerPrivate *priv;
@@ -47,18 +47,27 @@ struct _GbDocumentManager
struct _GbDocumentManagerClass
{
- GtkListStoreClass parent;
+ GObjectClass parent;
+
+ void (*document_added) (GbDocumentManager *manager,
+ GbDocument *document);
+ void (*document_removed) (GbDocumentManager *manager,
+ GbDocument *document);
+ void (*document_modified_changed) (GbDocumentManager *manager,
+ GbDocument *document);
};
-GType gb_document_manager_get_type (void);
-GbDocumentManager *gb_document_manager_new (void);
-GbDocumentManager *gb_document_manager_get_default (void);
-GbDocument *gb_document_manager_find_by_file (GbDocumentManager *manager,
- GFile *file);
-void gb_document_manager_add_document (GbDocumentManager *manager,
- GbDocument *document);
-gboolean gb_document_manager_remove_document (GbDocumentManager *manager,
- GbDocument *document);
+GType gb_document_manager_get_type (void);
+GbDocumentManager *gb_document_manager_new (void);
+GbDocumentManager *gb_document_manager_get_default (void);
+void gb_document_manager_add (GbDocumentManager *manager,
+ GbDocument *document);
+void gb_document_manager_remove (GbDocumentManager *manager,
+ GbDocument *document);
+GList *gb_document_manager_get_documents (GbDocumentManager *manager);
+guint gb_document_manager_get_count (GbDocumentManager *manager);
+GbDocument *gb_document_manager_find_with_file (GbDocumentManager *manager,
+ GFile *file);
G_END_DECLS
diff --git a/src/documents/gb-document-menu-button.c b/src/documents/gb-document-menu-button.c
new file mode 100644
index 0000000..24f6fc2
--- /dev/null
+++ b/src/documents/gb-document-menu-button.c
@@ -0,0 +1,650 @@
+/* gb-document-menu-button.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "document-menu-button"
+
+#include <glib/gi18n.h>
+
+#include "gb-document-menu-button.h"
+#include "gb-glib.h"
+
+struct _GbDocumentMenuButtonPrivate
+{
+ /* Objects owned by menu button */
+ GbDocumentManager *document_manager;
+ GHashTable *focus_time;
+
+ /* Weak references */
+ GbDocument *selected_document;
+
+ /* Unowned references */
+ GBinding *title_binding;
+ GBinding *modified_binding;
+
+ /* Widgets owned by Template */
+ GtkLabel *label;
+ GtkLabel *modified_label;
+ GtkListBox *list_box;
+ GtkPopover *popover;
+ GtkScrolledWindow *scrolled_window;
+ GtkSearchEntry *search_entry;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbDocumentMenuButton,
+ gb_document_menu_button,
+ GTK_TYPE_MENU_BUTTON)
+
+enum {
+ PROP_0,
+ PROP_DOCUMENT_MANAGER,
+ LAST_PROP
+};
+
+enum {
+ DOCUMENT_SELECTED,
+ LAST_SIGNAL
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+static guint gSignals [LAST_SIGNAL];
+
+GtkWidget *
+gb_document_menu_button_new (void)
+{
+ return g_object_new (GB_TYPE_DOCUMENT_MENU_BUTTON, NULL);
+}
+
+GbDocumentManager *
+gb_document_menu_button_get_document_manager (GbDocumentMenuButton *button)
+{
+ g_return_val_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button), NULL);
+
+ return button->priv->document_manager;
+}
+
+static GtkListBoxRow *
+gb_document_menu_button_create_row (GbDocumentMenuButton *button,
+ GbDocument *document)
+{
+ GtkListBoxRow *row;
+ GtkLabel *label;
+ GtkLabel *modified;
+ GtkBox *box;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button), NULL);
+ g_return_val_if_fail (GB_IS_DOCUMENT (document), NULL);
+
+ row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "visible", TRUE,
+ NULL);
+ g_object_set_data (G_OBJECT (row), "GB_DOCUMENT", document);
+
+ box = g_object_new (GTK_TYPE_BOX,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "visible", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (box));
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "margin-top", 3,
+ "margin-bottom", 3,
+ "margin-start", 6,
+ "margin-end", 3,
+ "hexpand", FALSE,
+ "visible", TRUE,
+ "valign", GTK_ALIGN_BASELINE,
+ "xalign", 0.0f,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (label));
+
+ modified = g_object_new (GTK_TYPE_LABEL,
+ "label", "•",
+ "visible", FALSE,
+ "valign", GTK_ALIGN_BASELINE,
+ "xalign", 0.0f,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (modified));
+
+ g_object_bind_property (document, "title", label, "label",
+ G_BINDING_SYNC_CREATE);
+
+ g_object_bind_property (document, "modified", modified, "visible",
+ G_BINDING_SYNC_CREATE);
+
+ return row;
+}
+
+static void
+gb_document_menu_button_update_sensitive (GbDocumentMenuButton *button)
+{
+ gboolean sensitive = FALSE;
+ guint count;
+
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+
+ if (button->priv->document_manager)
+ {
+ count = gb_document_manager_get_count (button->priv->document_manager);
+ sensitive = (count > 0);
+ }
+
+ gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive);
+}
+
+static void
+gb_document_menu_button_add_document (GbDocumentMenuButton *button,
+ GbDocument *document,
+ GbDocumentManager *document_manager)
+{
+ GtkListBoxRow *row;
+
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+ g_return_if_fail (GB_IS_DOCUMENT (document));
+ g_return_if_fail (GB_IS_DOCUMENT_MANAGER (document_manager));
+
+ row = gb_document_menu_button_create_row (button, document);
+
+ gtk_list_box_insert (button->priv->list_box, GTK_WIDGET (row), -1);
+
+ gb_document_menu_button_update_sensitive (button);
+}
+
+static void
+gb_document_menu_button_remove_document (GbDocumentMenuButton *button,
+ GbDocument *document,
+ GbDocumentManager *document_manager)
+{
+ GQuark qname;
+ GList *list;
+ GList *iter;
+
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+ g_return_if_fail (GB_IS_DOCUMENT (document));
+ g_return_if_fail (GB_IS_DOCUMENT_MANAGER (document_manager));
+
+ g_hash_table_remove (button->priv->focus_time, document);
+
+ list = gtk_container_get_children (GTK_CONTAINER (button->priv->list_box));
+ qname = g_quark_from_static_string ("GB_DOCUMENT");
+
+ for (iter = list; iter; iter = iter->next)
+ {
+ GtkListBoxRow *row = iter->data;
+ GbDocument *item;
+
+ if (!GTK_IS_LIST_BOX_ROW (iter->data))
+ continue;
+
+ item = g_object_get_qdata (G_OBJECT (row), qname);
+
+ if (item == document)
+ {
+ gtk_container_remove (GTK_CONTAINER (button->priv->list_box),
+ GTK_WIDGET (row));
+ break;
+ }
+ }
+
+ g_list_free (list);
+
+ gb_document_menu_button_update_sensitive (button);
+}
+
+static void
+gb_document_menu_button_connect (GbDocumentMenuButton *button,
+ GbDocumentManager *document_manager)
+{
+ GList *documents;
+ GList *iter;
+
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+ g_return_if_fail (GB_IS_DOCUMENT_MANAGER (document_manager));
+
+ g_signal_connect_object (document_manager,
+ "document-added",
+ G_CALLBACK (gb_document_menu_button_add_document),
+ button,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (document_manager,
+ "document-removed",
+ G_CALLBACK (gb_document_menu_button_remove_document),
+ button,
+ G_CONNECT_SWAPPED);
+
+ documents = gb_document_manager_get_documents (document_manager);
+
+ for (iter = documents; iter; iter = iter->next)
+ {
+ GbDocument *document;
+
+ document = GB_DOCUMENT (iter->data);
+ gb_document_menu_button_add_document (button, document, document_manager);
+ }
+
+ g_list_free (documents);
+}
+
+static void
+gb_document_menu_button_disconnect (GbDocumentMenuButton *button,
+ GbDocumentManager *document_manager)
+{
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+ g_return_if_fail (GB_IS_DOCUMENT_MANAGER (document_manager));
+
+ g_signal_handlers_disconnect_by_func (document_manager,
+ gb_document_menu_button_add_document,
+ button);
+
+ g_signal_handlers_disconnect_by_func (document_manager,
+ gb_document_menu_button_remove_document,
+ button);
+}
+
+void
+gb_document_menu_button_set_document_manager (GbDocumentMenuButton *button,
+ GbDocumentManager *document_manager)
+{
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+ g_return_if_fail (!document_manager || GB_IS_DOCUMENT_MANAGER (document_manager));
+
+ if (document_manager != button->priv->document_manager)
+ {
+ if (button->priv->document_manager)
+ {
+ gb_document_menu_button_disconnect (button, document_manager);
+ g_clear_object (&button->priv->document_manager);
+ }
+
+ if (document_manager)
+ {
+ button->priv->document_manager = g_object_ref (document_manager);
+ gb_document_menu_button_connect (button, document_manager);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (button),
+ gParamSpecs [PROP_DOCUMENT_MANAGER]);
+ }
+}
+
+void
+gb_document_menu_button_select_document (GbDocumentMenuButton *button,
+ GbDocument *document)
+{
+ GbDocumentMenuButtonPrivate *priv;
+ gsize value;
+
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+ g_return_if_fail (!document || GB_IS_DOCUMENT (document));
+
+ priv = button->priv;
+
+ if (priv->title_binding)
+ {
+ g_binding_unbind (priv->title_binding);
+ priv->title_binding = NULL;
+ }
+
+ if (priv->modified_binding)
+ {
+ g_binding_unbind (priv->modified_binding);
+ priv->modified_binding = NULL;
+ }
+
+ gb_clear_weak_pointer (&priv->selected_document);
+ gb_set_weak_pointer (document, &priv->selected_document);
+
+ if (document)
+ {
+ priv->title_binding =
+ g_object_bind_property (document, "title", priv->label, "label",
+ G_BINDING_SYNC_CREATE);
+
+ priv->modified_binding =
+ g_object_bind_property (document, "modified",
+ priv->modified_label, "visible",
+ G_BINDING_SYNC_CREATE);
+
+ value = g_get_monotonic_time () / (G_USEC_PER_SEC / 10);
+ g_hash_table_replace (priv->focus_time, document,
+ GSIZE_TO_POINTER (value));
+
+ g_signal_emit (button, gSignals [DOCUMENT_SELECTED], 0, document);
+ }
+ else
+ {
+ gtk_label_set_label (priv->label, NULL);
+ gtk_widget_set_visible (GTK_WIDGET (priv->modified_label), FALSE);
+ }
+
+ gb_document_menu_button_update_sensitive (button);
+
+ gtk_list_box_invalidate_sort (priv->list_box);
+}
+
+static gboolean
+gb_document_menu_button_do_hide (gpointer data)
+{
+ GbDocumentMenuButton *button = data;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button), FALSE);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
+ g_object_unref (button);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gb_document_menu_button_delayed_hide (GbDocumentMenuButton *button)
+{
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+
+ g_timeout_add (200,
+ gb_document_menu_button_do_hide,
+ g_object_ref (button));
+}
+
+static void
+gb_document_menu_button_row_activated (GbDocumentMenuButton *button,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
+{
+ GbDocument *document;
+
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+ g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
+ g_return_if_fail (GTK_IS_LIST_BOX (list_box));
+
+ document = g_object_get_data (G_OBJECT (row), "GB_DOCUMENT");
+
+ if (GB_IS_DOCUMENT (document))
+ gb_document_menu_button_select_document (button, document);
+
+ gb_document_menu_button_delayed_hide (button);
+}
+
+static void
+gb_document_menu_button_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
+
+ if (before)
+ {
+ GtkWidget *widget;
+
+ widget = g_object_new (GTK_TYPE_SEPARATOR,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "visible", TRUE,
+ NULL);
+ gtk_list_box_row_set_header (row, widget);
+ }
+}
+
+static gint
+gb_document_menu_button_sort_func (GtkListBoxRow *row1,
+ GtkListBoxRow *row2,
+ gpointer user_data)
+{
+
+ GbDocumentMenuButton *button = user_data;
+ GbDocument *doc1 = g_object_get_data (G_OBJECT (row1), "GB_DOCUMENT");
+ GbDocument *doc2 = g_object_get_data (G_OBJECT (row2), "GB_DOCUMENT");
+ gpointer value1 = g_hash_table_lookup (button->priv->focus_time, doc1);
+ gpointer value2 = g_hash_table_lookup (button->priv->focus_time, doc2);
+
+ if (!value1 && !value2)
+ return g_strcmp0 (gb_document_get_title (doc1),
+ gb_document_get_title (doc2));
+
+ if (!value1)
+ return 1;
+
+ if (!value2)
+ return -1;
+
+ return GPOINTER_TO_INT (value2 - value1);
+}
+
+static gboolean
+gb_document_menu_button_filter_func (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ GbDocumentMenuButton *button = user_data;
+ GbDocument *document;
+ const gchar *title;
+ const gchar *str;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button), FALSE);
+
+ document = g_object_get_data (G_OBJECT (row), "GB_DOCUMENT");
+ title = gb_document_get_title (document);
+ str = gtk_entry_get_text (GTK_ENTRY (button->priv->search_entry));
+
+ /*
+ * TODO: Replace this with a proper fuzzy search with scoring and
+ * highlighting. Score should include the distance between
+ * matched characters.
+ */
+ for (; title && *str; str = g_utf8_next_char (str))
+ {
+ gunichar c = g_utf8_get_char (str);
+
+ if (g_unichar_isspace (c))
+ continue;
+
+ title = g_utf8_strchr (title, -1, c);
+
+ if (title)
+ title = g_utf8_next_char (title);
+ }
+
+ return title && ((str == NULL) || (*str == '\0'));
+}
+
+static void
+gb_document_menu_button_search_changed (GbDocumentMenuButton *button,
+ GtkEditable *editable)
+{
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+
+ gtk_list_box_invalidate_filter (button->priv->list_box);
+}
+
+static void
+gb_document_menu_button_search_activate (GbDocumentMenuButton *button,
+ GtkEditable *editable)
+{
+ GtkListBoxRow *row;
+
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+
+ row = gtk_list_box_get_row_at_y (button->priv->list_box, 1);
+
+ if (row)
+ {
+ GbDocument *document;
+
+ document = g_object_get_data (G_OBJECT (row), "GB_DOCUMENT");
+ gb_document_menu_button_select_document (button, document);
+ }
+}
+
+static void
+gb_document_menu_button_clicked (GtkButton *button)
+{
+ GbDocumentMenuButton *self = (GbDocumentMenuButton *)button;
+
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+
+ gtk_entry_set_text (GTK_ENTRY (self->priv->search_entry), "");
+
+ GTK_BUTTON_CLASS (gb_document_menu_button_parent_class)->clicked (button);
+}
+
+static void
+gb_document_menu_button_constructed (GObject *object)
+{
+ GbDocumentMenuButton *button = (GbDocumentMenuButton *)object;
+
+ G_OBJECT_CLASS (gb_document_menu_button_parent_class)->constructed (object);
+
+ g_signal_connect_object (button->priv->list_box,
+ "row-activated",
+ G_CALLBACK (gb_document_menu_button_row_activated),
+ button,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (button->priv->search_entry,
+ "activate",
+ G_CALLBACK (gb_document_menu_button_search_activate),
+ button,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (button->priv->search_entry,
+ "changed",
+ G_CALLBACK (gb_document_menu_button_search_changed),
+ button,
+ G_CONNECT_SWAPPED);
+
+ gtk_list_box_set_header_func (button->priv->list_box,
+ gb_document_menu_button_header_func,
+ button,
+ NULL);
+
+ gtk_list_box_set_sort_func (button->priv->list_box,
+ gb_document_menu_button_sort_func,
+ button,
+ NULL);
+
+ gtk_list_box_set_filter_func (button->priv->list_box,
+ gb_document_menu_button_filter_func,
+ button,
+ NULL);
+}
+
+static void
+gb_document_menu_button_finalize (GObject *object)
+{
+ GbDocumentMenuButtonPrivate *priv = GB_DOCUMENT_MENU_BUTTON (object)->priv;
+
+ g_clear_object (&priv->document_manager);
+ g_clear_pointer (&priv->focus_time, g_hash_table_unref);
+
+ G_OBJECT_CLASS (gb_document_menu_button_parent_class)->finalize (object);
+}
+
+static void
+gb_document_menu_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbDocumentMenuButton *self = GB_DOCUMENT_MENU_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT_MANAGER:
+ g_value_set_object (value,
+ gb_document_menu_button_get_document_manager (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_document_menu_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbDocumentMenuButton *self = GB_DOCUMENT_MENU_BUTTON (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT_MANAGER:
+ gb_document_menu_button_set_document_manager (self,
+ g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_document_menu_button_class_init (GbDocumentMenuButtonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass);
+
+ object_class->constructed = gb_document_menu_button_constructed;
+ object_class->finalize = gb_document_menu_button_finalize;
+ object_class->get_property = gb_document_menu_button_get_property;
+ object_class->set_property = gb_document_menu_button_set_property;
+
+ button_class->clicked = gb_document_menu_button_clicked;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/builder/ui/gb-document-menu-button.ui");
+ gtk_widget_class_bind_template_child_private (widget_class, GbDocumentMenuButton, label);
+ gtk_widget_class_bind_template_child_private (widget_class, GbDocumentMenuButton, list_box);
+ gtk_widget_class_bind_template_child_private (widget_class, GbDocumentMenuButton, modified_label);
+ gtk_widget_class_bind_template_child_private (widget_class, GbDocumentMenuButton, popover);
+ gtk_widget_class_bind_template_child_private (widget_class, GbDocumentMenuButton, scrolled_window);
+ gtk_widget_class_bind_template_child_private (widget_class, GbDocumentMenuButton, search_entry);
+
+ gParamSpecs [PROP_DOCUMENT_MANAGER] =
+ g_param_spec_object ("document-manager",
+ _("Document Manager"),
+ _("The document manager for the button."),
+ GB_TYPE_DOCUMENT_MANAGER,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_DOCUMENT_MANAGER,
+ gParamSpecs [PROP_DOCUMENT_MANAGER]);
+
+ gSignals [DOCUMENT_SELECTED] =
+ g_signal_new ("document-selected",
+ GB_TYPE_DOCUMENT_MENU_BUTTON,
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GbDocumentMenuButtonClass,
+ document_selected),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ GB_TYPE_DOCUMENT);
+}
+
+static void
+gb_document_menu_button_init (GbDocumentMenuButton *self)
+{
+ self->priv = gb_document_menu_button_get_instance_private (self);
+
+ self->priv->focus_time = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gb_document_menu_button_update_sensitive (self);
+}
diff --git a/src/documents/gb-document-menu-button.h b/src/documents/gb-document-menu-button.h
new file mode 100644
index 0000000..116c346
--- /dev/null
+++ b/src/documents/gb-document-menu-button.h
@@ -0,0 +1,67 @@
+/* gb-document-menu-button.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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_DOCUMENT_MENU_BUTTON_H
+#define GB_DOCUMENT_MENU_BUTTON_H
+
+#include <gtk/gtk.h>
+
+#include "gb-document.h"
+#include "gb-document-manager.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_DOCUMENT_MENU_BUTTON (gb_document_menu_button_get_type())
+#define GB_DOCUMENT_MENU_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),
GB_TYPE_DOCUMENT_MENU_BUTTON, GbDocumentMenuButton))
+#define GB_DOCUMENT_MENU_BUTTON_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),
GB_TYPE_DOCUMENT_MENU_BUTTON, GbDocumentMenuButton const))
+#define GB_DOCUMENT_MENU_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),
GB_TYPE_DOCUMENT_MENU_BUTTON, GbDocumentMenuButtonClass))
+#define GB_IS_DOCUMENT_MENU_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),
GB_TYPE_DOCUMENT_MENU_BUTTON))
+#define GB_IS_DOCUMENT_MENU_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),
GB_TYPE_DOCUMENT_MENU_BUTTON))
+#define GB_DOCUMENT_MENU_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),
GB_TYPE_DOCUMENT_MENU_BUTTON, GbDocumentMenuButtonClass))
+
+typedef struct _GbDocumentMenuButton GbDocumentMenuButton;
+typedef struct _GbDocumentMenuButtonClass GbDocumentMenuButtonClass;
+typedef struct _GbDocumentMenuButtonPrivate GbDocumentMenuButtonPrivate;
+
+struct _GbDocumentMenuButton
+{
+ GtkMenuButton parent;
+
+ /*< private >*/
+ GbDocumentMenuButtonPrivate *priv;
+};
+
+struct _GbDocumentMenuButtonClass
+{
+ GtkMenuButtonClass parent;
+
+ void (*document_selected) (GbDocumentMenuButton *button,
+ GbDocument *document);
+};
+
+GType gb_document_menu_button_get_type (void);
+GtkWidget *gb_document_menu_button_new (void);
+GbDocumentManager *gb_document_menu_button_get_document_manager (GbDocumentMenuButton *button);
+void gb_document_menu_button_set_document_manager (GbDocumentMenuButton *button,
+ GbDocumentManager *document_manager);
+void gb_document_menu_button_select_document (GbDocumentMenuButton *button,
+ GbDocument *document);
+
+G_END_DECLS
+
+#endif /* GB_DOCUMENT_MENU_BUTTON_H */
diff --git a/src/documents/gb-document-split.c b/src/documents/gb-document-split.c
new file mode 100644
index 0000000..64281d5
--- /dev/null
+++ b/src/documents/gb-document-split.c
@@ -0,0 +1,37 @@
+/* gb-document-split.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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 "gb-document-split.h"
+
+GType
+gb_document_split_get_type (void)
+{
+ static GType type_id;
+
+ const GEnumValue values[] = {
+ { GB_DOCUMENT_SPLIT_NONE, "GB_DOCUMENT_SPLIT_NONE", "NONE" },
+ { GB_DOCUMENT_SPLIT_LEFT, "GB_DOCUMENT_SPLIT_LEFT", "LEFT" },
+ { GB_DOCUMENT_SPLIT_LEFT, "GB_DOCUMENT_SPLIT_RIGHT", "RIGHT" },
+ { 0 }
+ };
+
+ if (!type_id)
+ type_id = g_enum_register_static ("GbDocumentSplit", values);
+
+ return type_id;
+}
diff --git a/src/documents/gb-document-split.h b/src/documents/gb-document-split.h
new file mode 100644
index 0000000..7ee3f68
--- /dev/null
+++ b/src/documents/gb-document-split.h
@@ -0,0 +1,38 @@
+/* gb-document-split.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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_DOCUMENT_SPLIT_H
+#define GB_DOCUMENT_SPLIT_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_DOCUMENT_SPLIT (gb_document_split_get_type())
+
+typedef enum {
+ GB_DOCUMENT_SPLIT_NONE = 0,
+ GB_DOCUMENT_SPLIT_RIGHT = 1,
+ GB_DOCUMENT_SPLIT_LEFT = 2,
+} GbDocumentSplit;
+
+GType gb_document_split_get_type (void);
+
+G_END_DECLS
+
+#endif /* GB_DOCUMENT_SPLIT_H */
diff --git a/src/documents/gb-document-stack.c b/src/documents/gb-document-stack.c
new file mode 100644
index 0000000..8223ea1
--- /dev/null
+++ b/src/documents/gb-document-stack.c
@@ -0,0 +1,667 @@
+/* gb-document-stack.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "document-stack"
+
+#include <glib/gi18n.h>
+
+#include "gb-document-menu-button.h"
+#include "gb-document-stack.h"
+#include "gb-glib.h"
+
+struct _GbDocumentStackPrivate
+{
+ /* Objects ownen by GbDocumentStack */
+ GbDocumentManager *document_manager;
+
+ /* Weak references */
+ GbDocumentView *active_view;
+
+ /* GtkWidgets owned by GtkWidgetClass template */
+ GbDocumentMenuButton *document_button;
+ GtkStack *controls;
+ GtkButton *close;
+ GtkStack *stack;
+ GtkMenuButton *stack_menu;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GbDocumentStack, gb_document_stack, GTK_TYPE_BOX)
+
+enum {
+ PROP_0,
+ PROP_ACTIVE_VIEW,
+ PROP_DOCUMENT_MANAGER,
+ LAST_PROP
+};
+
+enum {
+ CREATE_VIEW,
+ EMPTY,
+ LAST_SIGNAL
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+static guint gSignals [LAST_SIGNAL];
+
+GtkWidget *
+gb_document_stack_new (void)
+{
+ return g_object_new (GB_TYPE_DOCUMENT_STACK, NULL);
+}
+
+static void
+gb_document_stack_remove_view (GbDocumentStack *stack,
+ GbDocumentView *view)
+{
+ GbDocument *document = NULL;
+ GtkWidget *visible_child;
+ GtkWidget *controls;
+ gboolean visible;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+ g_return_if_fail (GB_IS_DOCUMENT_VIEW (view));
+
+ /* Release our weak pointer */
+ if (view == stack->priv->active_view)
+ gb_clear_weak_pointer (&stack->priv->active_view);
+
+ /* Notify the document view it is being closed */
+ gb_document_view_close (view);
+
+ /* Remove the document view and its controls */
+ controls = gb_document_view_get_controls (view);
+ if (controls)
+ gtk_container_remove (GTK_CONTAINER (stack->priv->controls), controls);
+ gtk_container_remove (GTK_CONTAINER (stack->priv->stack), GTK_WIDGET (view));
+
+ /*
+ * Set the visible child to the first document view. We probably want to
+ * be more intelligent about this in the future. visible_child may be
+ * NULL, which will clear the title string in the menu button.
+ */
+ visible_child = gtk_stack_get_visible_child (stack->priv->stack);
+ if (visible_child)
+ document = gb_document_view_get_document (GB_DOCUMENT_VIEW (visible_child));
+ gb_document_menu_button_select_document (stack->priv->document_button,
+ document);
+
+ /* Only show close and stack menu if we have children */
+ visible = (visible_child != NULL);
+ gtk_widget_set_visible (GTK_WIDGET (stack->priv->close), visible);
+ gtk_widget_set_visible (GTK_WIDGET (stack->priv->stack_menu), visible);
+
+ if (!visible_child)
+ g_signal_emit (stack, gSignals [EMPTY], 0);
+}
+
+/**
+ * gb_document_stack_get_document_manager:
+ *
+ * Fetches the document manager for the stack.
+ *
+ * Returns: (transfer none): A #GbDocumentManager
+ */
+GbDocumentManager *
+gb_document_stack_get_document_manager (GbDocumentStack *stack)
+{
+ g_return_val_if_fail (GB_IS_DOCUMENT_STACK (stack), NULL);
+
+ return stack->priv->document_manager;
+}
+
+/**
+ * gb_document_stack_set_document_manager:
+ * @document_manager: A #GbDocumentManager.
+ *
+ * Sets the document manager for the stack. All existing views will be removed
+ * if the #GbDocumentManager is different than the current document manager.
+ */
+void
+gb_document_stack_set_document_manager (GbDocumentStack *stack,
+ GbDocumentManager *document_manager)
+{
+ GbDocumentStackPrivate *priv;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+ g_return_if_fail (!document_manager ||
+ GB_IS_DOCUMENT_MANAGER (document_manager));
+
+ priv = stack->priv;
+
+ if (document_manager != priv->document_manager)
+ {
+ if (priv->document_manager)
+ {
+ GList *list;
+ GList *iter;
+
+ /* Release our document manager */
+ g_clear_object (&priv->document_manager);
+ gb_document_menu_button_set_document_manager (priv->document_button,
+ NULL);
+
+ /* Remove any views lingering in here */
+ list = gtk_container_get_children (GTK_CONTAINER (priv->stack));
+ for (iter = list; iter; iter = iter->next)
+ gb_document_stack_remove_view (stack, iter->data);
+ g_list_free (list);
+ }
+
+ if (document_manager)
+ {
+ priv->document_manager = g_object_ref (document_manager);
+ gb_document_menu_button_set_document_manager (priv->document_button,
+ document_manager);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (stack),
+ gParamSpecs [PROP_DOCUMENT_MANAGER]);
+ }
+}
+
+/**
+ * gb_document_stack_get_active_view:
+ *
+ * Fetches the active view for the document stack.
+ *
+ * Returns: (transfer none): A #GbDocumentView or %NULL.
+ */
+GbDocumentView *
+gb_document_stack_get_active_view (GbDocumentStack *stack)
+{
+ GtkWidget *child;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_STACK (stack), NULL);
+
+ child = gtk_stack_get_visible_child (stack->priv->stack);
+ if (GB_IS_DOCUMENT_VIEW (child))
+ return GB_DOCUMENT_VIEW (child);
+
+ return NULL;
+}
+
+void
+gb_document_stack_set_active_view (GbDocumentStack *stack,
+ GbDocumentView *active_view)
+{
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+ g_return_if_fail (!active_view || GB_IS_DOCUMENT_VIEW (active_view));
+
+ if (active_view != stack->priv->active_view)
+ {
+ gb_clear_weak_pointer (&stack->priv->active_view);
+ gb_set_weak_pointer (active_view, &stack->priv->active_view);
+
+ if (active_view)
+ {
+ GtkWidget *controls;
+
+ gtk_stack_set_visible_child (stack->priv->stack,
+ GTK_WIDGET (active_view));
+
+ controls = gb_document_view_get_controls (active_view);
+
+ if (controls)
+ gtk_stack_set_visible_child (stack->priv->controls, controls);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (stack),
+ gParamSpecs [PROP_ACTIVE_VIEW]);
+ }
+}
+
+/**
+ * gb_document_stack_find_with_document:
+ *
+ * Finds the first #GbDocumentView containing @document.
+ *
+ * Returns: (transfer none): A #GbDocumentView or %NULL.
+ */
+GtkWidget *
+gb_document_stack_find_with_document (GbDocumentStack *stack,
+ GbDocument *document)
+{
+ GbDocumentView *ret = NULL;
+ GList *list;
+ GList *iter;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_STACK (stack), NULL);
+ g_return_val_if_fail (GB_IS_DOCUMENT (document), NULL);
+
+ list = gtk_container_get_children (GTK_CONTAINER (stack->priv->stack));
+
+ for (iter = list; iter; iter = iter->next)
+ {
+ GbDocument *value;
+
+ value = gb_document_view_get_document (GB_DOCUMENT_VIEW (iter->data));
+ if (!value)
+ continue;
+
+ if (document == value)
+ {
+ ret = GB_DOCUMENT_VIEW (iter->data);
+ break;
+ }
+ }
+
+ g_list_free (list);
+
+ return GTK_WIDGET (ret);
+}
+
+/**
+ * gb_document_stack_find_with_type:
+ *
+ * Finds the first #GbDocumentView of type @type_id. The view may be a subclass
+ * of type #GType provided.
+ *
+ * Returns: (transfer none): A #GbDocumentView or %NULL.
+ */
+GtkWidget *
+gb_document_stack_find_with_type (GbDocumentStack *stack,
+ GType type_id)
+{
+ GbDocumentView *ret = NULL;
+ GList *list;
+ GList *iter;
+
+ g_return_val_if_fail (GB_IS_DOCUMENT_STACK (stack), NULL);
+ g_return_val_if_fail (g_type_is_a (type_id, GB_TYPE_DOCUMENT_VIEW), NULL);
+
+ list = gtk_container_get_children (GTK_CONTAINER (stack->priv->stack));
+
+ for (iter = list; iter; iter = iter->next)
+ {
+ GbDocument *value;
+
+ value = gb_document_view_get_document (GB_DOCUMENT_VIEW (iter->data));
+ if (!value)
+ continue;
+
+ if (g_type_is_a (G_TYPE_FROM_INSTANCE (value), type_id))
+ {
+ ret = GB_DOCUMENT_VIEW (iter->data);
+ break;
+ }
+ }
+
+ g_list_free (list);
+
+ return GTK_WIDGET (ret);
+}
+
+void
+gb_document_stack_focus_document (GbDocumentStack *stack,
+ GbDocument *document)
+{
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+ g_return_if_fail (GB_IS_DOCUMENT (document));
+
+ gb_document_menu_button_select_document (stack->priv->document_button,
+ document);
+}
+
+static void
+gb_document_stack_document_selected (GbDocumentStack *stack,
+ GbDocument *document,
+ GbDocumentMenuButton *button)
+{
+ GtkWidget *view;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+ g_return_if_fail (GB_IS_DOCUMENT (document));
+ g_return_if_fail (GB_IS_DOCUMENT_MENU_BUTTON (button));
+
+ view = gb_document_stack_find_with_document (stack, document);
+
+ if (!view)
+ {
+ GtkWidget *controls;
+
+ view = gb_document_create_view (document);
+
+ if (!view)
+ {
+ g_warning ("Failed to create view");
+ return;
+ }
+
+ gtk_container_add (GTK_CONTAINER (stack->priv->stack), view);
+
+ controls = gb_document_view_get_controls (GB_DOCUMENT_VIEW (view));
+
+ if (controls)
+ gtk_container_add (GTK_CONTAINER (stack->priv->controls), controls);
+ }
+
+ gtk_widget_set_visible (GTK_WIDGET (stack->priv->close), TRUE);
+ gtk_widget_set_visible (GTK_WIDGET (stack->priv->stack_menu), TRUE);
+ gb_document_stack_set_active_view (stack, GB_DOCUMENT_VIEW (view));
+ gtk_widget_grab_focus (view);
+
+ return;
+}
+
+static void
+gb_document_stack_close_clicked (GbDocumentStack *stack,
+ GtkButton *button)
+{
+ GbDocumentStackPrivate *priv;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+ g_return_if_fail (GTK_IS_BUTTON (button));
+
+ priv = stack->priv;
+
+ if (priv->active_view)
+ gb_document_stack_remove_view (stack, priv->active_view);
+}
+
+static void
+gb_document_stack_grab_focus (GtkWidget *widget)
+{
+ GbDocumentStack *stack = (GbDocumentStack *)widget;
+ GbDocumentView *active_view;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+
+ active_view = gb_document_stack_get_active_view (stack);
+ if (active_view)
+ gtk_widget_grab_focus (GTK_WIDGET (active_view));
+}
+
+static void
+gb_document_stack_constructed (GObject *object)
+{
+ GbDocumentStack *stack = (GbDocumentStack *)object;
+ GApplication *app;
+
+ G_OBJECT_CLASS (gb_document_stack_parent_class)->constructed (object);
+
+ app = g_application_get_default ();
+
+ if (GTK_IS_APPLICATION (app))
+ {
+ GMenu *menu;
+
+ menu = gtk_application_get_menu_by_id (GTK_APPLICATION (app),
+ "gb-document-stack-menu");
+
+ if (menu)
+ gtk_menu_button_set_menu_model (stack->priv->stack_menu,
+ G_MENU_MODEL (menu));
+ }
+
+ g_signal_connect_object (stack->priv->document_button,
+ "document-selected",
+ G_CALLBACK (gb_document_stack_document_selected),
+ stack,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (stack->priv->close,
+ "clicked",
+ G_CALLBACK (gb_document_stack_close_clicked),
+ stack,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+gb_document_stack_move_document_left (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GbDocumentStack *stack = user_data;
+ GbDocumentView *view;
+ GbDocument *document;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+
+ view = gb_document_stack_get_active_view (stack);
+ if (!view)
+ return;
+
+ document = gb_document_view_get_document (view);
+ if (!document)
+ return;
+
+ g_signal_emit (stack, gSignals [CREATE_VIEW], 0, document,
+ GB_DOCUMENT_SPLIT_LEFT);
+
+ gb_document_stack_remove_view (stack, view);
+}
+
+static void
+gb_document_stack_move_document_right (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GbDocumentStack *stack = user_data;
+ GbDocumentView *view;
+ GbDocument *document;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+
+ view = gb_document_stack_get_active_view (stack);
+ if (!view)
+ return;
+
+ document = gb_document_view_get_document (view);
+ if (!document)
+ return;
+
+ g_signal_emit (stack, gSignals [CREATE_VIEW], 0, document,
+ GB_DOCUMENT_SPLIT_RIGHT);
+
+ gb_document_stack_remove_view (stack, view);
+}
+
+static void
+gb_document_stack_split_document_left (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GbDocumentStack *stack = user_data;
+ GbDocumentView *view;
+ GbDocument *document;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+
+ view = gb_document_stack_get_active_view (stack);
+ if (!view)
+ return;
+
+ document = gb_document_view_get_document (view);
+ if (!document)
+ return;
+
+ g_signal_emit (stack, gSignals [CREATE_VIEW], 0, document,
+ GB_DOCUMENT_SPLIT_LEFT);
+}
+
+static void
+gb_document_stack_split_document_right (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ GbDocumentStack *stack = user_data;
+ GbDocumentView *view;
+ GbDocument *document;
+
+ g_return_if_fail (GB_IS_DOCUMENT_STACK (stack));
+
+ view = gb_document_stack_get_active_view (stack);
+ if (!view)
+ return;
+
+ document = gb_document_view_get_document (view);
+ if (!document)
+ return;
+
+ g_signal_emit (stack, gSignals [CREATE_VIEW], 0, document,
+ GB_DOCUMENT_SPLIT_RIGHT);
+}
+
+static void
+gb_document_stack_finalize (GObject *object)
+{
+ GbDocumentStackPrivate *priv = GB_DOCUMENT_STACK (object)->priv;
+
+ gb_clear_weak_pointer (&priv->active_view);
+ g_clear_object (&priv->document_manager);
+
+ G_OBJECT_CLASS (gb_document_stack_parent_class)->finalize (object);
+}
+
+static void
+gb_document_stack_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbDocumentStack *self = GB_DOCUMENT_STACK (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE_VIEW:
+ g_value_set_object (value, gb_document_stack_get_active_view (self));
+ break;
+
+ case PROP_DOCUMENT_MANAGER:
+ g_value_set_object (value, gb_document_stack_get_document_manager (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_document_stack_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbDocumentStack *self = GB_DOCUMENT_STACK (object);
+
+ switch (prop_id)
+ {
+ case PROP_ACTIVE_VIEW:
+ gb_document_stack_set_active_view (self, g_value_get_object (value));
+ break;
+
+ case PROP_DOCUMENT_MANAGER:
+ gb_document_stack_set_document_manager (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_document_stack_class_init (GbDocumentStackClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = gb_document_stack_constructed;
+ object_class->finalize = gb_document_stack_finalize;
+ object_class->get_property = gb_document_stack_get_property;
+ object_class->set_property = gb_document_stack_set_property;
+
+ widget_class->grab_focus = gb_document_stack_grab_focus;
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/gb-document-stack.ui");
+ gtk_widget_class_bind_template_child_internal_private (widget_class, GbDocumentStack, close);
+ gtk_widget_class_bind_template_child_internal_private (widget_class, GbDocumentStack, stack);
+ gtk_widget_class_bind_template_child_internal_private (widget_class, GbDocumentStack, stack_menu);
+ gtk_widget_class_bind_template_child_internal_private (widget_class, GbDocumentStack, controls);
+ gtk_widget_class_bind_template_child_internal_private (widget_class, GbDocumentStack, document_button);
+
+ gParamSpecs [PROP_ACTIVE_VIEW] =
+ g_param_spec_object ("active-view",
+ _("Active View"),
+ _("The active view within the stack."),
+ GB_TYPE_DOCUMENT_VIEW,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_ACTIVE_VIEW,
+ gParamSpecs [PROP_ACTIVE_VIEW]);
+
+ gParamSpecs [PROP_DOCUMENT_MANAGER] =
+ g_param_spec_object ("document-manager",
+ _("Document Manager"),
+ _("The document manager for the stack."),
+ GB_TYPE_DOCUMENT_MANAGER,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_DOCUMENT_MANAGER,
+ gParamSpecs [PROP_DOCUMENT_MANAGER]);
+
+ gSignals [CREATE_VIEW] =
+ g_signal_new ("create-view",
+ GB_TYPE_DOCUMENT_STACK,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GbDocumentStackClass, create_view),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 2,
+ GB_TYPE_DOCUMENT,
+ GB_TYPE_DOCUMENT_SPLIT);
+
+ /**
+ * GbDocumentStack::empty:
+ *
+ * This signal is emitted when the last document view has been closed.
+ * The parent grid may want to destroy the grid when this occurs.
+ */
+ gSignals [EMPTY] =
+ g_signal_new ("empty",
+ GB_TYPE_DOCUMENT_STACK,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GbDocumentStackClass, empty),
+ NULL, NULL,
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE,
+ 0);
+
+ g_type_ensure (GB_TYPE_DOCUMENT_MENU_BUTTON);
+}
+
+static void
+gb_document_stack_init (GbDocumentStack *self)
+{
+ const GActionEntry entries[] = {
+ { "move-document-left", gb_document_stack_move_document_left },
+ { "move-document-right", gb_document_stack_move_document_right },
+ { "split-document-left", gb_document_stack_split_document_left },
+ { "split-document-right", gb_document_stack_split_document_right },
+ };
+ GSimpleActionGroup *actions;
+
+ self->priv = gb_document_stack_get_instance_private (self);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ actions = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (actions), entries,
+ G_N_ELEMENTS (entries), self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self), "stack",
+ G_ACTION_GROUP (actions));
+ g_clear_object (&actions);
+}
diff --git a/src/documents/gb-document-stack.h b/src/documents/gb-document-stack.h
new file mode 100644
index 0000000..420557f
--- /dev/null
+++ b/src/documents/gb-document-stack.h
@@ -0,0 +1,77 @@
+/* gb-document-stack.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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_DOCUMENT_STACK_H
+#define GB_DOCUMENT_STACK_H
+
+#include <gtk/gtk.h>
+
+#include "gb-document-manager.h"
+#include "gb-document-split.h"
+#include "gb-document-view.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_DOCUMENT_STACK (gb_document_stack_get_type())
+#define GB_DOCUMENT_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_DOCUMENT_STACK,
GbDocumentStack))
+#define GB_DOCUMENT_STACK_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_DOCUMENT_STACK,
GbDocumentStack const))
+#define GB_DOCUMENT_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GB_TYPE_DOCUMENT_STACK,
GbDocumentStackClass))
+#define GB_IS_DOCUMENT_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_DOCUMENT_STACK))
+#define GB_IS_DOCUMENT_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GB_TYPE_DOCUMENT_STACK))
+#define GB_DOCUMENT_STACK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GB_TYPE_DOCUMENT_STACK,
GbDocumentStackClass))
+
+typedef struct _GbDocumentStack GbDocumentStack;
+typedef struct _GbDocumentStackClass GbDocumentStackClass;
+typedef struct _GbDocumentStackPrivate GbDocumentStackPrivate;
+
+struct _GbDocumentStack
+{
+ GtkBox parent;
+
+ /*< private >*/
+ GbDocumentStackPrivate *priv;
+};
+
+struct _GbDocumentStackClass
+{
+ GtkBoxClass parent;
+
+ void (*create_view) (GbDocumentStack *stack,
+ GbDocument *document,
+ GbDocumentSplit split);
+ void (*empty) (GbDocumentStack *stack);
+};
+
+GType gb_document_stack_get_type (void);
+GtkWidget *gb_document_stack_new (void);
+void gb_document_stack_focus_document (GbDocumentStack *stack,
+ GbDocument *document);
+GbDocumentManager *gb_document_stack_get_document_manager (GbDocumentStack *stack);
+void gb_document_stack_set_document_manager (GbDocumentStack *stack,
+ GbDocumentManager *manager);
+GbDocumentView *gb_document_stack_get_active_view (GbDocumentStack *stack);
+void gb_document_stack_set_active_view (GbDocumentStack *stack,
+ GbDocumentView *view);
+GtkWidget *gb_document_stack_find_with_document (GbDocumentStack *stack,
+ GbDocument *document);
+GtkWidget *gb_document_stack_find_with_type (GbDocumentStack *stack,
+ GType type_id);
+
+G_END_DECLS
+
+#endif /* GB_DOCUMENT_STACK_H */
diff --git a/src/documents/gb-document-view.c b/src/documents/gb-document-view.c
new file mode 100644
index 0000000..2c6f92b
--- /dev/null
+++ b/src/documents/gb-document-view.c
@@ -0,0 +1,202 @@
+/* gb-document-view.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#define G_LOG_DOMAIN "document-view"
+
+#include <glib/gi18n.h>
+
+#include "gb-document-view.h"
+
+struct _GbDocumentViewPrivate
+{
+ GtkBox *controls;
+};
+
+static void buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GbDocumentView,
+ gb_document_view,
+ GTK_TYPE_BOX,
+ G_ADD_PRIVATE (GbDocumentView)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+ buildable_init))
+
+enum {
+ PROP_0,
+ PROP_CONTROLS,
+ PROP_DOCUMENT,
+ LAST_PROP
+};
+
+enum {
+ CLOSE,
+ LAST_SIGNAL
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+static guint gSignals [LAST_SIGNAL];
+
+/**
+ * gb_document_view_get_controls:
+ *
+ * This returns a #GtkBox that can be used to place controls that should be
+ * displayed at the top of the document stack. It is available in the Gtk
+ * template as an internal child named "controls".
+ *
+ * Returns: (transfer none): A #GtkBox
+ */
+GtkWidget *
+gb_document_view_get_controls (GbDocumentView *view)
+{
+ g_return_val_if_fail (GB_IS_DOCUMENT_VIEW (view), NULL);
+
+ return GTK_WIDGET (view->priv->controls);
+}
+
+/**
+ * gb_document_view_get_document:
+ *
+ * Retrieves the #GbDocument being viewed.
+ *
+ * Returns: (transfer none): A #GbDocument.
+ */
+GbDocument *
+gb_document_view_get_document (GbDocumentView *view)
+{
+ g_return_val_if_fail (GB_IS_DOCUMENT_VIEW (view), NULL);
+
+ if (GB_DOCUMENT_VIEW_GET_CLASS (view)->get_document)
+ return GB_DOCUMENT_VIEW_GET_CLASS (view)->get_document (view);
+
+ g_warning ("%s() does not implement get_document() vfunc.",
+ g_type_name (G_TYPE_FROM_INSTANCE (view)));
+
+ return NULL;
+}
+
+void
+gb_document_view_close (GbDocumentView *view)
+{
+ g_return_if_fail (GB_IS_DOCUMENT_VIEW (view));
+
+ g_signal_emit (view, gSignals [CLOSE], 0);
+}
+
+static GObject *
+gb_document_view_get_internal_child (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ const gchar *childname)
+{
+ GbDocumentView *view = (GbDocumentView *)buildable;
+
+ if (g_strcmp0 (childname, "controls") == 0)
+ return G_OBJECT (view->priv->controls);
+
+ return NULL;
+}
+
+static void
+gb_document_view_destroy (GtkWidget *widget)
+{
+ GbDocumentViewPrivate *priv = GB_DOCUMENT_VIEW (widget)->priv;
+
+ g_clear_object (&priv->controls);
+
+ GTK_WIDGET_CLASS (gb_document_view_parent_class)->destroy (widget);
+}
+
+static void
+gb_document_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbDocumentView *self = GB_DOCUMENT_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONTROLS:
+ g_value_set_object (value, gb_document_view_get_controls (self));
+ break;
+
+ case PROP_DOCUMENT:
+ g_value_set_object (value, gb_document_view_get_document (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_document_view_class_init (GbDocumentViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = gb_document_view_get_property;
+
+ widget_class->destroy = gb_document_view_destroy;
+
+ gParamSpecs [PROP_CONTROLS] =
+ g_param_spec_object ("controls",
+ _("Controls"),
+ _("The widget containing the view controls."),
+ GTK_TYPE_BOX,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_CONTROLS,
+ gParamSpecs [PROP_CONTROLS]);
+
+ gParamSpecs [PROP_DOCUMENT] =
+ g_param_spec_object ("document",
+ _("Document"),
+ _("The document being viewed."),
+ GB_TYPE_DOCUMENT,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_DOCUMENT,
+ gParamSpecs [PROP_DOCUMENT]);
+
+ gSignals [CLOSE] =
+ g_signal_new ("close",
+ GB_TYPE_DOCUMENT_VIEW,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GbDocumentViewClass, close),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+gb_document_view_init (GbDocumentView *self)
+{
+ self->priv = gb_document_view_get_instance_private (self);
+
+ self->priv->controls = g_object_new (GTK_TYPE_BOX,
+ "orientation", GTK_ORIENTATION_HORIZONTAL,
+ "visible", TRUE,
+ NULL);
+ g_object_ref_sink (self->priv->controls);
+}
+
+static void
+buildable_init (GtkBuildableIface *iface)
+{
+ iface->get_internal_child = gb_document_view_get_internal_child;
+}
diff --git a/src/documents/gb-document-view.h b/src/documents/gb-document-view.h
new file mode 100644
index 0000000..b50be8c
--- /dev/null
+++ b/src/documents/gb-document-view.h
@@ -0,0 +1,64 @@
+/* gb-document-view.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * 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_DOCUMENT_VIEW_H
+#define GB_DOCUMENT_VIEW_H
+
+#include <gtk/gtk.h>
+
+#include "gb-document.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_DOCUMENT_VIEW (gb_document_view_get_type())
+#define GB_DOCUMENT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_DOCUMENT_VIEW,
GbDocumentView))
+#define GB_DOCUMENT_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GB_TYPE_DOCUMENT_VIEW,
GbDocumentView const))
+#define GB_DOCUMENT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GB_TYPE_DOCUMENT_VIEW,
GbDocumentViewClass))
+#define GB_IS_DOCUMENT_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_DOCUMENT_VIEW))
+#define GB_IS_DOCUMENT_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GB_TYPE_DOCUMENT_VIEW))
+#define GB_DOCUMENT_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GB_TYPE_DOCUMENT_VIEW,
GbDocumentViewClass))
+
+typedef struct _GbDocumentView GbDocumentView;
+typedef struct _GbDocumentViewClass GbDocumentViewClass;
+typedef struct _GbDocumentViewPrivate GbDocumentViewPrivate;
+
+struct _GbDocumentView
+{
+ GtkBox parent;
+
+ /*< private >*/
+ GbDocumentViewPrivate *priv;
+};
+
+struct _GbDocumentViewClass
+{
+ GtkBoxClass parent;
+
+ GbDocument *(*get_document) (GbDocumentView *view);
+ gboolean (*close) (GbDocumentView *view);
+};
+
+GType gb_document_view_get_type (void);
+GbDocumentView *gb_document_view_new (void);
+void gb_document_view_close (GbDocumentView *view);
+GbDocument *gb_document_view_get_document (GbDocumentView *view);
+GtkWidget *gb_document_view_get_controls (GbDocumentView *view);
+
+G_END_DECLS
+
+#endif /* GB_DOCUMENT_VIEW_H */
diff --git a/src/documents/gb-document.h b/src/documents/gb-document.h
index f572dac..bce458f 100644
--- a/src/documents/gb-document.h
+++ b/src/documents/gb-document.h
@@ -19,7 +19,7 @@
#ifndef GB_DOCUMENT_H
#define GB_DOCUMENT_H
-#include "gb-tab.h"
+#include <gtk/gtk.h>
G_BEGIN_DECLS
@@ -28,22 +28,22 @@ G_BEGIN_DECLS
#define GB_IS_DOCUMENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GB_TYPE_DOCUMENT))
#define GB_DOCUMENT_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), GB_TYPE_DOCUMENT,
GbDocumentInterface))
-typedef struct _GbDocument GbDocument;
+typedef struct _GbDocument GbDocument;
typedef struct _GbDocumentInterface GbDocumentInterface;
struct _GbDocumentInterface
{
GTypeInterface parent;
- gboolean (*get_can_save) (GbDocument *document);
+ gboolean (*get_modified) (GbDocument *document);
const gchar *(*get_title) (GbDocument *document);
- GbTab *(*create_tab) (GbDocument *document);
+ GtkWidget *(*create_view) (GbDocument *document);
};
GType gb_document_get_type (void) G_GNUC_CONST;
-gboolean gb_document_get_can_save (GbDocument *document);
+gboolean gb_document_get_modified (GbDocument *document);
const gchar *gb_document_get_title (GbDocument *document);
-GbTab *gb_document_create_tab (GbDocument *document);
+GtkWidget *gb_document_create_view (GbDocument *document);
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]