[gnome-builder/wip/chergert/perspective] libide: sketch out core objects for workbench



commit 7f196f4bdb805f275edd28701c8747350b7d4471
Author: Christian Hergert <chergert redhat com>
Date:   Sun Nov 1 18:39:59 2015 -0800

    libide: sketch out core objects for workbench
    
    Lots to think about, just some sketches.

 data/ui/ide-workbench.ui              |   27 +++
 libide/Makefile.am                    |   16 ++
 libide/ide-application.c              |  107 +++++++++++
 libide/ide-application.h              |   58 ++++++
 libide/ide-layout-manager.c           |   72 ++++++++
 libide/ide-layout-manager.h           |   60 ++++++
 libide/ide-perspective.c              |  209 +++++++++++++++++++++
 libide/ide-perspective.h              |   59 ++++++
 libide/ide-preferences-addin.c        |   81 ++++++++
 libide/ide-preferences-addin.h        |   49 +++++
 libide/ide-preferences.c              |  116 ++++++++++++
 libide/ide-preferences.h              |  105 +++++++++++
 libide/ide-view.c                     |  129 +++++++++++++
 libide/ide-view.h                     |   61 ++++++
 libide/ide-workbench-addin.c          |   83 +++++++++
 libide/ide-workbench-addin.h          |   47 +++++
 libide/ide-workbench.c                |  322 +++++++++++++++++++++++++++++++++
 libide/ide-workbench.h                |   65 +++++++
 libide/ide.h                          |    6 +
 libide/resources/libide.gresource.xml |    2 +
 20 files changed, 1674 insertions(+), 0 deletions(-)
---
diff --git a/data/ui/ide-workbench.ui b/data/ui/ide-workbench.ui
new file mode 100644
index 0000000..28b1d8c
--- /dev/null
+++ b/data/ui/ide-workbench.ui
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.15 -->
+  <template class="IdeWorkbench" parent="GtkApplicationWindow">
+    <property name="title" translatable="yes">Builder</property>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="homogeneous">false</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkStackSwitcher" id="perspectives_stack_switcher">
+            <property name="orientation">vertical</property>
+            <property name="stack">perspectives_stack</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="perspectives_stack">
+            <property name="homogeneous">false</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 881f5ad..4dd716d 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -17,6 +17,8 @@ libide_1_0_la_public_sources = \
        git/ide-git-remote-callbacks.h \
        git/ide-git-vcs.c \
        git/ide-git-vcs.h \
+       ide-application.c \
+       ide-application.h \
        ide-back-forward-item.c \
        ide-back-forward-item.h \
        ide-back-forward-list.c \
@@ -86,9 +88,17 @@ libide_1_0_la_public_sources = \
        ide-indent-style.h \
        ide-indenter.c \
        ide-indenter.h \
+       ide-layout-manager.c \
+       ide-layout-manager.h \
        ide-log.c \
        ide-log.h \
        ide-macros.h \
+       ide-perspective.c \
+       ide-perspective.h \
+       ide-preferences.c \
+       ide-preferences.h \
+       ide-preferences-addin.c \
+       ide-preferences-addin.h \
        ide-object.c \
        ide-object.h \
        ide-pattern-spec.c \
@@ -172,10 +182,16 @@ libide_1_0_la_public_sources = \
        ide-unsaved-file.h \
        ide-unsaved-files.c \
        ide-unsaved-files.h \
+       ide-view.c \
+       ide-view.h \
        ide-vcs-uri.c \
        ide-vcs-uri.h \
        ide-vcs.c \
        ide-vcs.h \
+       ide-workbench.c \
+       ide-workbench.h \
+       ide-workbench-addin.c \
+       ide-workbench-addin.h \
        ide-worker.c \
        ide-worker.h \
        ide.c \
diff --git a/libide/ide-application.c b/libide/ide-application.c
new file mode 100644
index 0000000..f692c38
--- /dev/null
+++ b/libide/ide-application.c
@@ -0,0 +1,107 @@
+/* ide-application.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-application"
+
+#include <glib/gi18n.h>
+
+#include "ide-application.h"
+#include "ide-worker-manager.h"
+
+struct _IdeApplication
+{
+  GtkApplication    parent_instance;
+
+  GDateTime        *startup_time;
+  IdeWorkerManager *worker_manager;
+};
+
+G_DEFINE_TYPE (IdeApplication, ide_application, GTK_TYPE_APPLICATION_WINDOW)
+
+static void
+ide_application_class_init (IdeApplicationClass *klass)
+{
+}
+
+static void
+ide_application_init (IdeApplication *self)
+{
+  self->startup_time = g_date_time_new_now_local ();
+}
+
+GDateTime *
+ide_application_get_startup_time (IdeApplication *self)
+{
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
+
+  return self->startup_time;
+}
+
+static void
+ide_application_get_worker_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+  IdeWorkerManager *worker_manager = (IdeWorkerManager *)object;
+  GTask *task = user_data;
+  GError *error = NULL;
+  GDBusProxy *proxy;
+
+  g_assert (IDE_IS_WORKER_MANAGER (worker_manager));
+  g_assert (G_IS_TASK (task));
+
+  proxy = ide_worker_manager_get_worker_finish (worker_manager, result, &error);
+
+  if (proxy == NULL)
+    g_task_return_error (task, error);
+  else
+    g_task_return_pointer (task, proxy, g_object_unref);
+}
+
+void
+ide_application_get_worker_async (IdeApplication      *self,
+                                  const gchar         *plugin_name,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+  g_return_if_fail (plugin_name != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ide_worker_manager_get_worker_async (self->worker_manager,
+                                       plugin_name,
+                                       cancellable,
+                                       ide_application_get_worker_cb,
+                                       g_object_ref (task));
+}
+
+GDBusProxy *
+ide_application_get_worker_finish (IdeApplication  *self,
+                                   GAsyncResult    *result,
+                                   GError         **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (task), NULL);
+
+  return g_task_propagate_pointer (task, error);
+}
diff --git a/libide/ide-application.h b/libide/ide-application.h
new file mode 100644
index 0000000..91c60a7
--- /dev/null
+++ b/libide/ide-application.h
@@ -0,0 +1,58 @@
+/* ide-application.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_APPLICATION_H
+#define IDE_APPLICATION_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#define IDE_TYPE_APPLICATION    (ide_application_get_type())
+#define IDE_APPLICATION_DEFAULT (IDE_APPLICATION(g_application_get_default()))
+
+G_DECLARE_FINAL_TYPE (IdeApplication, ide_application, IDE, APPLICATION, GtkApplication)
+
+GDateTime  *ide_application_get_startup_time    (IdeApplication       *self);
+void        ide_application_open_project_async  (IdeApplication       *self,
+                                                 GFile                *project_file,
+                                                 GPtrArray            *additional_files,
+                                                 GCancellable         *cancellable,
+                                                 GAsyncReadyCallback   callback,
+                                                 gpointer              user_data);
+gboolean    ide_application_open_project_finish (IdeApplication       *self,
+                                                 GAsyncResult         *result,
+                                                 GError              **error);
+void        ide_application_open_files_async    (IdeApplication       *self,
+                                                 GFile               **files,
+                                                 gint                  n_files,
+                                                 GCancellable         *cancellable,
+                                                 GAsyncReadyCallback   callback,
+                                                 gpointer              user_data);
+gboolean    ide_application_open_files_finish   (IdeApplication       *self,
+                                                 GAsyncResult         *result,
+                                                 GError              **error);
+void        ide_application_get_worker_async    (IdeApplication       *self,
+                                                 const gchar          *plugin_name,
+                                                 GCancellable         *cancellable,
+                                                 GAsyncReadyCallback   callback,
+                                                 gpointer              user_data);
+GDBusProxy *ide_application_get_worker_finish   (IdeApplication       *self,
+                                                 GAsyncResult         *result,
+                                                 GError              **error);
+
+#endif /* IDE_APPLICATION_H */
diff --git a/libide/ide-layout-manager.c b/libide/ide-layout-manager.c
new file mode 100644
index 0000000..ff37280
--- /dev/null
+++ b/libide/ide-layout-manager.c
@@ -0,0 +1,72 @@
+/* ide-layout-manager.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-layout-manager.h"
+
+G_DEFINE_INTERFACE (IdeLayoutManager, ide_layout_manager, G_TYPE_OBJECT)
+
+static void
+ide_layout_manager_default_init (IdeLayoutManagerInterface *iface)
+{
+}
+
+/**
+ * ide_layout_manager_add:
+ * @self: An #IdeLayoutManager.
+ * @hints: (nullable): optional hints for the layout.
+ * @child: A #GtkWidget.
+ *
+ * Adds @child to the layout, optionally hinting to the layout manager about where to
+ * place the child.
+ *
+ * Returns: A layout specific identifier greater than zero, which should be passed to
+ *   ide_layout_manager_remove() to remove the widget from the layout.
+ */
+guint
+ide_layout_manager_add (IdeLayoutManager     *self,
+                        const IdeLayoutHints *hints,
+                        GtkWidget            *child)
+{
+  guint ret;
+
+  g_return_val_if_fail (IDE_IS_LAYOUT_MANAGER (self), 0);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), 0);
+
+  ret = IDE_LAYOUT_MANAGER_GET_IFACE (self)->add (self, hints, child);
+
+  g_return_val_if_fail (ret > 0, 0);
+
+  return ret;
+}
+
+/**
+ * ide_layout_manager_remove:
+ * @self: An #IdeLayoutManager.
+ * @layout_id: The layout id from ide_layout_manager_add()
+ *
+ * Removes a widget from the layout that was generated by calling * ide_layout_manager_add().
+ */
+void
+ide_layout_manager_remove (IdeLayoutManager *self,
+                           guint             layout_id)
+{
+  g_return_if_fail (IDE_IS_LAYOUT_MANAGER (self));
+  g_return_if_fail (layout_id > 0);
+
+  IDE_LAYOUT_MANAGER_GET_IFACE (self)->remove (self, layout_id);
+}
diff --git a/libide/ide-layout-manager.h b/libide/ide-layout-manager.h
new file mode 100644
index 0000000..3372138
--- /dev/null
+++ b/libide/ide-layout-manager.h
@@ -0,0 +1,60 @@
+/* ide-layout-manager.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_LAYOUT_MANAGER_H
+#define IDE_LAYOUT_MANAGER_H
+
+#include <gtk/gtk.h>
+
+#include "ide-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LAYOUT_MANAGER (ide_layout_manager_get_type())
+
+G_DECLARE_INTERFACE (IdeLayoutManager, ide_layout_manager, IDE, LAYOUT_MANAGER, GObject)
+
+typedef struct
+{
+  GtkWidget *left_of;
+  GtkWidget *right_of;
+  GtkWidget *above;
+  GtkWidget *below;
+  gint       column;
+} IdeLayoutHints;
+
+struct _IdeLayoutManagerInterface
+{
+  GTypeInterface parent_instance;
+
+  guint (*add)    (IdeLayoutManager     *self,
+                   const IdeLayoutHints *hints,
+                   GtkWidget            *child);
+  void  (*remove) (IdeLayoutManager     *self,
+                   guint                 layout_id);
+};
+
+guint ide_layout_manager_add    (IdeLayoutManager     *self,
+                                 const IdeLayoutHints *hints,
+                                 GtkWidget            *child);
+void  ide_layout_manager_remove (IdeLayoutManager     *self,
+                                 guint                 layout_id);
+
+G_END_DECLS
+
+#endif /* IDE_LAYOUT_MANAGER_H */
diff --git a/libide/ide-perspective.c b/libide/ide-perspective.c
new file mode 100644
index 0000000..0634f34
--- /dev/null
+++ b/libide/ide-perspective.c
@@ -0,0 +1,209 @@
+/* ide-perspective.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-perspective.h"
+
+G_DEFINE_INTERFACE (IdePerspective, ide_perspective, G_TYPE_OBJECT)
+
+static gboolean
+ide_perspective_real_agree_to_shutdown (IdePerspective *self)
+{
+  return TRUE;
+}
+
+static const gchar *
+ide_perspective_real_get_icon_name (IdePerspective *self)
+{
+  return NULL;
+}
+
+static gboolean
+ide_perspective_real_get_needs_attention (IdePerspective *self)
+{
+  return FALSE;
+}
+
+static const gchar *
+ide_perspective_real_get_title (IdePerspective *self)
+{
+  return NULL;
+}
+
+static GtkWidget *
+ide_perspective_real_get_titlebar (IdePerspective *self)
+{
+  return NULL;
+}
+
+static void
+ide_perspective_real_set_fullscreen (IdePerspective *self,
+                                     gboolean        fullscreen)
+{
+}
+
+static void
+ide_perspective_real_views_foreach (IdePerspective *self,
+                                    GtkCallback     callback,
+                                    gpointer        user_data)
+{
+}
+
+static void
+ide_perspective_default_init (IdePerspectiveInterface *iface)
+{
+  iface->agree_to_shutdown = ide_perspective_real_agree_to_shutdown;
+  iface->get_icon_name = ide_perspective_real_get_icon_name;
+  iface->get_needs_attention = ide_perspective_real_get_needs_attention;
+  iface->get_title = ide_perspective_real_get_title;
+  iface->get_titlebar = ide_perspective_real_get_titlebar;
+  iface->set_fullscreen = ide_perspective_real_set_fullscreen;
+  iface->views_foreach = ide_perspective_real_views_foreach;
+}
+
+/**
+ * ide_perspective_agree_to_shutdown:
+ * @self: An #IdePerspective.
+ *
+ * This interface method is called when the workbench would like to shutdown.
+ * If the perspective needs to focus and ask the user a question, this is the place
+ * to do so. You may run a #GtkDialog using gtk_dialog_run() or simply focus your
+ * perspective and return %FALSE.
+ *
+ * Returns: %TRUE to allow the workbench to continue shutting down.
+ */
+gboolean
+ide_perspective_agree_to_shutdown (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), FALSE);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->agree_to_shutdown (self);
+}
+
+/**
+ * ide_perspective_get_icon_name:
+ * @self: An #IdePerspective.
+ *
+ * This interface methods retrieves the icon name to use when displaying the
+ * perspective selection sidebar.
+ *
+ * If you implement an "icon-name" property, the icon may change at runtime.
+ *
+ * Returns: A named icon as a string which will not be modified or freed.
+ */
+const gchar *
+ide_perspective_get_icon_name (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), NULL);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->get_icon_name (self);
+}
+
+/**
+ * ide_perspective_get_needs_attention:
+ * @self: An #IdePerspective.
+ *
+ * This interface method returns %TRUE if the interface needs attention.
+ *
+ * One such use of this would be to indicate that contents within a perspective have
+ * changed since the user last focused the perspective. This should also be implemented
+ * with a boolean property named "needs-attention". If you call g_object_notify() (or one
+ * of its variants), the notifcation visual will be rendered with your icon.
+ *
+ * Returns: %TRUE if the perspective needs attention.
+ */
+gboolean
+ide_perspective_get_needs_attention (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), FALSE);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->get_needs_attention (self);
+}
+
+/**
+ * ide_perspective_get_title:
+ * @self: An #IdePerspective
+ *
+ * This interface method gets the title of the perspective. This is used for tooltips
+ * in the perspective selector and potentially other UI components.
+ *
+ * Returns: A string which will not be modified or freed.
+ */
+const gchar *
+ide_perspective_get_title (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), NULL);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->get_title (self);
+}
+
+/**
+ * ide_perspective_get_titlebar:
+ * @self: An #IdePerspective.
+ *
+ * This interface method should return a #GtkWidget suitable for being embedded as the
+ * titlebar for the application. If you return %NULL from this method, a suitable titlebar
+ * will be created for you.
+ *
+ * You may use #IdeHeaderBar for a base implementation to save you the trouble of
+ * creating a titlebar similar to other perspectives in Builder.
+ *
+ * Returns: (transfer none) (nullable): A #GtkWidget or %NULL.
+ */
+GtkWidget *
+ide_perspective_get_titlebar (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), NULL);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->get_titlebar (self);
+}
+
+/**
+ * ide_perspective_set_fullscreen:
+ * @self: An #IdePerspective.
+ * @fullscreen: If fullscreen mode should be activated.
+ *
+ * This interface method is used to notify the perspective that it is going into
+ * fullscreen mode. The #IdeWorkbench will notify the perspective before it is displayed.
+ */
+void
+ide_perspective_set_fullscreen (IdePerspective *self,
+                                gboolean        fullscreen)
+{
+  g_return_if_fail (IDE_IS_PERSPECTIVE (self));
+
+  IDE_PERSPECTIVE_GET_IFACE (self)->set_fullscreen (self, fullscreen);
+}
+
+/**
+ * ide_perspective_views_foreach:
+ * @self: An #IdePerspective.
+ * @callback: (scope call): A #GtkCallback.
+ * @user_data: user data for @callback.
+ *
+ * This interface method is used to iterate all #IdeView's that are descendents of @self.
+ */
+void
+ide_perspective_views_foreach (IdePerspective *self,
+                               GtkCallback     callback,
+                               gpointer        user_data)
+{
+  g_return_if_fail (IDE_IS_PERSPECTIVE (self));
+  g_return_if_fail (callback != NULL);
+
+  IDE_PERSPECTIVE_GET_IFACE (self)->views_foreach (self, callback, user_data);
+}
diff --git a/libide/ide-perspective.h b/libide/ide-perspective.h
new file mode 100644
index 0000000..caf0302
--- /dev/null
+++ b/libide/ide-perspective.h
@@ -0,0 +1,59 @@
+/* ide-perspective.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_PERSPECTIVE_H
+#define IDE_PERSPECTIVE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PERSPECTIVE (ide_perspective_get_type())
+
+G_DECLARE_INTERFACE (IdePerspective, ide_perspective, IDE, PERSPECTIVE, GtkWidget)
+
+struct _IdePerspectiveInterface
+{
+  GTypeInterface parent;
+
+  gboolean     (*agree_to_shutdown)   (IdePerspective *self);
+  const gchar *(*get_icon_name)       (IdePerspective *self);
+  gboolean     (*get_needs_attention) (IdePerspective *self);
+  const gchar *(*get_title)           (IdePerspective *self);
+  GtkWidget   *(*get_titlebar)        (IdePerspective *self);
+  void         (*set_fullscreen)      (IdePerspective *self,
+                                       gboolean        fullscreen);
+  void         (*views_foreach)       (IdePerspective *self,
+                                       GtkCallback     callback,
+                                       gpointer        user_data);
+};
+
+gboolean     ide_perspective_agree_to_shutdown   (IdePerspective *self);
+const gchar *ide_perspective_get_icon_name       (IdePerspective *self);
+const gchar *ide_perspective_get_title           (IdePerspective *self);
+GtkWidget   *ide_perspective_get_titlebar        (IdePerspective *self);
+gboolean     ide_perspective_get_needs_attention (IdePerspective *self);
+void         ide_perspective_set_fullscreen      (IdePerspective *self,
+                                                  gboolean        fullscreen);
+void         ide_perspective_views_foreach       (IdePerspective *self,
+                                                  GtkCallback     callback,
+                                                  gpointer        user_data);
+
+G_END_DECLS
+
+#endif /* IDE_PERSPECTIVE_H */
diff --git a/libide/ide-preferences-addin.c b/libide/ide-preferences-addin.c
new file mode 100644
index 0000000..2463942
--- /dev/null
+++ b/libide/ide-preferences-addin.c
@@ -0,0 +1,81 @@
+/* ide-preferences-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-preferences-addin.h"
+
+G_DEFINE_INTERFACE (IdePreferencesAddin, ide_preferences_addin, G_TYPE_OBJECT)
+
+static void
+ide_preferences_addin_real_load (IdePreferencesAddin *self,
+                                 IdePreferences      *preferences)
+{
+}
+
+static void
+ide_preferences_addin_real_unload (IdePreferencesAddin *self,
+                                   IdePreferences      *preferences)
+{
+}
+
+static void
+ide_preferences_addin_default_init (IdePreferencesAddinInterface *iface)
+{
+  iface->load = ide_preferences_addin_real_load;
+  iface->unload = ide_preferences_addin_real_unload;
+}
+
+/**
+ * ide_preferences_addin_load:
+ * @self: An #IdePreferencesAddin.
+ * @preferences: The preferences container implementation.
+ *
+ * This interface method is called when a preferences addin is initialized. It could be
+ * initialized from multiple preferences implementations, so consumers should use the
+ * #IdePreferences interface to add their preferences controls to the container.
+ *
+ * Such implementations might include a preferences dialog window, or a preferences
+ * widget which could be rendered as a perspective.
+ */
+void
+ide_preferences_addin_load (IdePreferencesAddin *self,
+                            IdePreferences      *preferences)
+{
+  g_return_if_fail (IDE_IS_PREFERENCES_ADDIN (self));
+  g_return_if_fail (IDE_IS_PREFERENCES (preferences));
+
+  IDE_PREFERENCES_ADDIN_GET_IFACE (self)->load (self, preferences);
+}
+
+/**
+ * ide_preferences_addin_unload:
+ * @self: An #IdePreferencesAddin.
+ * @preferences: The preferences container implementation.
+ *
+ * This interface method is called when the preferences addin should remove all controls
+ * added to @preferences. This could happen during desctruction of @preferences, or when
+ * the plugin is unloaded.
+ */
+void
+ide_preferences_addin_unload (IdePreferencesAddin *self,
+                              IdePreferences      *preferences)
+{
+  g_return_if_fail (IDE_IS_PREFERENCES_ADDIN (self));
+  g_return_if_fail (IDE_IS_PREFERENCES (preferences));
+
+  IDE_PREFERENCES_ADDIN_GET_IFACE (self)->unload (self, preferences);
+}
diff --git a/libide/ide-preferences-addin.h b/libide/ide-preferences-addin.h
new file mode 100644
index 0000000..308b7be
--- /dev/null
+++ b/libide/ide-preferences-addin.h
@@ -0,0 +1,49 @@
+/* ide-preferences-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_PREFERENCES_ADDIN_H
+#define IDE_PREFERENCES_ADDIN_H
+
+#include <gtk/gtk.h>
+
+#include "ide-preferences.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_ADDIN (ide_preferences_addin_get_type())
+
+G_DECLARE_INTERFACE (IdePreferencesAddin, ide_preferences_addin, IDE, PREFERENCES_ADDIN, GObject)
+
+struct _IdePreferencesAddinInterface
+{
+  GTypeInterface parent_interface;
+
+  void (*load)   (IdePreferencesAddin *self,
+                  IdePreferences      *preferences);
+  void (*unload) (IdePreferencesAddin *self,
+                  IdePreferences      *preferences);
+};
+
+void ide_preferences_addin_load   (IdePreferencesAddin *self,
+                                   IdePreferences      *preferences);
+void ide_preferences_addin_unload (IdePreferencesAddin *self,
+                                   IdePreferences      *preferences);
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_ADDIN_H */
diff --git a/libide/ide-preferences.c b/libide/ide-preferences.c
new file mode 100644
index 0000000..eaae59d
--- /dev/null
+++ b/libide/ide-preferences.c
@@ -0,0 +1,116 @@
+/* ide-preferences.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-preferences.h"
+
+G_DEFINE_INTERFACE (IdePreferences, ide_preferences, G_TYPE_OBJECT)
+
+static void
+ide_preferences_default_init (IdePreferencesInterface *iface)
+{
+}
+
+void
+ide_preferences_add_page (IdePreferences *self,
+                          const gchar    *page_name,
+                          const gchar    *title,
+                          gint            priority)
+{
+  g_return_if_fail (IDE_IS_PREFERENCES (self));
+  g_return_if_fail (page_name != NULL);
+  g_return_if_fail (title != NULL);
+
+  IDE_PREFERENCES_GET_IFACE (self)->add_page (self, page_name, title, priority);
+}
+
+void
+ide_preferences_add_group (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           const gchar    *title,
+                           gint            priority)
+{
+  g_return_if_fail (IDE_IS_PREFERENCES (self));
+  g_return_if_fail (page_name != NULL);
+  g_return_if_fail (group_name != NULL);
+  g_return_if_fail (title != NULL);
+
+  IDE_PREFERENCES_GET_IFACE (self)->add_group (self, page_name, group_name, title, priority);
+}
+
+guint
+ide_preferences_add_switch (IdePreferences *self,
+                            const gchar    *page_name,
+                            const gchar    *group_name,
+                            const gchar    *schema_id,
+                            const gchar    *key,
+                            const gchar    *title,
+                            const gchar    *subtitle,
+                            const gchar    *keywords,
+                            gint            priority)
+{
+  g_return_val_if_fail (IDE_IS_PREFERENCES (self), 0);
+  g_return_val_if_fail (page_name != NULL, 0);
+  g_return_val_if_fail (group_name != NULL, 0);
+  g_return_val_if_fail (schema_id != NULL, 0);
+  g_return_val_if_fail (key != NULL, 0);
+  g_return_val_if_fail (title != NULL, 0);
+
+  return IDE_PREFERENCES_GET_IFACE (self)->add_switch (self, page_name, group_name, schema_id, key,
+                                                       title, subtitle, keywords, priority);
+}
+
+guint
+ide_preferences_add_spinbutton (IdePreferences *self,
+                                const gchar    *page_name,
+                                const gchar    *group_name,
+                                const gchar    *schema_id,
+                                const gchar    *key,
+                                const gchar    *title,
+                                const gchar    *subtitle,
+                                const gchar    *keywords,
+                                gint            priority)
+{
+  g_return_val_if_fail (IDE_IS_PREFERENCES (self), 0);
+  g_return_val_if_fail (page_name != NULL, 0);
+  g_return_val_if_fail (group_name != NULL, 0);
+  g_return_val_if_fail (schema_id != NULL, 0);
+  g_return_val_if_fail (key != NULL, 0);
+  g_return_val_if_fail (title != NULL, 0);
+
+  return IDE_PREFERENCES_GET_IFACE (self)->add_spinbutton (self, page_name, group_name, schema_id,
+                                                           key, title, subtitle, keywords,
+                                                           priority);
+}
+
+guint
+ide_preferences_add_custom (IdePreferences *self,
+                            const gchar    *page_name,
+                            const gchar    *group_name,
+                            GtkWidget      *widget,
+                            const gchar    *keywords,
+                            gint            priority)
+{
+  g_return_val_if_fail (IDE_IS_PREFERENCES (self), 0);
+  g_return_val_if_fail (page_name != NULL, 0);
+  g_return_val_if_fail (group_name != NULL, 0);
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+
+  return IDE_PREFERENCES_GET_IFACE (self)->add_custom (self, page_name, group_name, widget,
+                                                       keywords, priority);
+}
diff --git a/libide/ide-preferences.h b/libide/ide-preferences.h
new file mode 100644
index 0000000..bca3d13
--- /dev/null
+++ b/libide/ide-preferences.h
@@ -0,0 +1,105 @@
+/* ide-preferences.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_PREFERENCES_H
+#define IDE_PREFERENCES_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES (ide_preferences_get_type())
+
+G_DECLARE_INTERFACE (IdePreferences, ide_preferences, IDE, PREFERENCES, GObject)
+
+struct _IdePreferencesInterface
+{
+  GTypeInterface parent_interface;
+
+  void  (*add_page)       (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *title,
+                           gint            priority);
+  void  (*add_group)      (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           const gchar    *title,
+                           gint            priority);
+  guint (*add_switch)     (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           const gchar    *schema_id,
+                           const gchar    *key,
+                           const gchar    *title,
+                           const gchar    *subtitle,
+                           const gchar    *keywords,
+                           gint            priority);
+  guint (*add_spinbutton) (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           const gchar    *schema_id,
+                           const gchar    *key,
+                           const gchar    *title,
+                           const gchar    *subtitle,
+                           const gchar    *keywords,
+                           gint            priority);
+  guint (*add_custom)     (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           GtkWidget      *widget,
+                           const gchar    *keywords,
+                           gint            priority);
+};
+
+void  ide_preferences_add_page       (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *title,
+                                      gint            priority);
+void  ide_preferences_add_group      (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      const gchar    *title,
+                                      gint            priority);
+guint ide_preferences_add_switch     (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      const gchar    *schema_id,
+                                      const gchar    *key,
+                                      const gchar    *title,
+                                      const gchar    *subtitle,
+                                      const gchar    *keywords,
+                                      gint            priority);
+guint ide_preferences_add_spinbutton (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      const gchar    *schema_id,
+                                      const gchar    *key,
+                                      const gchar    *title,
+                                      const gchar    *subtitle,
+                                      const gchar    *keywords,
+                                      gint            priority);
+guint ide_preferences_add_custom     (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      GtkWidget      *widget,
+                                      const gchar    *keywords,
+                                      gint            priority);
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_H */
diff --git a/libide/ide-view.c b/libide/ide-view.c
new file mode 100644
index 0000000..6a7ad9c
--- /dev/null
+++ b/libide/ide-view.c
@@ -0,0 +1,129 @@
+/* ide-view.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-view.h"
+
+G_DEFINE_INTERFACE (IdeView, ide_view, GTK_TYPE_WIDGET)
+
+static const gchar *
+ide_view_real_get_title (IdeView *self)
+{
+  return NULL;
+}
+
+static const gchar *
+ide_view_real_get_icon_name (IdeView *self)
+{
+  return NULL;
+}
+
+static gboolean
+ide_view_real_get_can_save (IdeView *self)
+{
+  return FALSE;
+}
+
+static gboolean
+ide_view_real_get_needs_attention (IdeView *self)
+{
+  return FALSE;
+}
+
+static void
+ide_view_real_save_async (IdeView             *self,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)
+{
+  g_task_report_new_error (self, callback, user_data, ide_view_real_save_async,
+                           G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Saving is not supported.");
+}
+
+static gboolean
+ide_view_real_save_finish (IdeView       *self,
+                           GAsyncResult  *result,
+                           GError       **error)
+{
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_view_default_init (IdeViewInterface *iface)
+{
+  iface->get_title = ide_view_real_get_title;
+  iface->get_icon_name = ide_view_real_get_icon_name;
+  iface->get_can_save = ide_view_real_get_can_save;
+  iface->get_needs_attention = ide_view_real_get_needs_attention;
+  iface->save_async = ide_view_real_save_async;
+  iface->save_finish = ide_view_real_save_finish;
+}
+
+const gchar *
+ide_view_get_title (IdeView *self)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), NULL);
+
+  return NULL;
+}
+
+const gchar *
+ide_view_get_icon_name (IdeView *self)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), NULL);
+
+  return IDE_VIEW_GET_IFACE (self)->get_icon_name (self);
+}
+
+gboolean
+ide_view_get_can_save (IdeView *self)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), FALSE);
+
+  return IDE_VIEW_GET_IFACE (self)->get_can_save (self);
+}
+
+gboolean
+ide_view_get_needs_attention (IdeView *self)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), FALSE);
+
+  return IDE_VIEW_GET_IFACE (self)->get_needs_attention (self);
+}
+
+void
+ide_view_save_async (IdeView             *self,
+                     GCancellable        *cancellable,
+                     GAsyncReadyCallback  callback,
+                     gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_VIEW (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_VIEW_GET_IFACE (self)->save_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_view_save_finish (IdeView       *self,
+                      GAsyncResult  *result,
+                      GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_VIEW_GET_IFACE (self)->save_finish (self, result, error);
+}
diff --git a/libide/ide-view.h b/libide/ide-view.h
new file mode 100644
index 0000000..d73f462
--- /dev/null
+++ b/libide/ide-view.h
@@ -0,0 +1,61 @@
+/* ide-view.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_VIEW_H
+#define IDE_VIEW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_VIEW (ide_view_get_type())
+
+G_DECLARE_INTERFACE (IdeView, ide_view, IDE, VIEW, GtkWidget)
+
+struct _IdeViewInterface
+{
+  GTypeInterface parent;
+
+  const gchar *(*get_title)           (IdeView              *self);
+  const gchar *(*get_icon_name)       (IdeView              *self);
+  gboolean     (*get_can_save)        (IdeView              *self);
+  gboolean     (*get_needs_attention) (IdeView              *self);
+  void         (*save_async)          (IdeView              *self,
+                                       GCancellable         *cancellable,
+                                       GAsyncReadyCallback   callback,
+                                       gpointer              user_data);
+  gboolean     (*save_finish)         (IdeView              *self,
+                                       GAsyncResult         *result,
+                                       GError              **error);
+};
+
+const gchar *ide_view_get_title           (IdeView              *self);
+const gchar *ide_view_get_icon_name       (IdeView              *self);
+gboolean     ide_view_get_can_save        (IdeView              *self);
+gboolean     ide_view_get_needs_attention (IdeView              *self);
+void         ide_view_save_async          (IdeView              *self,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data);
+gboolean     ide_view_save_finish         (IdeView              *self,
+                                           GAsyncResult         *result,
+                                           GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_VIEW_H */
diff --git a/libide/ide-workbench-addin.c b/libide/ide-workbench-addin.c
new file mode 100644
index 0000000..1cf1d61
--- /dev/null
+++ b/libide/ide-workbench-addin.c
@@ -0,0 +1,83 @@
+/* ide-workbench-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-workbench-addin"
+
+#include "ide-workbench-addin.h"
+
+G_DEFINE_INTERFACE (IdeWorkbenchAddin, ide_workbench_addin, G_TYPE_OBJECT)
+
+static void
+ide_workbench_addin_real_load (IdeWorkbenchAddin *self,
+                               IdeWorkbench      *workbench)
+{
+}
+
+static void
+ide_workbench_addin_real_unload (IdeWorkbenchAddin *self,
+                                 IdeWorkbench      *workbench)
+{
+}
+
+static void
+ide_workbench_addin_default_init (IdeWorkbenchAddinInterface *iface)
+{
+  iface->load = ide_workbench_addin_real_load;
+  iface->unload = ide_workbench_addin_real_unload;
+}
+
+/**
+ * ide_workbench_addin_load:
+ * @self: An #IdeWorkbenchAddin
+ * @workbench: An #IdeWorkbench
+ *
+ * This interface method is called to load @self. Addin implementations should add any
+ * required UI or actions to @workbench here. You should remove anything you've added
+ * in ide_workbench_addin_unload(), as that will be called when your plugin is deactivated
+ * or the workbench is in the destruction process.
+ */
+void
+ide_workbench_addin_load (IdeWorkbenchAddin *self,
+                          IdeWorkbench      *workbench)
+{
+  g_return_if_fail (IDE_IS_WORKBENCH_ADDIN (self));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  IDE_WORKBENCH_ADDIN_GET_IFACE (self)->load (self, workbench);
+}
+
+/**
+ * ide_workbench_addin_unload:
+ * @self: An #IdeWorkbenchAddin
+ * @workbench: An #IdeWorkbench
+ *
+ * This interface method should cleanup after anything added to @workbench in
+ * ide_workbench_addin_load().
+ *
+ * This might be called when a plugin is deactivated, or the workbench is in the
+ * destruction process.
+ */
+void
+ide_workbench_addin_unload (IdeWorkbenchAddin *self,
+                            IdeWorkbench      *workbench)
+{
+  g_return_if_fail (IDE_IS_WORKBENCH_ADDIN (self));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  IDE_WORKBENCH_ADDIN_GET_IFACE (self)->unload (self, workbench);
+}
diff --git a/libide/ide-workbench-addin.h b/libide/ide-workbench-addin.h
new file mode 100644
index 0000000..9897d2b
--- /dev/null
+++ b/libide/ide-workbench-addin.h
@@ -0,0 +1,47 @@
+/* ide-workbench-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_WORKBENCH_ADDIN_H
+#define IDE_WORKBENCH_ADDIN_H
+
+#include "ide-workbench.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_WORKBENCH_ADDIN (ide_workbench_addin_get_type())
+
+G_DECLARE_INTERFACE (IdeWorkbenchAddin, ide_workbench_addin, IDE, WORKBENCH_ADDIN, GObject)
+
+struct _IdeWorkbenchAddinInterface
+{
+  GTypeInterface parent;
+
+  void (*load)   (IdeWorkbenchAddin *self,
+                  IdeWorkbench      *workbench);
+  void (*unload) (IdeWorkbenchAddin *self,
+                  IdeWorkbench      *workbench);
+};
+
+void ide_workbench_addin_load   (IdeWorkbenchAddin *self,
+                                 IdeWorkbench      *workbench);
+void ide_workbench_addin_unload (IdeWorkbenchAddin *self,
+                                 IdeWorkbench      *workbench);
+
+G_END_DECLS
+
+#endif /* IDE_WORKBENCH_ADDIN_H */
diff --git a/libide/ide-workbench.c b/libide/ide-workbench.c
new file mode 100644
index 0000000..fd5b344
--- /dev/null
+++ b/libide/ide-workbench.c
@@ -0,0 +1,322 @@
+/* ide-workbench.c
+ *
+ * Copyright (C) 2015 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 "ide-workbench"
+
+#include <libpeas/peas.h>
+
+#include "ide-macros.h"
+#include "ide-workbench.h"
+
+struct _IdeWorkbench
+{
+  GtkApplicationWindow  parent;
+
+  IdeContext           *context;
+
+  PeasExtensionSet     *perspectives;
+  IdePerspective       *perspective;
+
+  GtkStack             *perspectives_stack;
+  GtkStackSwitcher     *perspectives_stack_switcher;
+};
+
+typedef struct
+{
+  GtkCallback callback;
+  gpointer    user_data;
+} IdeWorkbenchForeach;
+
+G_DEFINE_TYPE (IdeWorkbench, ide_workbench, GTK_TYPE_APPLICATION_WINDOW)
+
+enum {
+  PROP_0,
+  PROP_PERSPECTIVE,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void
+ide_workbench_add_perspective (PeasExtensionSet *set,
+                               PeasPluginInfo   *plugin_info,
+                               PeasExtension    *extension,
+                               gpointer          user_data)
+{
+  IdeWorkbench *self = user_data;
+  IdePerspective *perspective = (IdePerspective *)extension;
+  const gchar *title;
+  const gchar *icon_name;
+
+  g_assert (IDE_IS_WORKBENCH (self));
+  g_assert (IDE_IS_PERSPECTIVE (perspective));
+
+  g_object_ref_sink (perspective);
+
+  title = ide_perspective_get_title (perspective);
+  icon_name = ide_perspective_get_icon_name (perspective);
+
+  gtk_container_add_with_properties (GTK_CONTAINER (self->perspectives_stack),
+                                     GTK_WIDGET (perspective),
+                                     "icon-name", icon_name,
+                                     "needs-attention", FALSE,
+                                     "title", title,
+                                     NULL);
+}
+
+static void
+ide_workbench_remove_perspective (PeasExtensionSet *set,
+                                  PeasPluginInfo   *plugin_info,
+                                  PeasExtension    *extension,
+                                  gpointer          user_data)
+{
+  IdeWorkbench *self = user_data;
+  IdePerspective *perspective = (IdePerspective *)extension;
+
+  g_assert (IDE_IS_WORKBENCH (self));
+  g_assert (IDE_IS_PERSPECTIVE (perspective));
+
+  gtk_container_remove (GTK_CONTAINER (self->perspectives_stack), GTK_WIDGET (perspective));
+}
+
+static void
+ide_workbench_finalize (GObject *object)
+{
+  IdeWorkbench *self = (IdeWorkbench *)object;
+
+  ide_clear_weak_pointer (&self->perspective);
+  g_clear_object (&self->context);
+
+  G_OBJECT_CLASS (ide_workbench_parent_class)->finalize (object);
+}
+
+static void
+ide_workbench_constructed (GObject *object)
+{
+  IdeWorkbench *self = (IdeWorkbench *)object;
+
+  G_OBJECT_CLASS (ide_workbench_parent_class)->constructed (object);
+
+  self->perspectives = peas_extension_set_new (peas_engine_get_default (),
+                                               IDE_TYPE_PERSPECTIVE,
+                                               NULL);
+
+  g_signal_connect_object (self->perspectives,
+                           "extension-added",
+                           G_CALLBACK (ide_workbench_add_perspective),
+                           self,
+                           0);
+
+  g_signal_connect_object (self->perspectives,
+                           "extension-removed",
+                           G_CALLBACK (ide_workbench_remove_perspective),
+                           self,
+                           0);
+
+  peas_extension_set_foreach (self->perspectives,
+                              ide_workbench_add_perspective,
+                              self);
+}
+
+static void
+ide_workbench_destroy (GtkWidget *widget)
+{
+  IdeWorkbench *self = (IdeWorkbench *)widget;
+
+  g_assert (IDE_IS_WORKBENCH (self));
+
+  g_clear_object (&self->perspectives);
+
+  GTK_WIDGET_CLASS (ide_workbench_parent_class)->destroy (widget);
+}
+
+static void
+ide_workbench_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  IdeWorkbench *self = IDE_WORKBENCH (object);
+
+  switch (prop_id)
+    {
+    case PROP_PERSPECTIVE:
+      g_value_set_object (value, ide_workbench_get_perspective (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_workbench_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  IdeWorkbench *self = IDE_WORKBENCH (object);
+
+  switch (prop_id)
+    {
+    case PROP_PERSPECTIVE:
+      ide_workbench_set_perspective (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_workbench_class_init (IdeWorkbenchClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = ide_workbench_constructed;
+  object_class->finalize = ide_workbench_finalize;
+  object_class->get_property = ide_workbench_get_property;
+  object_class->set_property = ide_workbench_set_property;
+
+  widget_class->destroy = ide_workbench_destroy;
+
+  gParamSpecs [PROP_PERSPECTIVE] =
+    g_param_spec_object ("perspective",
+                         "Perspective",
+                         "Perspective",
+                         IDE_TYPE_PERSPECTIVE,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/libide/ui/ide-workbench.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, perspectives_stack);
+  gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, perspectives_stack_switcher);
+}
+
+static void
+ide_workbench_init (IdeWorkbench *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+/**
+ * ide_workbench_get_perspective:
+ * @self: An #IdeWorkbench.
+ *
+ * Gets the current perspective.
+ *
+ * Returns: (transfer none): An #IdePerspective.
+ */
+IdePerspective *
+ide_workbench_get_perspective (IdeWorkbench *self)
+{
+  GtkWidget *visible_child;
+
+  g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
+
+  visible_child = gtk_stack_get_visible_child (self->perspectives_stack);
+
+  return IDE_PERSPECTIVE (visible_child);
+}
+
+void
+ide_workbench_set_perspective (IdeWorkbench   *self,
+                               IdePerspective *perspective)
+{
+  g_return_if_fail (IDE_IS_WORKBENCH (self));
+  g_return_if_fail (IDE_IS_PERSPECTIVE (perspective));
+  g_return_if_fail (GTK_WIDGET (self->perspectives_stack) ==
+                    gtk_widget_get_parent (GTK_WIDGET (perspective)));
+
+  gtk_stack_set_visible_child (self->perspectives_stack, GTK_WIDGET (perspective));
+}
+
+static void
+ide_workbench_views_foreach_cb (GtkWidget *widget,
+                                gpointer   user_data)
+{
+  IdeWorkbenchForeach *foreach_data = user_data;
+
+  g_assert (foreach_data);
+  g_assert (foreach_data->callback);
+
+  foreach_data->callback (widget, foreach_data->user_data);
+}
+
+static void
+ide_workbench_views_foreach_exten_cb (PeasExtensionSet *set,
+                                      PeasPluginInfo   *plugin_info,
+                                      PeasExtension    *extension,
+                                      gpointer          user_data)
+{
+  IdePerspective *perspective = user_data;
+  IdeWorkbenchForeach *foreach_data = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_PERSPECTIVE (perspective));
+  g_assert (foreach_data != NULL);
+  g_assert (foreach_data->callback != NULL);
+
+  ide_perspective_views_foreach (IDE_PERSPECTIVE (perspective),
+                                 ide_workbench_views_foreach_cb,
+                                 foreach_data);
+}
+
+/**
+ * ide_workbench_views_foreach:
+ * @self: An #IdeWorkbench.
+ * @callback: (scope call): The callback to execute
+ * @user_data: user data for @callback.
+ *
+ * Executes @callback for every #IdeView across all perspectives.
+ */
+void
+ide_workbench_views_foreach (IdeWorkbench *self,
+                             GtkCallback   callback,
+                             gpointer      user_data)
+{
+  IdeWorkbenchForeach foreach = { callback, user_data };
+
+  g_return_if_fail (IDE_IS_WORKBENCH (self));
+  g_return_if_fail (callback != NULL);
+
+  peas_extension_set_foreach (self->perspectives,
+                              ide_workbench_views_foreach_exten_cb,
+                              &foreach);
+}
+
+/**
+ * ide_workbench_get_context:
+ * @self: An #IdeWorkbench.
+ *
+ * Gets the context associated with the workbench, or %NULL.
+ *
+ * Returns: (transfer none) (nullable): An #IdeContext or %NULL.
+ */
+IdeContext *
+ide_workbench_get_context (IdeWorkbench *self)
+{
+  g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
+
+  return self->context;
+}
diff --git a/libide/ide-workbench.h b/libide/ide-workbench.h
new file mode 100644
index 0000000..d0fb127
--- /dev/null
+++ b/libide/ide-workbench.h
@@ -0,0 +1,65 @@
+/* ide-workbench.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_WORKBENCH_H
+#define IDE_WORKBENCH_H
+
+#include <gtk/gtk.h>
+
+#include "ide-context.h"
+#include "ide-perspective.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_WORKBENCH (ide_workbench_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeWorkbench, ide_workbench, IDE, WORKBENCH, GtkApplicationWindow)
+
+void            ide_workbench_open_async      (IdeWorkbench         *self,
+                                               GFile               **files,
+                                               gint                  n_files,
+                                               GCancellable         *cancellable,
+                                               GAsyncReadyCallback   callback,
+                                               gpointer              user_data);
+gboolean        ide_workbench_open_finish     (IdeWorkbench         *self,
+                                               GAsyncResult         *result,
+                                               GError              **error);
+void            ide_workbench_save_all_async  (IdeWorkbench         *self,
+                                               GCancellable         *cancellable,
+                                               GAsyncReadyCallback   callback,
+                                               gpointer              user_data);
+gboolean        ide_workbench_save_all_finish (IdeWorkbench         *self,
+                                               GAsyncResult         *result,
+                                               GError              **error);
+void            ide_workbench_focus           (IdeWorkbench         *self,
+                                               GtkWidget            *widget);
+void            ide_workbench_close           (IdeWorkbench         *self);
+IdeContext     *ide_workbench_get_context     (IdeWorkbench         *self);
+IdePerspective *ide_workbench_get_perspective (IdeWorkbench         *self);
+void            ide_workbench_set_perspective (IdeWorkbench         *self,
+                                               IdePerspective       *perspective);
+gboolean        ide_workbench_get_fullscreen  (IdeWorkbench         *self);
+void            ide_workbench_set_fullscreen  (IdeWorkbench         *self,
+                                               gboolean              fullscreen);
+void            ide_workbench_views_foreach   (IdeWorkbench         *self,
+                                               GtkCallback           callback,
+                                               gpointer              user_data);
+
+G_END_DECLS
+
+#endif /* IDE_WORKBENCH_H */
diff --git a/libide/ide.h b/libide/ide.h
index b6bd696..f50106c 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -25,6 +25,7 @@ G_BEGIN_DECLS
 
 #define IDE_INSIDE
 
+#include "ide-application.h"
 #include "ide-back-forward-item.h"
 #include "ide-back-forward-list.h"
 #include "ide-build-result.h"
@@ -56,10 +57,14 @@ G_BEGIN_DECLS
 #include "ide-highlight-engine.h"
 #include "ide-highlighter.h"
 #include "ide-indenter.h"
+#include "ide-layout-manager.h"
 #include "ide-log.h"
 #include "ide-macros.h"
 #include "ide-object.h"
 #include "ide-pattern-spec.h"
+#include "ide-perspective.h"
+#include "ide-preferences.h"
+#include "ide-preferences-addin.h"
 #include "ide-process.h"
 #include "ide-progress.h"
 #include "ide-project.h"
@@ -96,6 +101,7 @@ G_BEGIN_DECLS
 #include "ide-unsaved-files.h"
 #include "ide-vcs.h"
 #include "ide-vcs-uri.h"
+#include "ide-workbench.h"
 
 #include "directory/ide-directory-vcs.h"
 #include "doap/ide-doap-person.h"
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 647b0ae..20186d2 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -18,5 +18,7 @@
 
     <file alias="keybindings/emacs.css">../../data/keybindings/emacs.css</file>
     <file alias="keybindings/vim.css">../../data/keybindings/vim.css</file>
+
+    <file alias="ui/ide-workbench.ui">../../data/ui/ide-workbench.ui</file>
   </gresource>
 </gresources>


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