[gnome-builder/wip/chergert/perspective] prefs: start on preferences perspective



commit a4e49a060e196da534ab777513f87c384dcaa475
Author: Christian Hergert <chergert redhat com>
Date:   Fri Nov 6 01:54:34 2015 -0800

    prefs: start on preferences perspective
    
    The goal of this is to allow for editing preferences from the workbench
    window without having to open a transient dialog. While we have an
    IdePreferences interface that can be implemented for a dialog as well,
    I doubt we will do that as this seems much more convenient.
    
    We might consider embedding the persepctive in a dialog for a case where
    the user just wants to edit preferences and not run Builder, but not too
    worried about that case.

 data/theme/shared.css                            |    8 +
 data/ui/ide-preferences-font-button.ui           |   37 ++
 data/ui/ide-preferences-group.ui                 |   49 ++
 data/ui/ide-preferences-page.ui                  |   21 +
 data/ui/ide-preferences-perspective.ui           |   29 +
 data/ui/ide-preferences-switch.ui                |   52 ++
 libide/Makefile.am                               |   25 +-
 libide/ide-preferences.h                         |  105 ----
 libide/ide-workbench-private.h                   |   24 +-
 libide/ide-workbench.c                           |   71 +++-
 libide/ide.h                                     |    4 +-
 libide/{ => preferences}/ide-preferences-addin.c |    0
 libide/{ => preferences}/ide-preferences-addin.h |    0
 libide/preferences/ide-preferences-builtin.c     |  110 ++++
 libide/preferences/ide-preferences-builtin.h     |   30 ++
 libide/preferences/ide-preferences-container.c   |  129 +++++
 libide/preferences/ide-preferences-container.h   |   37 ++
 libide/preferences/ide-preferences-font-button.c |  219 ++++++++
 libide/preferences/ide-preferences-font-button.h |   32 ++
 libide/preferences/ide-preferences-group.c       |  174 ++++++
 libide/preferences/ide-preferences-group.h       |   35 ++
 libide/preferences/ide-preferences-page.c        |  151 ++++++
 libide/preferences/ide-preferences-page.h        |   39 ++
 libide/preferences/ide-preferences-perspective.c |  607 ++++++++++++++++++++++
 libide/preferences/ide-preferences-perspective.h |   32 ++
 libide/preferences/ide-preferences-spin-button.c |   93 ++++
 libide/preferences/ide-preferences-spin-button.h |   32 ++
 libide/preferences/ide-preferences-switch.c      |  269 ++++++++++
 libide/preferences/ide-preferences-switch.h      |   32 ++
 libide/{ => preferences}/ide-preferences.c       |   85 +++-
 libide/preferences/ide-preferences.h             |  153 ++++++
 libide/resources/libide.gresource.xml            |    5 +
 32 files changed, 2543 insertions(+), 146 deletions(-)
---
diff --git a/data/theme/shared.css b/data/theme/shared.css
index e5dd4f8..140d2fc 100644
--- a/data/theme/shared.css
+++ b/data/theme/shared.css
@@ -188,3 +188,11 @@ IdeWorkbench > GtkStack > GtkBox.horizontal > GtkStackSwitcher > button:hover {
   background-color: mix(@theme_bg_color, @borders, 0.25);
 }
 */
+
+IdeWorkbench IdePreferencesPerspective IdePreferencesGroup GtkListBoxRow {
+  border-bottom: 1px solid shade(@borders, 1.2);
+  padding: 10px;
+}
+IdeWorkbench IdePreferencesPerspective IdePreferencesGroup GtkListBoxRow:last-child {
+  border-bottom: none;
+}
diff --git a/data/ui/ide-preferences-font-button.ui b/data/ui/ide-preferences-font-button.ui
new file mode 100644
index 0000000..ccf5243
--- /dev/null
+++ b/data/ui/ide-preferences-font-button.ui
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="IdePreferencesFontButton" parent="IdePreferencesContainer">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="spacing">18</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkLabel" id="title">
+            <property name="hexpand">true</property>
+            <property name="visible">true</property>
+            <property name="xalign">0.0</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="font_family">
+            <property name="visible">true</property>
+            <property name="xalign">1.0</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="font_size">
+            <property name="visible">true</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/ide-preferences-group.ui b/data/ui/ide-preferences-group.ui
new file mode 100644
index 0000000..cd2ec25
--- /dev/null
+++ b/data/ui/ide-preferences-group.ui
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="IdePreferencesGroup" parent="GtkBin">
+    <property name="vexpand">false</property>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkLabel" id="title">
+            <property name="visible">true</property>
+            <property name="hexpand">true</property>
+            <property name="xalign">0.0</property>
+            <attributes>
+              <attribute name="weight" value="bold"/>
+            </attributes>
+          </object>
+          <packing>
+            <property name="padding">12</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">vertical</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkBox" id="box">
+                <property name="orientation">vertical</property>
+                <property name="spacing">12</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkFrame" id="list_box_frame">
+                <child>
+                  <object class="GtkListBox" id="list_box">
+                    <property name="selection-mode">none</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/ide-preferences-page.ui b/data/ui/ide-preferences-page.ui
new file mode 100644
index 0000000..2c4269f
--- /dev/null
+++ b/data/ui/ide-preferences-page.ui
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="IdePreferencesPage" parent="GtkBin">
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkBox" id="box">
+            <property name="halign">center</property>
+            <property name="margin">24</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">24</property>
+            <property name="visible">true</property>
+            <property name="width-request">500</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/ide-preferences-perspective.ui b/data/ui/ide-preferences-perspective.ui
new file mode 100644
index 0000000..3bb5087
--- /dev/null
+++ b/data/ui/ide-preferences-perspective.ui
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="IdePreferencesPerspective" parent="GtkBin">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkStackSidebar" id="page_stack_sidebar">
+            <property name="stack">page_stack</property>
+            <property name="visible">true</property>
+            <property name="width-request">200</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="page_stack">
+            <property name="transition-duration">333</property>
+            <property name="transition-type">crossfade</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="IdeWorkbenchHeaderBar" id="titlebar">
+    <property name="visible">true</property>
+  </object>
+</interface>
diff --git a/data/ui/ide-preferences-switch.ui b/data/ui/ide-preferences-switch.ui
new file mode 100644
index 0000000..e7b2613
--- /dev/null
+++ b/data/ui/ide-preferences-switch.ui
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.18 -->
+  <template class="IdePreferencesSwitch" parent="IdePreferencesContainer">
+    <property name="vexpand">false</property>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkBox">
+            <property name="hexpand">true</property>
+            <property name="orientation">vertical</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="title">
+                <property name="visible">true</property>
+                <property name="vexpand">true</property>
+                <property name="xalign">0.0</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="subtitle">
+                <property name="visible">true</property>
+                <property name="vexpand">true</property>
+                <property name="xalign">0.0</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+                <attributes>
+                  <attribute name="scale" value="0.83333"/>
+                </attributes>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkImage" id="image">
+            <property name="icon-name">object-select-symbolic</property>
+            <property name="valign">center</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkSwitch" id="widget">
+            <property name="visible">true</property>
+            <property name="valign">center</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index e7ab03b..2f10411 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -99,10 +99,6 @@ libide_1_0_la_public_sources = \
        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 \
@@ -204,6 +200,10 @@ libide_1_0_la_public_sources = \
        ide.h \
        local/ide-local-device.c \
        local/ide-local-device.h \
+       preferences/ide-preferences.c \
+       preferences/ide-preferences.h \
+       preferences/ide-preferences-addin.c \
+       preferences/ide-preferences-addin.h \
        $(NULL)
 
 libide_1_0_la_SOURCES = \
@@ -270,6 +270,22 @@ libide_1_0_la_SOURCES = \
        modelines/ide-modelines-file-settings.h \
        modelines/modeline-parser.c \
        modelines/modeline-parser.h \
+       preferences/ide-preferences-builtin.c \
+       preferences/ide-preferences-builtin.h \
+       preferences/ide-preferences-container.c \
+       preferences/ide-preferences-container.h \
+       preferences/ide-preferences-font-button.c \
+       preferences/ide-preferences-font-button.h \
+       preferences/ide-preferences-group.c \
+       preferences/ide-preferences-group.h \
+       preferences/ide-preferences-page.c \
+       preferences/ide-preferences-page.h \
+       preferences/ide-preferences-perspective.c \
+       preferences/ide-preferences-perspective.h \
+       preferences/ide-preferences-spin-button.c \
+       preferences/ide-preferences-spin-button.h \
+       preferences/ide-preferences-switch.c \
+       preferences/ide-preferences-switch.h \
        theatrics/ide-box-theatric.c \
        theatrics/ide-box-theatric.h \
        util/ide-cairo.c \
@@ -312,6 +328,7 @@ libide_1_0_la_includes = \
        -I$(srcdir)/gsettings \
        -I$(srcdir)/local \
        -I$(srcdir)/modelines \
+       -I$(srcdir)/preferences \
        -I$(srcdir)/resources \
        -I$(srcdir)/theatrics \
        -I$(srcdir)/util \
diff --git a/libide/ide-workbench-private.h b/libide/ide-workbench-private.h
index f917bdc..ec8cecd 100644
--- a/libide/ide-workbench-private.h
+++ b/libide/ide-workbench-private.h
@@ -23,26 +23,28 @@
 
 #include "ide-editor-perspective.h"
 #include "ide-greeter-perspective.h"
+#include "ide-preferences-perspective.h"
 #include "ide-workbench.h"
 
 G_BEGIN_DECLS
 
 struct _IdeWorkbench
 {
-  GtkApplicationWindow   parent;
+  GtkApplicationWindow       parent;
 
-  IdeContext            *context;
-  PeasExtensionSet      *addins;
+  IdeContext                *context;
+  PeasExtensionSet          *addins;
 
-  IdePerspective        *perspective;
+  IdePerspective            *perspective;
 
-  GtkStack              *top_stack;
-  GtkStack              *titlebar_stack;
-  IdeEditorPerspective  *editor_perspective;
-  IdeGreeterPerspective *greeter_perspective;
-  GtkStack              *perspectives_stack;
-  GtkStackSwitcher      *perspectives_stack_switcher;
-  GtkPopover            *perspectives_popover;
+  GtkStack                  *top_stack;
+  GtkStack                  *titlebar_stack;
+  IdeEditorPerspective      *editor_perspective;
+  IdeGreeterPerspective     *greeter_perspective;
+  IdePreferencesPerspective *preferences_perspective;
+  GtkStack                  *perspectives_stack;
+  GtkStackSwitcher          *perspectives_stack_switcher;
+  GtkPopover                *perspectives_popover;
 };
 
 typedef struct
diff --git a/libide/ide-workbench.c b/libide/ide-workbench.c
index 9332d96..c09a8c9 100644
--- a/libide/ide-workbench.c
+++ b/libide/ide-workbench.c
@@ -36,6 +36,34 @@ enum {
 static GParamSpec *properties [LAST_PROP];
 
 static void
+ide_workbench_notify_visible_child (IdeWorkbench *self,
+                                    GParamSpec   *pspec,
+                                    GtkStack     *stack)
+{
+  IdePerspective *perspective;
+
+  g_assert (IDE_IS_WORKBENCH (self));
+  g_assert (GTK_IS_STACK (stack));
+
+  perspective = IDE_PERSPECTIVE (gtk_stack_get_visible_child (stack));
+
+  if (perspective != NULL)
+    {
+      GActionGroup *actions;
+      gchar *id;
+
+      id = ide_perspective_get_id (perspective);
+      gtk_stack_set_visible_child_name (self->titlebar_stack, id);
+
+      actions = ide_perspective_get_actions (perspective);
+      gtk_widget_insert_action_group (GTK_WIDGET (self), "perspective", actions);
+
+      g_clear_object (&actions);
+      g_free (id);
+    }
+}
+
+static void
 ide_workbench_finalize (GObject *object)
 {
   IdeWorkbench *self = (IdeWorkbench *)object;
@@ -163,15 +191,32 @@ ide_workbench_init_editor (IdeWorkbench *self)
 }
 
 static void
+ide_workbench_init_preferences (IdeWorkbench *self)
+{
+  g_assert (IDE_IS_WORKBENCH (self));
+
+  self->preferences_perspective = g_object_new (IDE_TYPE_PREFERENCES_PERSPECTIVE,
+                                                "visible", TRUE,
+                                                NULL);
+  ide_workbench_add_perspective (self, IDE_PERSPECTIVE (self->preferences_perspective));
+}
+
+static void
 ide_workbench_init (IdeWorkbench *self)
 {
   gtk_widget_init_template (GTK_WIDGET (self));
 
   ide_workbench_init_greeter (self);
   ide_workbench_init_editor (self);
-  ide_workbench_init_editor (self);
+  ide_workbench_init_preferences (self);
   ide_window_settings_register (GTK_WINDOW (self));
 
+  g_signal_connect_object (self->perspectives_stack,
+                           "notify::visible-child",
+                           G_CALLBACK (ide_workbench_notify_visible_child),
+                           self,
+                           G_CONNECT_SWAPPED);
+
   gtk_stack_set_visible_child (self->top_stack, GTK_WIDGET (self->greeter_perspective));
 }
 
@@ -375,23 +420,35 @@ void
 ide_workbench_set_perspective (IdeWorkbench   *self,
                                IdePerspective *perspective)
 {
-  GActionGroup *actions;
-  const gchar *id;
   GtkStack *stack;
+  gchar *id;
 
   g_return_if_fail (IDE_IS_WORKBENCH (self));
   g_return_if_fail (IDE_IS_PERSPECTIVE (perspective));
 
+  /*
+   * NOTE:
+   *
+   * The greeter perspective is special cased. We want to use the same window for the greeter
+   * and the workbench, so it has a toplevel stack with slightly different semantics than the
+   * other perspectives. We don't show the perspective sidebar, and we use a slide-right-left
+   * animation.
+   *
+   * Once we leave the greeter, we do not come back to it. We only use crossfade animations from
+   * then on.
+   */
+
   if (IDE_IS_GREETER_PERSPECTIVE (perspective))
     stack = self->top_stack;
   else
     stack = self->perspectives_stack;
 
   id = ide_perspective_get_id (perspective);
-  gtk_stack_set_visible_child_name (self->titlebar_stack, id);
   gtk_stack_set_visible_child_name (stack, id);
+  gtk_stack_set_visible_child_name (self->titlebar_stack, id);
+
+  if (!IDE_IS_GREETER_PERSPECTIVE (perspective))
+    gtk_stack_set_transition_type (self->titlebar_stack, GTK_STACK_TRANSITION_TYPE_CROSSFADE);
 
-  actions = ide_perspective_get_actions (perspective);
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "perspective", actions);
-  g_clear_object (&actions);
+  g_free (id);
 }
diff --git a/libide/ide.h b/libide/ide.h
index f50106c..7168b3b 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -63,8 +63,6 @@ G_BEGIN_DECLS
 #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"
@@ -109,6 +107,8 @@ G_BEGIN_DECLS
 #include "git/ide-git-remote-callbacks.h"
 #include "git/ide-git-vcs.h"
 #include "local/ide-local-device.h"
+#include "preferences/ide-preferences.h"
+#include "preferences/ide-preferences-addin.h"
 #include "util/ide-line-reader.h"
 #include "util/ide-list-inline.h"
 
diff --git a/libide/ide-preferences-addin.c b/libide/preferences/ide-preferences-addin.c
similarity index 100%
rename from libide/ide-preferences-addin.c
rename to libide/preferences/ide-preferences-addin.c
diff --git a/libide/ide-preferences-addin.h b/libide/preferences/ide-preferences-addin.h
similarity index 100%
rename from libide/ide-preferences-addin.h
rename to libide/preferences/ide-preferences-addin.h
diff --git a/libide/preferences/ide-preferences-builtin.c b/libide/preferences/ide-preferences-builtin.c
new file mode 100644
index 0000000..3f8594c
--- /dev/null
+++ b/libide/preferences/ide-preferences-builtin.c
@@ -0,0 +1,110 @@
+/* ide-preferences-builtin.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 <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+#include <libpeas/peas.h>
+
+#include "ide-preferences-builtin.h"
+
+static void
+ide_preferences_builtin_register_plugins (IdePreferences *preferences)
+{
+  PeasEngine *engine;
+  const GList *list;
+  guint i = 0;
+
+  g_assert (IDE_IS_PREFERENCES (preferences));
+
+  engine = peas_engine_get_default ();
+  list = peas_engine_get_plugin_list (engine);
+
+  ide_preferences_add_page (preferences, "plugins", _("Plugins"), 700);
+  ide_preferences_add_list_group (preferences, "plugins", "plugins", _("Installed Plugins"), 0);
+
+  for (; list; list = list->next, i++)
+    {
+      PeasPluginInfo *plugin_info = list->data;
+      const gchar *desc;
+      const gchar *name;
+
+      name = peas_plugin_info_get_name (plugin_info);
+      desc = peas_plugin_info_get_description (plugin_info);
+
+      /* TODO: support custom path (and use right schema/key) */
+      ide_preferences_add_switch (preferences, "plugins", "plugins", "org.gnome.builder.editor", 
"show-line-numbers", NULL, name, desc, NULL, i);
+    }
+}
+
+static void
+ide_preferences_builtin_register_appearance (IdePreferences *preferences)
+{
+  GtkSourceStyleSchemeManager *manager;
+  const gchar * const *scheme_ids;
+  gint i;
+
+  ide_preferences_add_page (preferences, "appearance", _("Appearance"), 0);
+
+  ide_preferences_add_group (preferences, "appearance", "basic", NULL, 0);
+  ide_preferences_add_switch (preferences, "appearance", "basic", "org.gnome.builder", "night-mode", NULL, 
_("Dark Theme"), _("Whether Builder should use a dark theme"), _("dark theme"), 0);
+  ide_preferences_add_switch (preferences, "appearance", "basic", "org.gnome.builder", "animations", NULL, 
_("Animations"), _("Whether animations should be used when appropriate"), _("animations"), 100);
+
+  ide_preferences_add_list_group (preferences, "appearance", "font", _("Font"), 100);
+  ide_preferences_add_font_button (preferences, "appearance", "font", "org.gnome.builder.editor", 
"font-name", _("Editor"), _("editor font monospace"), 0);
+
+  ide_preferences_add_list_group (preferences, "appearance", "schemes", _("Themes"), 200);
+
+  manager = gtk_source_style_scheme_manager_get_default ();
+  scheme_ids = gtk_source_style_scheme_manager_get_scheme_ids (manager);
+
+  for (i = 0; scheme_ids [i]; i++)
+    {
+      g_autofree gchar *variant_str = NULL;
+      GtkSourceStyleScheme *scheme;
+      const gchar *title;
+
+      variant_str = g_strdup_printf ("\"%s\"", scheme_ids [i]);
+      scheme = gtk_source_style_scheme_manager_get_scheme (manager, scheme_ids [i]);
+      title = gtk_source_style_scheme_get_name (scheme);
+
+      ide_preferences_add_radio (preferences, "appearance", "schemes", "org.gnome.builder.editor", 
"style-scheme-name", variant_str, title, NULL, title, i);
+    }
+
+  ide_preferences_add_list_group (preferences, "appearance", "draw-spaces", _("Whitespace Characters"), 300);
+  ide_preferences_add_switch (preferences, "appearance", "draw-spaces", "org.gnome.builder.editor", 
"draw-spaces", "\"space\"", _("Spaces"), NULL, NULL, 0);
+  ide_preferences_add_switch (preferences, "appearance", "draw-spaces", "org.gnome.builder.editor", 
"draw-spaces", "\"tab\"", _("Tabs"), NULL, NULL, 1);
+  ide_preferences_add_switch (preferences, "appearance", "draw-spaces", "org.gnome.builder.editor", 
"draw-spaces", "\"newline\"", _("New line and carriage return"), NULL, NULL, 2);
+  ide_preferences_add_switch (preferences, "appearance", "draw-spaces", "org.gnome.builder.editor", 
"draw-spaces", "\"nbsp\"", _("Non-breaking spaces"), NULL, NULL, 3);
+  ide_preferences_add_switch (preferences, "appearance", "draw-spaces", "org.gnome.builder.editor", 
"draw-spaces", "\"text\"", _("Spaces inside of text"), NULL, NULL, 4);
+  ide_preferences_add_switch (preferences, "appearance", "draw-spaces", "org.gnome.builder.editor", 
"draw-spaces", "\"trailing\"", _("Trailing Only"), NULL, NULL, 5);
+  ide_preferences_add_switch (preferences, "appearance", "draw-spaces", "org.gnome.builder.editor", 
"draw-spaces", "\"leading\"", _("Leading Only"), NULL, NULL, 6);
+}
+
+void
+_ide_preferences_builtin_register (IdePreferences *preferences)
+{
+  ide_preferences_builtin_register_appearance (preferences);
+
+  ide_preferences_add_page (preferences, "editor", _("Editor"), 100);
+  ide_preferences_add_page (preferences, "languages", _("Programming Languages"), 200);
+  ide_preferences_add_page (preferences, "code-insight", _("Code Insight"), 300);
+  ide_preferences_add_page (preferences, "keyboard", _("Keyboard"), 400);
+  ide_preferences_add_page (preferences, "vcs", _("Version Control"), 600);
+
+  ide_preferences_builtin_register_plugins (preferences);
+}
diff --git a/libide/preferences/ide-preferences-builtin.h b/libide/preferences/ide-preferences-builtin.h
new file mode 100644
index 0000000..c4bdc6c
--- /dev/null
+++ b/libide/preferences/ide-preferences-builtin.h
@@ -0,0 +1,30 @@
+/* ide-preferences-builtin.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_BUILTIN_H
+#define IDE_PREFERENCES_BUILTIN_H
+
+#include "ide-preferences.h"
+
+G_BEGIN_DECLS
+
+void _ide_preferences_builtin_register (IdePreferences *preferences);
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_BUILTIN_H */
diff --git a/libide/preferences/ide-preferences-container.c b/libide/preferences/ide-preferences-container.c
new file mode 100644
index 0000000..b2259f7
--- /dev/null
+++ b/libide/preferences/ide-preferences-container.c
@@ -0,0 +1,129 @@
+/* ide-preferences-container.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-container.h"
+
+typedef struct
+{
+  GtkBin parent_instance;
+  gint   priority;
+  gchar *keywords;
+} IdePreferencesContainerPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdePreferencesContainer, ide_preferences_container, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  PROP_KEYWORDS,
+  PROP_PRIORITY,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_preferences_container_finalize (GObject *object)
+{
+  IdePreferencesContainer *self = (IdePreferencesContainer *)object;
+  IdePreferencesContainerPrivate *priv = ide_preferences_container_get_instance_private (self);
+
+  g_clear_pointer (&priv->keywords, g_free);
+
+  G_OBJECT_CLASS (ide_preferences_container_parent_class)->finalize (object);
+}
+
+static void
+ide_preferences_container_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+  IdePreferencesContainer *self = IDE_PREFERENCES_CONTAINER (object);
+  IdePreferencesContainerPrivate *priv = ide_preferences_container_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_KEYWORDS:
+      g_value_set_string (value, priv->keywords);
+      break;
+
+    case PROP_PRIORITY:
+      g_value_set_int (value, priv->priority);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_container_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+  IdePreferencesContainer *self = IDE_PREFERENCES_CONTAINER (object);
+  IdePreferencesContainerPrivate *priv = ide_preferences_container_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_KEYWORDS:
+      priv->keywords = g_value_dup_string (value);
+      break;
+
+    case PROP_PRIORITY:
+      priv->priority = g_value_get_int (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_container_class_init (IdePreferencesContainerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_preferences_container_finalize;
+  object_class->get_property = ide_preferences_container_get_property;
+  object_class->set_property = ide_preferences_container_set_property;
+
+  properties [PROP_KEYWORDS] =
+    g_param_spec_string ("keywords",
+                         "Keywords",
+                         "Search keywords for the widget.",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PRIORITY] =
+    g_param_spec_int ("priority",
+                      "Priority",
+                      "The widget priority within the group.",
+                      G_MININT,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_preferences_container_init (IdePreferencesContainer *self)
+{
+}
diff --git a/libide/preferences/ide-preferences-container.h b/libide/preferences/ide-preferences-container.h
new file mode 100644
index 0000000..c5b2cad
--- /dev/null
+++ b/libide/preferences/ide-preferences-container.h
@@ -0,0 +1,37 @@
+/* ide-preferences-container.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_CONTAINER_H
+#define IDE_PREFERENCES_CONTAINER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_CONTAINER (ide_preferences_container_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdePreferencesContainer, ide_preferences_container, IDE, PREFERENCES_CONTAINER, 
GtkBin)
+
+struct _IdePreferencesContainerClass
+{
+  GtkBinClass parent_class;
+};
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_CONTAINER_H */
diff --git a/libide/preferences/ide-preferences-font-button.c 
b/libide/preferences/ide-preferences-font-button.c
new file mode 100644
index 0000000..ecb247d
--- /dev/null
+++ b/libide/preferences/ide-preferences-font-button.c
@@ -0,0 +1,219 @@
+/* ide-preferences-font-button.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-font-button.h"
+
+struct _IdePreferencesFontButton
+{
+  GtkBin     parent_instance;
+
+  GSettings *settings;
+  gchar     *schema_id;
+  gchar     *key;
+
+  GtkLabel  *title;
+  GtkLabel  *font_family;
+  GtkLabel  *font_size;
+};
+
+G_DEFINE_TYPE (IdePreferencesFontButton, ide_preferences_font_button, IDE_TYPE_PREFERENCES_CONTAINER)
+
+enum {
+  PROP_0,
+  PROP_KEY,
+  PROP_SCHEMA_ID,
+  PROP_TITLE,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_preferences_font_button_changed (IdePreferencesFontButton *self,
+                                     const gchar              *key,
+                                     GSettings                *settings)
+{
+  PangoFontDescription *font_desc;
+  gchar *name;
+
+  g_assert (IDE_IS_PREFERENCES_FONT_BUTTON (self));
+  g_assert (key != NULL);
+  g_assert (G_IS_SETTINGS (settings));
+
+  name = g_settings_get_string (settings, key);
+  font_desc = pango_font_description_from_string (name);
+
+  if (font_desc != NULL)
+    {
+      gchar *font_size;
+
+      gtk_label_set_label (self->font_family, pango_font_description_get_family (font_desc));
+      font_size = g_strdup_printf ("%dpt", pango_font_description_get_size (font_desc) / PANGO_SCALE);
+      gtk_label_set_label (self->font_size, font_size);
+      g_free (font_size);
+    }
+
+  g_clear_pointer (&font_desc, pango_font_description_free);
+  g_free (name);
+}
+
+static void
+ide_preferences_font_button_constructed (GObject *object)
+{
+  IdePreferencesFontButton *self = (IdePreferencesFontButton *)object;
+  GSettingsSchemaSource *source;
+  g_autoptr(GSettingsSchema) schema = NULL;
+  g_autofree gchar *signal_detail = NULL;
+
+  g_assert (IDE_IS_PREFERENCES_FONT_BUTTON (self));
+
+  source = g_settings_schema_source_get_default ();
+  schema = g_settings_schema_source_lookup (source, self->schema_id, TRUE);
+
+  if (schema == NULL || !g_settings_schema_has_key (schema, self->key))
+    {
+      gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
+      goto chainup;
+    }
+
+  self->settings = g_settings_new (self->schema_id);
+  signal_detail = g_strdup_printf ("changed::%s", self->key);
+
+  g_signal_connect_object (self->settings,
+                           signal_detail,
+                           G_CALLBACK (ide_preferences_font_button_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_preferences_font_button_changed (self, self->key, self->settings);
+
+chainup:
+  G_OBJECT_CLASS (ide_preferences_font_button_parent_class)->constructed (object);
+}
+
+static void
+ide_preferences_font_button_finalize (GObject *object)
+{
+  IdePreferencesFontButton *self = (IdePreferencesFontButton *)object;
+
+  g_clear_object (&self->settings);
+  g_clear_pointer (&self->schema_id, g_free);
+  g_clear_pointer (&self->key, g_free);
+
+  G_OBJECT_CLASS (ide_preferences_font_button_parent_class)->finalize (object);
+}
+
+static void
+ide_preferences_font_button_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  IdePreferencesFontButton *self = IDE_PREFERENCES_FONT_BUTTON (object);
+
+  switch (prop_id)
+    {
+    case PROP_KEY:
+      g_value_set_string (value, self->key);
+      break;
+
+    case PROP_SCHEMA_ID:
+      g_value_set_string (value, self->schema_id);
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (self->title));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_font_button_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  IdePreferencesFontButton *self = IDE_PREFERENCES_FONT_BUTTON (object);
+
+  switch (prop_id)
+    {
+    case PROP_KEY:
+      self->key = g_value_dup_string (value);
+      break;
+
+    case PROP_SCHEMA_ID:
+      self->schema_id = g_value_dup_string (value);
+      break;
+
+    case PROP_TITLE:
+      gtk_label_set_label (self->title, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_font_button_class_init (IdePreferencesFontButtonClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = ide_preferences_font_button_constructed;
+  object_class->finalize = ide_preferences_font_button_finalize;
+  object_class->get_property = ide_preferences_font_button_get_property;
+  object_class->set_property = ide_preferences_font_button_set_property;
+
+  properties [PROP_KEY] =
+    g_param_spec_string ("key",
+                         "Key",
+                         "Key",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SCHEMA_ID] =
+    g_param_spec_string ("schema-id",
+                         "Schema Id",
+                         "Schema Id",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "Title",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-preferences-font-button.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesFontButton, title);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesFontButton, font_family);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesFontButton, font_size);
+}
+
+static void
+ide_preferences_font_button_init (IdePreferencesFontButton *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/libide/preferences/ide-preferences-font-button.h 
b/libide/preferences/ide-preferences-font-button.h
new file mode 100644
index 0000000..50a7acd
--- /dev/null
+++ b/libide/preferences/ide-preferences-font-button.h
@@ -0,0 +1,32 @@
+/* ide-preferences-font-button.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_FONT_BUTTON_H
+#define IDE_PREFERENCES_FONT_BUTTON_H
+
+#include "ide-preferences-container.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_FONT_BUTTON (ide_preferences_font_button_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePreferencesFontButton, ide_preferences_font_button, IDE, PREFERENCES_FONT_BUTTON, 
GtkBin)
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_FONT_BUTTON_H */
diff --git a/libide/preferences/ide-preferences-group.c b/libide/preferences/ide-preferences-group.c
new file mode 100644
index 0000000..10cc4e6
--- /dev/null
+++ b/libide/preferences/ide-preferences-group.c
@@ -0,0 +1,174 @@
+/* ide-preferences-group.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-container.h"
+#include "ide-preferences-group.h"
+
+struct _IdePreferencesGroup
+{
+  GtkBin      parent_instance;
+
+  gint        priority;
+  guint       is_list : 1;
+
+  GtkLabel   *title;
+  GtkBox     *box;
+  GtkListBox *list_box;
+  GtkFrame   *list_box_frame;
+};
+
+G_DEFINE_TYPE (IdePreferencesGroup, ide_preferences_group, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  PROP_IS_LIST,
+  PROP_PRIORITY,
+  PROP_TITLE,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_preferences_group_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  IdePreferencesGroup *self = IDE_PREFERENCES_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_IS_LIST:
+      g_value_set_boolean (value, self->is_list);
+      break;
+
+    case PROP_PRIORITY:
+      g_value_set_int (value, self->priority);
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (self->title));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_group_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  IdePreferencesGroup *self = IDE_PREFERENCES_GROUP (object);
+
+  switch (prop_id)
+    {
+    case PROP_IS_LIST:
+      self->is_list = g_value_get_boolean (value);
+      gtk_widget_set_visible (GTK_WIDGET (self->box), !self->is_list);
+      gtk_widget_set_visible (GTK_WIDGET (self->list_box_frame), self->is_list);
+      break;
+
+    case PROP_PRIORITY:
+      self->priority = g_value_get_int (value);
+      break;
+
+    case PROP_TITLE:
+      gtk_label_set_label (self->title, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_group_class_init (IdePreferencesGroupClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = ide_preferences_group_get_property;
+  object_class->set_property = ide_preferences_group_set_property;
+
+  properties [PROP_IS_LIST] =
+    g_param_spec_boolean ("is-list",
+                          "Is List",
+                          "If the group should be rendered as a listbox.",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PRIORITY] =
+    g_param_spec_int ("priority",
+                      "Priority",
+                      "Priority",
+                      G_MININT,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "Title",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-preferences-group.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesGroup, box);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesGroup, list_box);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesGroup, list_box_frame);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesGroup, title);
+}
+
+static void
+ide_preferences_group_init (IdePreferencesGroup *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ide_preferences_group_add (IdePreferencesGroup *self,
+                           GtkWidget           *widget)
+{
+  gint position = -1;
+
+  g_return_if_fail (IDE_IS_PREFERENCES_GROUP (self));
+  g_return_if_fail (IDE_IS_PREFERENCES_CONTAINER (widget));
+
+  if (self->is_list)
+    {
+      GtkWidget *row;
+
+      row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+                          "child", widget,
+                          "focus-on-click", FALSE,
+                          "visible", TRUE,
+                          NULL);
+      gtk_container_add (GTK_CONTAINER (self->list_box), row);
+    }
+  else
+    gtk_container_add_with_properties (GTK_CONTAINER (self->box), widget,
+                                       "position", position,
+                                       NULL);
+}
diff --git a/libide/preferences/ide-preferences-group.h b/libide/preferences/ide-preferences-group.h
new file mode 100644
index 0000000..c4b0be9
--- /dev/null
+++ b/libide/preferences/ide-preferences-group.h
@@ -0,0 +1,35 @@
+/* ide-preferences-group.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_GROUP_H
+#define IDE_PREFERENCES_GROUP_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_GROUP (ide_preferences_group_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePreferencesGroup, ide_preferences_group, IDE, PREFERENCES_GROUP, GtkBin)
+
+void ide_preferences_group_add (IdePreferencesGroup *self,
+                                GtkWidget           *widget);
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_GROUP_H */
diff --git a/libide/preferences/ide-preferences-page.c b/libide/preferences/ide-preferences-page.c
new file mode 100644
index 0000000..7a680a1
--- /dev/null
+++ b/libide/preferences/ide-preferences-page.c
@@ -0,0 +1,151 @@
+/* ide-preferences-page.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 <glib/gi18n.h>
+
+#include "ide-preferences-group.h"
+#include "ide-preferences-page.h"
+
+struct _IdePreferencesPage
+{
+  GtkBin      parent_instance;
+
+  gint        priority;
+
+  GtkBox     *box;
+  GHashTable *groups_by_name;
+};
+
+enum {
+  PROP_0,
+  PROP_PRIORITY,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE (IdePreferencesPage, ide_preferences_page, GTK_TYPE_BIN)
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_preferences_page_finalize (GObject *object)
+{
+  IdePreferencesPage *self = (IdePreferencesPage *)object;
+
+  g_clear_pointer (&self->groups_by_name, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_preferences_page_parent_class)->finalize (object);
+}
+
+static void
+ide_preferences_page_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  IdePreferencesPage *self = IDE_PREFERENCES_PAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_PRIORITY:
+      g_value_set_int (value, self->priority);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_page_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  IdePreferencesPage *self = IDE_PREFERENCES_PAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_PRIORITY:
+      self->priority = g_value_get_int (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_page_class_init (IdePreferencesPageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_preferences_page_finalize;
+  object_class->get_property = ide_preferences_page_get_property;
+  object_class->set_property = ide_preferences_page_set_property;
+
+  properties [PROP_PRIORITY] =
+    g_param_spec_int ("priority",
+                      "Priority",
+                      "Priority",
+                      G_MININT,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-preferences-page.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesPage, box);
+}
+
+static void
+ide_preferences_page_init (IdePreferencesPage *self)
+{
+  self->groups_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+void
+ide_preferences_page_add_group (IdePreferencesPage  *self,
+                                IdePreferencesGroup *group)
+{
+  gchar *name = NULL;
+  gint position = -1;
+
+  g_return_if_fail (IDE_IS_PREFERENCES_PAGE (self));
+  g_return_if_fail (IDE_IS_PREFERENCES_GROUP (group));
+
+  gtk_container_add_with_properties (GTK_CONTAINER (self->box), GTK_WIDGET (group),
+                                     "position", position,
+                                     NULL);
+
+  g_object_get (group, "name", &name, NULL);
+  g_hash_table_insert (self->groups_by_name, name, group);
+}
+
+IdePreferencesGroup *
+ide_preferences_page_get_group (IdePreferencesPage *self,
+                                const gchar        *name)
+{
+  g_return_val_if_fail (IDE_IS_PREFERENCES_PAGE (self), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  return g_hash_table_lookup (self->groups_by_name, name);
+}
diff --git a/libide/preferences/ide-preferences-page.h b/libide/preferences/ide-preferences-page.h
new file mode 100644
index 0000000..840fe10
--- /dev/null
+++ b/libide/preferences/ide-preferences-page.h
@@ -0,0 +1,39 @@
+/* ide-preferences-page.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_PAGE_H
+#define IDE_PREFERENCES_PAGE_H
+
+#include <gtk/gtk.h>
+
+#include "ide-preferences-group.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_PAGE (ide_preferences_page_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePreferencesPage, ide_preferences_page, IDE, PREFERENCES_PAGE, GtkBin)
+
+void                 ide_preferences_page_add_group (IdePreferencesPage  *self,
+                                                     IdePreferencesGroup *group);
+IdePreferencesGroup *ide_preferences_page_get_group (IdePreferencesPage  *self,
+                                                     const gchar         *group_name);
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_PAGE_H */
diff --git a/libide/preferences/ide-preferences-perspective.c 
b/libide/preferences/ide-preferences-perspective.c
new file mode 100644
index 0000000..2a80e7f
--- /dev/null
+++ b/libide/preferences/ide-preferences-perspective.c
@@ -0,0 +1,607 @@
+/* ide-preferences-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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-preferences-perspective"
+
+#include <glib/gi18n.h>
+
+#include "ide-perspective.h"
+#include "ide-preferences.h"
+#include "ide-preferences-builtin.h"
+#include "ide-preferences-container.h"
+#include "ide-preferences-font-button.h"
+#include "ide-preferences-group.h"
+#include "ide-preferences-page.h"
+#include "ide-preferences-perspective.h"
+#include "ide-preferences-spin-button.h"
+#include "ide-preferences-switch.h"
+#include "ide-workbench-header-bar.h"
+
+struct _IdePreferencesPerspective
+{
+  GtkBin                 parent_instance;
+
+  guint                  last_widget_id;
+
+  GSequence             *pages;
+  GHashTable            *widgets;
+
+  GtkStack              *page_stack;
+  GtkStackSwitcher      *page_stack_sidebar;
+  IdeWorkbenchHeaderBar *titlebar;
+};
+
+static void ide_preferences_iface_init (IdePreferencesInterface *iface);
+static void ide_perspective_iface_init (IdePerspectiveInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdePreferencesPerspective, ide_preferences_perspective, GTK_TYPE_BIN, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_PREFERENCES, ide_preferences_iface_init)
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_PERSPECTIVE, ide_perspective_iface_init))
+
+enum {
+  PROP_0,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static gint
+sort_by_priority (gconstpointer a,
+                  gconstpointer b,
+                  gpointer      user_data)
+{
+  gint prioritya = 0;
+  gint priorityb = 0;
+
+  g_object_get ((gpointer)a, "priority", &prioritya, NULL);
+  g_object_get ((gpointer)b, "priority", &priorityb, NULL);
+
+  return prioritya - priorityb;
+}
+
+static void
+ide_preferences_perspective_constructed (GObject *object)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)object;
+
+  G_OBJECT_CLASS (ide_preferences_perspective_parent_class)->constructed (object);
+
+  _ide_preferences_builtin_register (IDE_PREFERENCES (self));
+}
+
+static void
+ide_preferences_perspective_finalize (GObject *object)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)object;
+
+  g_clear_pointer (&self->pages, g_sequence_free);
+  g_clear_pointer (&self->widgets, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_preferences_perspective_parent_class)->finalize (object);
+}
+
+static void
+ide_preferences_perspective_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  //IdePreferencesPerspective *self = IDE_PREFERENCES_PERSPECTIVE (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_perspective_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  //IdePreferencesPerspective *self = IDE_PREFERENCES_PERSPECTIVE (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_perspective_class_init (IdePreferencesPerspectiveClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = ide_preferences_perspective_constructed;
+  object_class->finalize = ide_preferences_perspective_finalize;
+  object_class->get_property = ide_preferences_perspective_get_property;
+  object_class->set_property = ide_preferences_perspective_set_property;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-preferences-perspective.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesPerspective, page_stack_sidebar);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesPerspective, page_stack);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesPerspective, titlebar);
+}
+
+static void
+ide_preferences_perspective_init (IdePreferencesPerspective *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->pages = g_sequence_new (NULL);
+  self->widgets = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+ide_preferences_perspective_add_page (IdePreferences *preferences,
+                                      const gchar    *page_name,
+                                      const gchar    *title,
+                                      gint            priority)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)preferences;
+  IdePreferencesPage *page;
+  GSequenceIter *iter;
+
+  g_assert (IDE_IS_PREFERENCES (preferences));
+  g_assert (IDE_IS_PREFERENCES_PERSPECTIVE (self));
+  g_assert (page_name != NULL);
+  g_assert (title != NULL);
+
+  if (gtk_stack_get_child_by_name (self->page_stack, page_name))
+    {
+      g_warning ("A preference page named %s has already been registered.",
+                 page_name);
+      return;
+    }
+
+  page = g_object_new (IDE_TYPE_PREFERENCES_PAGE,
+                       "priority", priority,
+                       "visible", TRUE,
+                       NULL);
+  iter = g_sequence_insert_sorted (self->pages, page, sort_by_priority, NULL);
+  gtk_container_add_with_properties (GTK_CONTAINER (self->page_stack), GTK_WIDGET (page),
+                                     "position", g_sequence_iter_get_position (iter),
+                                     "name", page_name,
+                                     "title", title,
+                                     NULL);
+}
+
+static void
+ide_preferences_perspective_add_group (IdePreferences *preferences,
+                                       const gchar    *page_name,
+                                       const gchar    *group_name,
+                                       const gchar    *title,
+                                       gint            priority)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)preferences;
+  IdePreferencesGroup *group;
+  GtkWidget *page;
+
+  g_assert (IDE_IS_PREFERENCES_PERSPECTIVE (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+
+  page = gtk_stack_get_child_by_name (self->page_stack, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return;
+    }
+
+  group = g_object_new (IDE_TYPE_PREFERENCES_GROUP,
+                        "name", group_name,
+                        "priority", priority,
+                        "title", title,
+                        "visible", TRUE,
+                        NULL);
+  ide_preferences_page_add_group (IDE_PREFERENCES_PAGE (page), group);
+}
+
+static void
+ide_preferences_perspective_add_list_group  (IdePreferences *preferences,
+                                             const gchar    *page_name,
+                                             const gchar    *group_name,
+                                             const gchar    *title,
+                                             gint            priority)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)preferences;
+  IdePreferencesGroup *group;
+  GtkWidget *page;
+
+  g_assert (IDE_IS_PREFERENCES_PERSPECTIVE (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+
+  page = gtk_stack_get_child_by_name (self->page_stack, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return;
+    }
+
+  group = g_object_new (IDE_TYPE_PREFERENCES_GROUP,
+                        "is-list", TRUE,
+                        "name", group_name,
+                        "priority", priority,
+                        "title", title,
+                        "visible", TRUE,
+                        NULL);
+  ide_preferences_page_add_group (IDE_PREFERENCES_PAGE (page), group);
+}
+
+static guint
+ide_preferences_perspective_add_radio (IdePreferences *preferences,
+                                       const gchar    *page_name,
+                                       const gchar    *group_name,
+                                       const gchar    *schema_id,
+                                       const gchar    *key,
+                                       const gchar    *variant_string,
+                                       const gchar    *title,
+                                       const gchar    *subtitle,
+                                       const gchar    *keywords,
+                                       gint            priority)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)preferences;
+  IdePreferencesSwitch *widget;
+  IdePreferencesGroup *group;
+  g_autoptr(GVariant) variant = NULL;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (IDE_IS_PREFERENCES_PERSPECTIVE (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = gtk_stack_get_child_by_name (self->page_stack, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = ide_preferences_page_get_group (IDE_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  if (variant_string != NULL)
+    {
+      g_autoptr(GError) error = NULL;
+
+      variant = g_variant_parse (NULL, variant_string, NULL, NULL, &error);
+
+      if (variant == NULL)
+        g_warning ("%s", error->message);
+      else
+        g_variant_ref_sink (variant);
+    }
+
+  widget = g_object_new (IDE_TYPE_PREFERENCES_SWITCH,
+                         "key", key,
+                         "keywords", keywords,
+                         "priority", priority,
+                         "is-radio", TRUE,
+                         "schema-id", schema_id,
+                         "subtitle", subtitle,
+                         "target", variant,
+                         "title", title,
+                         "visible", TRUE,
+                         NULL);
+
+  ide_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+ide_preferences_perspective_add_switch (IdePreferences *preferences,
+                                        const gchar    *page_name,
+                                        const gchar    *group_name,
+                                        const gchar    *schema_id,
+                                        const gchar    *key,
+                                        const gchar    *variant_string,
+                                        const gchar    *title,
+                                        const gchar    *subtitle,
+                                        const gchar    *keywords,
+                                        gint            priority)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)preferences;
+  IdePreferencesSwitch *widget;
+  IdePreferencesGroup *group;
+  g_autoptr(GVariant) variant = NULL;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (IDE_IS_PREFERENCES_PERSPECTIVE (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = gtk_stack_get_child_by_name (self->page_stack, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = ide_preferences_page_get_group (IDE_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  if (variant_string != NULL)
+    {
+      g_autoptr(GError) error = NULL;
+
+      variant = g_variant_parse (NULL, variant_string, NULL, NULL, &error);
+
+      if (variant == NULL)
+        g_warning ("%s", error->message);
+      else
+        g_variant_ref_sink (variant);
+    }
+
+  widget = g_object_new (IDE_TYPE_PREFERENCES_SWITCH,
+                         "key", key,
+                         "keywords", keywords,
+                         "priority", priority,
+                         "schema-id", schema_id,
+                         "subtitle", subtitle,
+                         "title", title,
+                         "target", variant,
+                         "visible", TRUE,
+                         NULL);
+
+  ide_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+ide_preferences_perspective_add_spin_button (IdePreferences *preferences,
+                                             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)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)preferences;
+  IdePreferencesSpinButton *widget;
+  IdePreferencesGroup *group;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (IDE_IS_PREFERENCES_PERSPECTIVE (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = gtk_stack_get_child_by_name (self->page_stack, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = ide_preferences_page_get_group (IDE_PREFERENCES_PAGE (page), group_name);
+
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  widget = g_object_new (IDE_TYPE_PREFERENCES_SPIN_BUTTON,
+                         "key", key,
+                         "keywords", keywords,
+                         "priority", priority,
+                         "schema-id", schema_id,
+                         "subtitle", subtitle,
+                         "title", title,
+                         "visible", TRUE,
+                         NULL);
+
+  ide_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+ide_preferences_perspective_add_font_button (IdePreferences *preferences,
+                                             const gchar    *page_name,
+                                             const gchar    *group_name,
+                                             const gchar    *schema_id,
+                                             const gchar    *key,
+                                             const gchar    *title,
+                                             const gchar    *keywords,
+                                             gint            priority)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)preferences;
+  IdePreferencesSwitch *widget;
+  IdePreferencesGroup *group;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (IDE_IS_PREFERENCES_PERSPECTIVE (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (schema_id != NULL);
+  g_assert (key != NULL);
+  g_assert (title != NULL);
+
+  page = gtk_stack_get_child_by_name (self->page_stack, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = ide_preferences_page_get_group (IDE_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  widget = g_object_new (IDE_TYPE_PREFERENCES_FONT_BUTTON,
+                         "key", key,
+                         "keywords", keywords,
+                         "priority", priority,
+                         "schema-id", schema_id,
+                         "title", title,
+                         "visible", TRUE,
+                         NULL);
+
+  ide_preferences_group_add (group, GTK_WIDGET (widget));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static guint
+ide_preferences_perspective_add_custom (IdePreferences *preferences,
+                                        const gchar    *page_name,
+                                        const gchar    *group_name,
+                                        GtkWidget      *widget,
+                                        const gchar    *keywords,
+                                        gint            priority)
+{
+  IdePreferencesPerspective *self = (IdePreferencesPerspective *)preferences;
+  IdePreferencesContainer *container;
+  IdePreferencesGroup *group;
+  GtkWidget *page;
+  guint widget_id;
+
+  g_assert (IDE_IS_PREFERENCES_PERSPECTIVE (self));
+  g_assert (page_name != NULL);
+  g_assert (group_name != NULL);
+  g_assert (GTK_IS_WIDGET (widget));
+
+  page = gtk_stack_get_child_by_name (self->page_stack, page_name);
+
+  if (page == NULL)
+    {
+      g_warning ("No page named \"%s\" could be found.", page_name);
+      return 0;
+    }
+
+  group = ide_preferences_page_get_group (IDE_PREFERENCES_PAGE (page), group_name);
+
+  if (group == NULL)
+    {
+      g_warning ("No such preferences group \"%s\" in page \"%s\"",
+                 group_name, page_name);
+      return 0;
+    }
+
+  container = g_object_new (IDE_TYPE_PREFERENCES_CONTAINER,
+                            "child", widget,
+                            "keywords", keywords,
+                            "priority", priority,
+                            "visible", TRUE,
+                            NULL);
+
+  ide_preferences_group_add (group, GTK_WIDGET (container));
+
+  widget_id = ++self->last_widget_id;
+  g_hash_table_insert (self->widgets, GINT_TO_POINTER (widget_id), widget);
+
+  return widget_id;
+}
+
+static void
+ide_preferences_iface_init (IdePreferencesInterface *iface)
+{
+  iface->add_page = ide_preferences_perspective_add_page;
+  iface->add_group = ide_preferences_perspective_add_group;
+  iface->add_list_group  = ide_preferences_perspective_add_list_group ;
+  iface->add_radio = ide_preferences_perspective_add_radio;
+  iface->add_font_button = ide_preferences_perspective_add_font_button;
+  iface->add_switch = ide_preferences_perspective_add_switch;
+  iface->add_spin_button = ide_preferences_perspective_add_spin_button;
+  iface->add_custom = ide_preferences_perspective_add_custom;
+}
+
+static gchar *
+ide_preferences_perspective_get_title (IdePerspective *perspective)
+{
+  return g_strdup (_("Preferences"));
+}
+
+static gchar *
+ide_preferences_perspective_get_icon_name (IdePerspective *perspective)
+{
+  return g_strdup ("preferences-system-symbolic");
+}
+
+static GtkWidget *
+ide_preferences_perspective_get_titlebar (IdePerspective *perspective)
+{
+  return GTK_WIDGET (IDE_PREFERENCES_PERSPECTIVE (perspective)->titlebar);
+}
+
+static void
+ide_perspective_iface_init (IdePerspectiveInterface *iface)
+{
+  iface->get_icon_name = ide_preferences_perspective_get_icon_name;
+  iface->get_title = ide_preferences_perspective_get_title;
+  iface->get_titlebar = ide_preferences_perspective_get_titlebar;
+}
diff --git a/libide/preferences/ide-preferences-perspective.h 
b/libide/preferences/ide-preferences-perspective.h
new file mode 100644
index 0000000..c5b2a4b
--- /dev/null
+++ b/libide/preferences/ide-preferences-perspective.h
@@ -0,0 +1,32 @@
+/* ide-preferences-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_PREFERENCES_PERSPECTIVE_H
+#define IDE_PREFERENCES_PERSPECTIVE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_PERSPECTIVE (ide_preferences_perspective_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePreferencesPerspective, ide_preferences_perspective, IDE, PREFERENCES_PERSPECTIVE, 
GtkBin)
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_PERSPECTIVE_H */
diff --git a/libide/preferences/ide-preferences-spin-button.c 
b/libide/preferences/ide-preferences-spin-button.c
new file mode 100644
index 0000000..0f67def
--- /dev/null
+++ b/libide/preferences/ide-preferences-spin-button.c
@@ -0,0 +1,93 @@
+/* ide-preferences-spin-button.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-spin-button.h"
+
+struct _IdePreferencesSpinButton
+{
+  GtkBin         parent_instance;
+
+  GtkSpinButton *spin_button;
+};
+
+G_DEFINE_TYPE (IdePreferencesSpinButton, ide_preferences_spin_button, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_preferences_spin_button_finalize (GObject *object)
+{
+  IdePreferencesSpinButton *self = (IdePreferencesSpinButton *)object;
+
+  G_OBJECT_CLASS (ide_preferences_spin_button_parent_class)->finalize (object);
+}
+
+static void
+ide_preferences_spin_button_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  IdePreferencesSpinButton *self = IDE_PREFERENCES_SPIN_BUTTON (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_spin_button_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  IdePreferencesSpinButton *self = IDE_PREFERENCES_SPIN_BUTTON (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_spin_button_class_init (IdePreferencesSpinButtonClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_preferences_spin_button_finalize;
+  object_class->get_property = ide_preferences_spin_button_get_property;
+  object_class->set_property = ide_preferences_spin_button_set_property;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-preferences-spin-button.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesSpinButton, spin_button);
+}
+
+static void
+ide_preferences_spin_button_init (IdePreferencesSpinButton *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/libide/preferences/ide-preferences-spin-button.h 
b/libide/preferences/ide-preferences-spin-button.h
new file mode 100644
index 0000000..07beec5
--- /dev/null
+++ b/libide/preferences/ide-preferences-spin-button.h
@@ -0,0 +1,32 @@
+/* ide-preferences-spin-button.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_SPIN_BUTTON_H
+#define IDE_PREFERENCES_SPIN_BUTTON_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_SPIN_BUTTON (ide_preferences_spin_button_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePreferencesSpinButton, ide_preferences_spin_button, IDE, PREFERENCES_SPIN_BUTTON, 
GtkBin)
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_SPIN_BUTTON_H */
diff --git a/libide/preferences/ide-preferences-switch.c b/libide/preferences/ide-preferences-switch.c
new file mode 100644
index 0000000..ca68192
--- /dev/null
+++ b/libide/preferences/ide-preferences-switch.c
@@ -0,0 +1,269 @@
+/* ide-preferences-switch.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-switch.h"
+
+struct _IdePreferencesSwitch
+{
+  IdePreferencesContainer parent_instance;
+
+  guint     is_radio : 1;
+
+  gchar     *key;
+  gchar     *schema_id;
+  GSettings *settings;
+  GVariant  *target;
+
+  GtkLabel  *subtitle;
+  GtkLabel  *title;
+  GtkSwitch *widget;
+  GtkImage  *image;
+};
+
+G_DEFINE_TYPE (IdePreferencesSwitch, ide_preferences_switch, IDE_TYPE_PREFERENCES_CONTAINER)
+
+enum {
+  PROP_0,
+  PROP_IS_RADIO,
+  PROP_SUBTITLE,
+  PROP_TARGET,
+  PROP_TITLE,
+  PROP_SCHEMA_ID,
+  PROP_KEY,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_preferences_switch_changed (IdePreferencesSwitch *self,
+                                const gchar          *key,
+                                GSettings            *settings)
+{
+  GVariant *value;
+  gboolean active;
+
+  g_assert (IDE_IS_PREFERENCES_SWITCH (self));
+  g_assert (key != NULL);
+  g_assert (G_IS_SETTINGS (settings));
+
+  value = g_settings_get_value (settings, key);
+  active = g_variant_equal (value, self->target);
+
+  gtk_switch_set_active (self->widget, active);
+  gtk_widget_set_visible (GTK_WIDGET (self->image), active);
+}
+
+static void
+ide_preferences_switch_constructed (GObject *object)
+{
+  IdePreferencesSwitch *self = (IdePreferencesSwitch *)object;
+  GSettingsSchemaSource *source;
+  g_autoptr(GSettingsSchema) schema = NULL;
+  g_autofree gchar *signal_detail = NULL;
+
+  g_assert (IDE_IS_PREFERENCES_SWITCH (self));
+
+  source = g_settings_schema_source_get_default ();
+  schema = g_settings_schema_source_lookup (source, self->schema_id, TRUE);
+
+  if (schema == NULL || !g_settings_schema_has_key (schema, self->key))
+    {
+      gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
+      goto chainup;
+    }
+
+  self->settings = g_settings_new (self->schema_id);
+  signal_detail = g_strdup_printf ("changed::%s", self->key);
+
+  g_signal_connect_object (self->settings,
+                           signal_detail,
+                           G_CALLBACK (ide_preferences_switch_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_preferences_switch_changed (self, self->key, self->settings);
+
+chainup:
+  G_OBJECT_CLASS (ide_preferences_switch_parent_class)->constructed (object);
+}
+
+static void
+ide_preferences_switch_finalize (GObject *object)
+{
+  IdePreferencesSwitch *self = (IdePreferencesSwitch *)object;
+
+  g_clear_pointer (&self->key, g_free);
+  g_clear_pointer (&self->schema_id, g_free);
+  g_clear_pointer (&self->target, g_variant_unref);
+  g_clear_object (&self->settings);
+
+  G_OBJECT_CLASS (ide_preferences_switch_parent_class)->finalize (object);
+}
+
+static void
+ide_preferences_switch_get_property (GObject    *object,
+                                     guint       prop_id,
+                                     GValue     *value,
+                                     GParamSpec *pspec)
+{
+  IdePreferencesSwitch *self = IDE_PREFERENCES_SWITCH (object);
+
+  switch (prop_id)
+    {
+    case PROP_IS_RADIO:
+      g_value_set_boolean (value, self->is_radio);
+      break;
+
+    case PROP_SCHEMA_ID:
+      g_value_set_string (value, self->schema_id);
+      break;
+
+    case PROP_KEY:
+      g_value_set_string (value, self->key);
+      break;
+
+    case PROP_TARGET:
+      g_value_set_variant (value, self->target);
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, gtk_label_get_label (self->title));
+      break;
+
+    case PROP_SUBTITLE:
+      g_value_set_string (value, gtk_label_get_label (self->subtitle));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_switch_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  IdePreferencesSwitch *self = IDE_PREFERENCES_SWITCH (object);
+
+  switch (prop_id)
+    {
+    case PROP_IS_RADIO:
+      self->is_radio = g_value_get_boolean (value);
+      gtk_widget_set_visible (GTK_WIDGET (self->widget), !self->is_radio);
+      gtk_widget_set_visible (GTK_WIDGET (self->image), self->is_radio);
+      break;
+
+    case PROP_SCHEMA_ID:
+      self->schema_id = g_value_dup_string (value);
+      break;
+
+    case PROP_KEY:
+      self->key = g_value_dup_string (value);
+      break;
+
+    case PROP_TARGET:
+      self->target = g_value_dup_variant (value);
+      break;
+
+    case PROP_TITLE:
+      gtk_label_set_label (self->title, g_value_get_string (value));
+      break;
+
+    case PROP_SUBTITLE:
+      g_object_set (self->subtitle,
+                    "label", g_value_get_string (value),
+                    "visible", !!g_value_get_string (value),
+                    NULL);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_preferences_switch_class_init (IdePreferencesSwitchClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = ide_preferences_switch_constructed;
+  object_class->finalize = ide_preferences_switch_finalize;
+  object_class->get_property = ide_preferences_switch_get_property;
+  object_class->set_property = ide_preferences_switch_set_property;
+
+  properties [PROP_IS_RADIO] =
+    g_param_spec_boolean ("is-radio",
+                         "Is Radio",
+                         "If a radio style should be used instead of a switch.",
+                         FALSE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SCHEMA_ID] =
+    g_param_spec_string ("schema-id",
+                         "Schema Id",
+                         "Schema Id",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TARGET] =
+    g_param_spec_variant ("target",
+                          "Target",
+                          "Target",
+                          G_VARIANT_TYPE_ANY,
+                          NULL,
+                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_KEY] =
+    g_param_spec_string ("key",
+                         "Key",
+                         "Key",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "Title",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SUBTITLE] =
+    g_param_spec_string ("subtitle",
+                         "Subtitle",
+                         "Subtitle",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/ui/ide-preferences-switch.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesSwitch, image);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesSwitch, subtitle);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesSwitch, title);
+  gtk_widget_class_bind_template_child (widget_class, IdePreferencesSwitch, widget);
+}
+
+static void
+ide_preferences_switch_init (IdePreferencesSwitch *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/libide/preferences/ide-preferences-switch.h b/libide/preferences/ide-preferences-switch.h
new file mode 100644
index 0000000..f4b2cdd
--- /dev/null
+++ b/libide/preferences/ide-preferences-switch.h
@@ -0,0 +1,32 @@
+/* ide-preferences-switch.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_SWITCH_H
+#define IDE_PREFERENCES_SWITCH_H
+
+#include "ide-preferences-container.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_SWITCH (ide_preferences_switch_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePreferencesSwitch, ide_preferences_switch, IDE, PREFERENCES_SWITCH, 
IdePreferencesContainer)
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_SWITCH_H */
diff --git a/libide/ide-preferences.c b/libide/preferences/ide-preferences.c
similarity index 51%
rename from libide/ide-preferences.c
rename to libide/preferences/ide-preferences.c
index eaae59d..3253773 100644
--- a/libide/ide-preferences.c
+++ b/libide/preferences/ide-preferences.c
@@ -48,7 +48,6 @@ ide_preferences_add_group (IdePreferences *self,
   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);
 }
@@ -59,6 +58,7 @@ ide_preferences_add_switch (IdePreferences *self,
                             const gchar    *group_name,
                             const gchar    *schema_id,
                             const gchar    *key,
+                            const gchar    *variant_string,
                             const gchar    *title,
                             const gchar    *subtitle,
                             const gchar    *keywords,
@@ -71,20 +71,19 @@ ide_preferences_add_switch (IdePreferences *self,
   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);
+  return IDE_PREFERENCES_GET_IFACE (self)->add_switch (self, page_name, group_name, schema_id, key, 
variant_string, 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)
+ide_preferences_add_spin_button (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);
@@ -93,9 +92,7 @@ ide_preferences_add_spinbutton (IdePreferences *self,
   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);
+  return IDE_PREFERENCES_GET_IFACE (self)->add_spin_button (self, page_name, group_name, schema_id, key, 
title, subtitle, keywords, priority);
 }
 
 guint
@@ -111,6 +108,60 @@ ide_preferences_add_custom (IdePreferences *self,
   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);
+  return IDE_PREFERENCES_GET_IFACE (self)->add_custom (self, page_name, group_name, widget, keywords, 
priority);
+}
+
+void
+ide_preferences_add_list_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);
+
+  return IDE_PREFERENCES_GET_IFACE (self)->add_list_group  (self, page_name, group_name, title, priority);
+}
+
+guint ide_preferences_add_radio (IdePreferences *self,
+                                 const gchar    *page_name,
+                                 const gchar    *group_name,
+                                 const gchar    *schema_id,
+                                 const gchar    *key,
+                                 const gchar    *variant_string,
+                                 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_radio (self, page_name, group_name, schema_id, key, 
variant_string, title, subtitle, keywords, priority);
+}
+
+guint
+ide_preferences_add_font_button (IdePreferences *self,
+                                 const gchar    *page_name,
+                                 const gchar    *group_name,
+                                 const gchar    *schema_id,
+                                 const gchar    *key,
+                                 const gchar    *title,
+                                 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_font_button (self, page_name, group_name, schema_id, key, 
title, keywords, priority);
 }
diff --git a/libide/preferences/ide-preferences.h b/libide/preferences/ide-preferences.h
new file mode 100644
index 0000000..1c56a3d
--- /dev/null
+++ b/libide/preferences/ide-preferences.h
@@ -0,0 +1,153 @@
+/* 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);
+  void  (*add_list_group ) (IdePreferences *self,
+                            const gchar    *page_name,
+                            const gchar    *group_name,
+                            const gchar    *title,
+                            gint            priority);
+  guint (*add_radio)       (IdePreferences *self,
+                            const gchar    *page_name,
+                            const gchar    *group_name,
+                            const gchar    *schema_id,
+                            const gchar    *key,
+                            const gchar    *variant_string,
+                            const gchar    *title,
+                            const gchar    *subtitle,
+                            const gchar    *keywords,
+                            gint            priority);
+  guint (*add_font_button) (IdePreferences *self,
+                            const gchar    *page_name,
+                            const gchar    *group_name,
+                            const gchar    *schema_id,
+                            const gchar    *key,
+                            const gchar    *title,
+                            const gchar    *keywords,
+                            gint            priority);
+  guint (*add_switch)      (IdePreferences *self,
+                            const gchar    *page_name,
+                            const gchar    *group_name,
+                            const gchar    *schema_id,
+                            const gchar    *key,
+                            const gchar    *variant_string,
+                            const gchar    *title,
+                            const gchar    *subtitle,
+                            const gchar    *keywords,
+                            gint            priority);
+  guint (*add_spin_button) (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);
+void  ide_preferences_add_list_group  (IdePreferences *self,
+                                       const gchar    *page_name,
+                                       const gchar    *group_name,
+                                       const gchar    *title,
+                                       gint            priority);
+guint ide_preferences_add_radio       (IdePreferences *self,
+                                       const gchar    *page_name,
+                                       const gchar    *group_name,
+                                       const gchar    *schema_id,
+                                       const gchar    *key,
+                                       const gchar    *variant_string,
+                                       const gchar    *title,
+                                       const gchar    *subtitle,
+                                       const gchar    *keywords,
+                                       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    *variant_string,
+                                       const gchar    *title,
+                                       const gchar    *subtitle,
+                                       const gchar    *keywords,
+                                       gint            priority);
+guint ide_preferences_add_spin_button (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);
+guint ide_preferences_add_font_button (IdePreferences *self,
+                                       const gchar    *page_name,
+                                       const gchar    *group_name,
+                                       const gchar    *schema_id,
+                                       const gchar    *key,
+                                       const gchar    *title,
+                                       const gchar    *keywords,
+                                       gint            priority);
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_H */
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 55ee581..24b9646 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -25,6 +25,11 @@
     <file alias="ui/ide-editor-perspective.ui">../../data/ui/ide-editor-perspective.ui</file>
     <file alias="ui/ide-greeter-perspective.ui">../../data/ui/ide-greeter-perspective.ui</file>
     <file alias="ui/ide-greeter-project-row.ui">../../data/ui/ide-greeter-project-row.ui</file>
+    <file alias="ui/ide-preferences-font-button.ui">../../data/ui/ide-preferences-font-button.ui</file>
+    <file alias="ui/ide-preferences-page.ui">../../data/ui/ide-preferences-page.ui</file>
+    <file alias="ui/ide-preferences-perspective.ui">../../data/ui/ide-preferences-perspective.ui</file>
+    <file alias="ui/ide-preferences-group.ui">../../data/ui/ide-preferences-group.ui</file>
+    <file alias="ui/ide-preferences-switch.ui">../../data/ui/ide-preferences-switch.ui</file>
     <file alias="ui/ide-workbench.ui">../../data/ui/ide-workbench.ui</file>
     <file alias="ui/ide-workbench-header-bar.ui">../../data/ui/ide-workbench-header-bar.ui</file>
   </gresource>


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