[gnome-builder/wip/chergert/gobjgen: 8/8] wip



commit 193ede50d281f6c5f0ae6c0c19ed308f92d9d13d
Author: Christian Hergert <chergert redhat com>
Date:   Fri Aug 12 09:09:08 2016 +0200

    wip

 configure.ac                                       |    3 +
 contrib/tmpl/tmpl-expr-eval.c                      |  308 +++++++-
 contrib/tmpl/tmpl-expr-parser.y                    |    3 +
 contrib/tmpl/tmpl-expr-scanner.l                   |    1 +
 contrib/tmpl/tmpl-expr-types.h                     |    1 +
 contrib/tmpl/tmpl-expr.c                           |   12 +
 contrib/tmpl/tmpl-expr.h                           |    1 +
 contrib/tmpl/tmpl-iterator.c                       |   27 +-
 contrib/tmpl/tmpl-template.c                       |   14 +-
 libide/ide.h                                       |    1 +
 libide/template/ide-template-base.c                |   69 ++-
 libidemm/idemm/Makefile.am                         |    2 +
 libidemm/tools/Makefile.am                         |    4 +-
 plugins/Makefile.am                                |    1 +
 plugins/gobject-templates/Makefile.am              |   54 ++
 plugins/gobject-templates/configure.ac             |   12 +
 plugins/gobject-templates/gbp-gobject-dialog.c     |  478 +++++++++++
 plugins/gobject-templates/gbp-gobject-dialog.h     |   40 +
 plugins/gobject-templates/gbp-gobject-dialog.ui    |  268 ++++++
 .../gbp-gobject-property-editor.c                  |  280 +++++++
 .../gbp-gobject-property-editor.h                  |   36 +
 .../gbp-gobject-property-editor.ui                 |  724 ++++++++++++++++
 plugins/gobject-templates/gbp-gobject-property.c   |  515 ++++++++++++
 plugins/gobject-templates/gbp-gobject-property.h   |   79 ++
 .../gobject-templates/gbp-gobject-signal-editor.c  |  137 +++
 .../gobject-templates/gbp-gobject-signal-editor.h  |   39 +
 .../gobject-templates/gbp-gobject-signal-editor.ui |  101 +++
 plugins/gobject-templates/gbp-gobject-signal.c     |  111 +++
 plugins/gobject-templates/gbp-gobject-signal.h     |   33 +
 .../gobject-templates/gbp-gobject-spec-editor.c    |  212 +++++
 .../gobject-templates/gbp-gobject-spec-editor.h    |   42 +
 .../gobject-templates/gbp-gobject-spec-editor.ui   |  351 ++++++++
 plugins/gobject-templates/gbp-gobject-spec.c       |  512 ++++++++++++
 plugins/gobject-templates/gbp-gobject-spec.h       |   52 ++
 plugins/gobject-templates/gbp-gobject-template.c   |  349 ++++++++
 plugins/gobject-templates/gbp-gobject-template.h   |   50 ++
 .../gbp-gobject-workbench-addin.c                  |  228 +++++
 .../gbp-gobject-workbench-addin.h                  |   33 +
 .../gobject-templates/gobject-templates-plugin.c   |   30 +
 .../gobject-templates.gresource.xml                |   17 +
 plugins/gobject-templates/gobject-templates.plugin |    9 +
 plugins/gobject-templates/gobject.c.tmpl           |  877 ++++++++++++++++++++
 plugins/gobject-templates/gobject.h.tmpl           |   84 ++
 plugins/gobject-templates/gtk/menus.ui             |   24 +
 src/Makefile.am                                    |    2 +
 45 files changed, 6185 insertions(+), 41 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index c896021..1241804 100644
--- a/configure.ac
+++ b/configure.ac
@@ -295,6 +295,7 @@ m4_include([plugins/gcc/configure.ac])
 m4_include([plugins/gettext/configure.ac])
 m4_include([plugins/git/configure.ac])
 m4_include([plugins/gnome-code-assistance/configure.ac])
+m4_include([plugins/gobject-templates/configure.ac])
 m4_include([plugins/hello-cpp/configure.ac])
 m4_include([plugins/html-completion/configure.ac])
 m4_include([plugins/html-preview/configure.ac])
@@ -451,6 +452,7 @@ PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/nautilus"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/pnl"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/rg"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/search"
+PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/tmpl"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/xml"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/libide"
 PLUGIN_CFLAGS="$PLUGIN_CFLAGS $LIBIDE_CFLAGS"
@@ -609,5 +611,6 @@ echo ""
 echo " Templates"
 echo ""
 echo "  Autotools ............................ : ${enable_autotools_templates}"
+echo "  GObject .............................. : ${enable_gobject_templates}"
 echo ""
 
diff --git a/contrib/tmpl/tmpl-expr-eval.c b/contrib/tmpl/tmpl-expr-eval.c
index 47aaaab..652499b 100644
--- a/contrib/tmpl/tmpl-expr-eval.c
+++ b/contrib/tmpl/tmpl-expr-eval.c
@@ -18,6 +18,7 @@
 
 #include <girepository.h>
 #include <math.h>
+#include <string.h>
 
 #include "tmpl-error.h"
 #include "tmpl-expr.h"
@@ -67,6 +68,14 @@ static gboolean builtin_repr                 (const GValue  *value,
 static gboolean builtin_sqrt                 (const GValue  *value,
                                               GValue        *return_value,
                                               GError       **error);
+static gboolean eq_enum_string               (const GValue  *left,
+                                              const GValue  *right,
+                                              GValue        *return_value,
+                                              GError       **error);
+static gboolean ne_enum_string               (const GValue  *left,
+                                              const GValue  *right,
+                                              GValue        *return_value,
+                                              GError       **error);
 
 static GHashTable *fast_dispatch;
 static BuiltinFunc builtin_funcs [] = {
@@ -85,8 +94,11 @@ build_hash (TmplExprType type,
             GType        left,
             GType        right)
 {
-  g_assert (!left || G_TYPE_IS_FUNDAMENTAL (left));
-  g_assert (!right || G_TYPE_IS_FUNDAMENTAL (right));
+  if (left && !G_TYPE_IS_FUNDAMENTAL (left))
+    return 0;
+
+  if (right && !G_TYPE_IS_FUNDAMENTAL (right))
+    return 0;
 
   return type | (left << 16) | (right << 24);
 }
@@ -132,6 +144,28 @@ throw_type_mismatch (GError       **error,
     return throw_type_mismatch (error, left, right, "invalid add"); \
   } G_STMT_END
 
+static FastDispatch
+find_dispatch_slow (TmplExprSimple *node,
+                    const GValue   *left,
+                    const GValue   *right)
+{
+  if (node->type == TMPL_EXPR_EQ)
+    {
+      if ((G_VALUE_HOLDS_STRING (left) && G_VALUE_HOLDS_ENUM (right)) ||
+          (G_VALUE_HOLDS_STRING (right) && G_VALUE_HOLDS_ENUM (left)))
+        return eq_enum_string;
+    }
+
+  if (node->type == TMPL_EXPR_NE)
+    {
+      if ((G_VALUE_HOLDS_STRING (left) && G_VALUE_HOLDS_ENUM (right)) ||
+          (G_VALUE_HOLDS_STRING (right) && G_VALUE_HOLDS_ENUM (left)))
+        return ne_enum_string;
+    }
+
+  return NULL;
+}
+
 static gboolean
 tmpl_expr_simple_eval (TmplExprSimple  *node,
                        TmplScope       *scope,
@@ -150,16 +184,23 @@ tmpl_expr_simple_eval (TmplExprSimple  *node,
       ((node->right == NULL) ||
        tmpl_expr_eval_internal (node->right, scope, &right, error)))
     {
-      FastDispatch dispatch;
+      FastDispatch dispatch = NULL;
       guint hash;
 
       hash = build_hash (node->type, G_VALUE_TYPE (&left), G_VALUE_TYPE (&right));
-      dispatch = g_hash_table_lookup (fast_dispatch, GINT_TO_POINTER (hash));
 
-      if (G_UNLIKELY (dispatch == NULL))
+      if (hash != 0)
+        dispatch = g_hash_table_lookup (fast_dispatch, GINT_TO_POINTER (hash));
+
+      if G_UNLIKELY (dispatch == NULL)
         {
-          throw_type_mismatch (error, &left, &right, "type mismatch");
-          goto cleanup;
+          dispatch = find_dispatch_slow (node, &left, &right);
+
+          if (dispatch == NULL)
+            {
+              throw_type_mismatch (error, &left, &right, "type mismatch");
+              goto cleanup;
+            }
         }
 
       ret = dispatch (&left, &right, return_value, error);
@@ -511,14 +552,46 @@ cleanup:
   TMPL_CLEAR_VALUE (&left);
   TMPL_CLEAR_VALUE (&right);
 
+  g_assert (ret == TRUE || (error == NULL || *error != NULL));
+
   return ret;
 }
 
+static gchar *
+make_title (const gchar *str)
+{
+  g_auto(GStrv) parts = NULL;
+  GString *ret;
+
+  g_assert (str != NULL);
+
+  ret = g_string_new (NULL);
+
+  for (; *str; str = g_utf8_next_char (str))
+    {
+      gunichar ch = g_utf8_get_char (str);
+
+      if (!g_unichar_isalnum (ch))
+        {
+          if (ret->len && ret->str[ret->len - 1] != ' ')
+            g_string_append_c (ret, ' ');
+          continue;
+        }
+
+      if (ret->len && ret->str[ret->len - 1] != ' ')
+        g_string_append_unichar (ret, ch);
+      else
+        g_string_append_unichar (ret, g_unichar_toupper (ch));
+    }
+
+  return g_string_free (ret, FALSE);
+}
+
 static gboolean
 tmpl_expr_gi_call_eval (TmplExprGiCall  *node,
-                       TmplScope       *scope,
-                       GValue         *return_value,
-                       GError        **error)
+                        TmplScope       *scope,
+                        GValue          *return_value,
+                        GError         **error)
 {
   GValue left = G_VALUE_INIT;
   GValue right = G_VALUE_INIT;
@@ -543,6 +616,109 @@ tmpl_expr_gi_call_eval (TmplExprGiCall  *node,
   if (!tmpl_expr_eval_internal (node->object, scope, &left, error))
     goto cleanup;
 
+  if (G_VALUE_HOLDS_STRING (&left))
+    {
+      const gchar *str = g_value_get_string (&left) ?: "";
+
+      /*
+       * TODO: This should be abstracted somewhere else rather than our G-I call.
+       *       Basically we are adding useful string functions like:
+       *
+       *       "foo".upper()
+       *       "foo".lower()
+       *       "foo".casefold()
+       *       "foo".reverse()
+       *       "foo".len()
+       *       "foo".title()
+       */
+      if (FALSE) {}
+      else if (g_str_equal (node->name, "upper"))
+        {
+          g_value_init (return_value, G_TYPE_STRING);
+          g_value_take_string (return_value, g_utf8_strup (str, -1));
+          ret = TRUE;
+        }
+      else if (g_str_equal (node->name, "lower"))
+        {
+          g_value_init (return_value, G_TYPE_STRING);
+          g_value_take_string (return_value, g_utf8_strdown (str, -1));
+          ret = TRUE;
+        }
+      else if (g_str_equal (node->name, "casefold"))
+        {
+          g_value_init (return_value, G_TYPE_STRING);
+          g_value_take_string (return_value, g_utf8_casefold (str, -1));
+          ret = TRUE;
+        }
+      else if (g_str_equal (node->name, "reverse"))
+        {
+          g_value_init (return_value, G_TYPE_STRING);
+          g_value_take_string (return_value, g_utf8_strreverse (str, -1));
+          ret = TRUE;
+        }
+      else if (g_str_equal (node->name, "len"))
+        {
+          g_value_init (return_value, G_TYPE_UINT);
+          g_value_set_uint (return_value, strlen (str));
+          ret = TRUE;
+        }
+      else if (g_str_equal (node->name, "space"))
+        {
+          gchar *space;
+          guint len = strlen (str);
+
+          g_value_init (return_value, G_TYPE_STRING);
+          space = g_malloc (len + 1);
+          memset (space, ' ', len);
+          space[len] = '\0';
+          g_value_take_string (return_value, space);
+          ret = TRUE;
+        }
+      else if (g_str_equal (node->name, "title"))
+        {
+          g_value_init (return_value, G_TYPE_STRING);
+          g_value_take_string (return_value, make_title (str));
+          ret = TRUE;
+        }
+      else
+        {
+          g_set_error (error,
+                       TMPL_ERROR,
+                       TMPL_ERROR_GI_FAILURE,
+                       "No such method %s for string",
+                       node->name);
+        }
+
+      goto cleanup;
+    }
+
+  if (G_VALUE_HOLDS_ENUM (&left))
+    {
+      if (FALSE) {}
+      else if (g_str_equal (node->name, "nick"))
+        {
+          GEnumClass *enum_class = g_type_class_peek (G_VALUE_TYPE (&left));
+          GEnumValue *enum_value = g_enum_get_value (enum_class, g_value_get_enum (&left));
+
+          g_value_init (return_value, G_TYPE_STRING);
+
+          if (enum_value != NULL)
+            g_value_set_static_string (return_value, enum_value->value_nick);
+
+          ret = TRUE;
+        }
+      else
+        {
+          g_set_error (error,
+                       TMPL_ERROR,
+                       TMPL_ERROR_GI_FAILURE,
+                       "No such method %s for enum",
+                       node->name);
+        }
+
+      goto cleanup;
+    }
+
   if (!G_VALUE_HOLDS_OBJECT (&left))
     {
       g_set_error (error,
@@ -569,6 +745,8 @@ tmpl_expr_gi_call_eval (TmplExprGiCall  *node,
 
   while (g_type_is_a (type, G_TYPE_OBJECT))
     {
+      guint n_ifaces;
+
       base_info = g_irepository_find_by_gtype (repository, type);
 
       if (base_info == NULL)
@@ -585,6 +763,19 @@ tmpl_expr_gi_call_eval (TmplExprGiCall  *node,
       if (function != NULL)
         break;
 
+      /* Maybe the function is found in an interface */
+      n_ifaces = g_object_info_get_n_interfaces ((GIObjectInfo *)base_info);
+      for (i = 0; function == NULL && i < n_ifaces; i++)
+        {
+          GIInterfaceInfo *iface_info;
+
+          iface_info = g_object_info_get_interface ((GIObjectInfo *)base_info, i);
+
+          function = g_interface_info_find_method (iface_info, node->name);
+        }
+      if (function != NULL)
+        break;
+
       type = g_type_parent (type);
     }
 
@@ -814,9 +1005,9 @@ cleanup:
 
 static gboolean
 tmpl_expr_require_eval (TmplExprRequire  *node,
-                       TmplScope        *scope,
-                       GValue          *return_value,
-                       GError         **error)
+                        TmplScope        *scope,
+                        GValue           *return_value,
+                        GError          **error)
 {
   GITypelib *typelib;
   TmplSymbol *symbol;
@@ -831,6 +1022,8 @@ tmpl_expr_require_eval (TmplExprRequire  *node,
                                    G_IREPOSITORY_LOAD_FLAG_LAZY,
                                    error);
 
+  g_assert (typelib != NULL || (error == NULL || *error != NULL));
+
   if (typelib == NULL)
     return FALSE;
 
@@ -888,8 +1081,7 @@ tmpl_expr_eval_internal (TmplExpr   *node,
       return TRUE;
 
     case TMPL_EXPR_STMT_LIST:
-      tmpl_expr_stmt_list_eval ((TmplExprSimple *)node, scope, return_value, error);
-      break;
+      return tmpl_expr_stmt_list_eval ((TmplExprSimple *)node, scope, return_value, error);
 
     case TMPL_EXPR_IF:
     case TMPL_EXPR_WHILE:
@@ -900,7 +1092,6 @@ tmpl_expr_eval_internal (TmplExpr   *node,
 
     case TMPL_EXPR_SYMBOL_ASSIGN:
       return tmpl_expr_symbol_assign_eval ((TmplExprSymbolAssign *)node, scope, return_value, error);
-      break;
 
     case TMPL_EXPR_FN_CALL:
       return tmpl_expr_fn_call_eval ((TmplExprFnCall *)node, scope, return_value, error);
@@ -920,6 +1111,26 @@ tmpl_expr_eval_internal (TmplExpr   *node,
     case TMPL_EXPR_REQUIRE:
       return tmpl_expr_require_eval ((TmplExprRequire *)node, scope, return_value, error);
 
+    case TMPL_EXPR_INVERT_BOOLEAN:
+      {
+        GValue tmp = G_VALUE_INIT;
+        gboolean ret;
+
+        ret = tmpl_expr_eval_internal (((TmplExprSimple *)node)->left, scope, &tmp, error);
+
+        if (ret)
+          {
+            g_value_init (return_value, G_TYPE_BOOLEAN);
+            g_value_set_boolean (return_value, !tmpl_value_as_boolean (&tmp));
+          }
+
+        TMPL_CLEAR_VALUE (&tmp);
+
+        g_assert (ret == TRUE || (error == NULL || *error != NULL));
+
+        return ret;
+      }
+
     default:
       break;
     }
@@ -1041,6 +1252,55 @@ ne_string_string (const GValue  *left,
   return TRUE;
 }
 
+static gboolean
+eq_enum_string (const GValue  *left,
+                const GValue  *right,
+                GValue        *return_value,
+                GError       **error)
+{
+  const gchar *str;
+  GEnumClass *klass;
+  const GEnumValue *val;
+  GType type;
+  gint eval;
+
+  if (G_VALUE_HOLDS_STRING (left))
+    {
+      str = g_value_get_string (left);
+      eval = g_value_get_enum (right);
+      type = G_VALUE_TYPE (right);
+    }
+  else
+    {
+      str = g_value_get_string (right);
+      eval = g_value_get_enum (left);
+      type = G_VALUE_TYPE (left);
+    }
+
+  klass = g_type_class_peek (type);
+  val = g_enum_get_value ((GEnumClass *)klass, eval);
+
+  g_value_init (return_value, G_TYPE_BOOLEAN);
+  g_value_set_boolean (return_value, 0 == g_strcmp0 (str, val->value_nick));
+
+  return TRUE;
+}
+
+static gboolean
+ne_enum_string (const GValue  *left,
+                const GValue  *right,
+                GValue        *return_value,
+                GError       **error)
+{
+  if (eq_enum_string (left, right, return_value, error))
+    {
+      g_value_set_boolean (return_value, !g_value_get_boolean (return_value));
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
 #define SIMPLE_OP_FUNC(func_name, ret_type, set_func, get_left, op, get_right)  \
 static gboolean                                                                 \
 func_name (const GValue  *left,                                                 \
@@ -1065,6 +1325,10 @@ SIMPLE_OP_FUNC (gt_double_double,  G_TYPE_BOOLEAN, set_boolean, get_double, >,
 SIMPLE_OP_FUNC (eq_double_double,  G_TYPE_BOOLEAN, set_boolean, get_double, ==, get_double)
 SIMPLE_OP_FUNC (ne_double_double,  G_TYPE_BOOLEAN, set_boolean, get_double, !=, get_double)
 SIMPLE_OP_FUNC (gte_double_double, G_TYPE_BOOLEAN, set_boolean, get_double, >=, get_double)
+SIMPLE_OP_FUNC (eq_uint_double,    G_TYPE_BOOLEAN, set_boolean, get_uint,   ==, get_double)
+SIMPLE_OP_FUNC (eq_double_uint,    G_TYPE_BOOLEAN, set_boolean, get_double, ==, get_uint)
+SIMPLE_OP_FUNC (ne_uint_double,    G_TYPE_BOOLEAN, set_boolean, get_uint,   !=, get_double)
+SIMPLE_OP_FUNC (ne_double_uint,    G_TYPE_BOOLEAN, set_boolean, get_double, !=, get_uint)
 
 #undef SIMPLE_OP_FUNC
 
@@ -1092,6 +1356,10 @@ build_dispatch_table (void)
   ADD_DISPATCH_FUNC (TMPL_EXPR_LTE,         G_TYPE_DOUBLE, G_TYPE_DOUBLE, lte_double_double);
   ADD_DISPATCH_FUNC (TMPL_EXPR_GTE,         G_TYPE_DOUBLE, G_TYPE_DOUBLE, gte_double_double);
   ADD_DISPATCH_FUNC (TMPL_EXPR_EQ,          G_TYPE_DOUBLE, G_TYPE_DOUBLE, eq_double_double);
+  ADD_DISPATCH_FUNC (TMPL_EXPR_EQ,          G_TYPE_UINT,   G_TYPE_DOUBLE, eq_uint_double);
+  ADD_DISPATCH_FUNC (TMPL_EXPR_EQ,          G_TYPE_DOUBLE, G_TYPE_UINT,   eq_double_uint);
+  ADD_DISPATCH_FUNC (TMPL_EXPR_NE,          G_TYPE_UINT,   G_TYPE_DOUBLE, ne_uint_double);
+  ADD_DISPATCH_FUNC (TMPL_EXPR_NE,          G_TYPE_DOUBLE, G_TYPE_UINT,   ne_double_uint);
   ADD_DISPATCH_FUNC (TMPL_EXPR_MUL,         G_TYPE_STRING, G_TYPE_DOUBLE, mul_string_double);
   ADD_DISPATCH_FUNC (TMPL_EXPR_MUL,         G_TYPE_DOUBLE, G_TYPE_STRING, mul_double_string);
   ADD_DISPATCH_FUNC (TMPL_EXPR_EQ,          G_TYPE_STRING, G_TYPE_STRING, eq_string_string);
@@ -1108,6 +1376,8 @@ tmpl_expr_eval (TmplExpr   *node,
                 GValue     *return_value,
                 GError    **error)
 {
+  gboolean ret;
+
   g_return_val_if_fail (node != NULL, FALSE);
   g_return_val_if_fail (scope != NULL, FALSE);
   g_return_val_if_fail (return_value != NULL, FALSE);
@@ -1116,7 +1386,11 @@ tmpl_expr_eval (TmplExpr   *node,
   if (g_once_init_enter (&fast_dispatch))
     g_once_init_leave (&fast_dispatch, build_dispatch_table ());
 
-  return tmpl_expr_eval_internal (node, scope, return_value, error);
+  ret = tmpl_expr_eval_internal (node, scope, return_value, error);
+
+  g_assert (ret == TRUE || (error == NULL || *error != NULL));
+
+  return ret;
 }
 
 static gboolean
diff --git a/contrib/tmpl/tmpl-expr-parser.y b/contrib/tmpl/tmpl-expr-parser.y
index 8f44e69..e305dda 100644
--- a/contrib/tmpl/tmpl-expr-parser.y
+++ b/contrib/tmpl/tmpl-expr-parser.y
@@ -175,6 +175,9 @@ exp: exp CMP exp {
   | NAME '(' ')' {
     $$ = tmpl_expr_new_user_fn_call ($1, NULL);
   }
+  | '!' exp {
+    $$ = tmpl_expr_new_invert_boolean ($2);
+  }
   | REQUIRE NAME {
     $$ = tmpl_expr_new_require ($2, NULL);
   }
diff --git a/contrib/tmpl/tmpl-expr-scanner.l b/contrib/tmpl/tmpl-expr-scanner.l
index afe2435..48a3f4d 100644
--- a/contrib/tmpl/tmpl-expr-scanner.l
+++ b/contrib/tmpl/tmpl-expr-scanner.l
@@ -38,6 +38,7 @@ TmplExprParser *parser = yyextra;
 "," |
 "." |
 ";" |
+"!" |
 "(" |
 ")" { return yytext [0]; }
 
diff --git a/contrib/tmpl/tmpl-expr-types.h b/contrib/tmpl/tmpl-expr-types.h
index 250113b..0d4b1bb 100644
--- a/contrib/tmpl/tmpl-expr-types.h
+++ b/contrib/tmpl/tmpl-expr-types.h
@@ -72,6 +72,7 @@ typedef enum
   TMPL_EXPR_REQUIRE,
   TMPL_EXPR_AND,
   TMPL_EXPR_OR,
+  TMPL_EXPR_INVERT_BOOLEAN,
 } TmplExprType;
 
 typedef enum
diff --git a/contrib/tmpl/tmpl-expr.c b/contrib/tmpl/tmpl-expr.c
index f329bdb..228a882 100644
--- a/contrib/tmpl/tmpl-expr.c
+++ b/contrib/tmpl/tmpl-expr.c
@@ -83,6 +83,7 @@ tmpl_expr_destroy (TmplExpr *self)
     case TMPL_EXPR_USER_FN_CALL:
     case TMPL_EXPR_AND:
     case TMPL_EXPR_OR:
+    case TMPL_EXPR_INVERT_BOOLEAN:
       g_clear_pointer (&self->simple.left, tmpl_expr_unref);
       g_clear_pointer (&self->simple.right, tmpl_expr_unref);
       break;
@@ -210,6 +211,17 @@ tmpl_expr_new_simple (TmplExprType  type,
 }
 
 TmplExpr *
+tmpl_expr_new_invert_boolean (TmplExpr *left)
+{
+  TmplExprSimple *ret;
+
+  ret = tmpl_expr_new (TMPL_EXPR_INVERT_BOOLEAN);
+  ret->left = left;
+
+  return (TmplExpr *)ret;
+}
+
+TmplExpr *
 tmpl_expr_new_flow (TmplExprType  type,
                     TmplExpr     *condition,
                     TmplExpr     *primary,
diff --git a/contrib/tmpl/tmpl-expr.h b/contrib/tmpl/tmpl-expr.h
index d6e2826..1edeb6a 100644
--- a/contrib/tmpl/tmpl-expr.h
+++ b/contrib/tmpl/tmpl-expr.h
@@ -36,6 +36,7 @@ gboolean  tmpl_expr_eval              (TmplExpr         *expr,
                                        GValue           *return_value,
                                        GError          **error);
 TmplExpr *tmpl_expr_new_boolean       (gboolean          value);
+TmplExpr *tmpl_expr_new_invert_boolean(TmplExpr         *left);
 TmplExpr *tmpl_expr_new_getattr       (TmplExpr         *left,
                                        const gchar      *attr);
 TmplExpr *tmpl_expr_new_setattr       (TmplExpr         *left,
diff --git a/contrib/tmpl/tmpl-iterator.c b/contrib/tmpl/tmpl-iterator.c
index 1446d68..4c1c897 100644
--- a/contrib/tmpl/tmpl-iterator.c
+++ b/contrib/tmpl/tmpl-iterator.c
@@ -63,7 +63,10 @@ list_model_move_next (TmplIterator *iter)
   guint index = GPOINTER_TO_INT (iter->data1);
   guint n_items = GPOINTER_TO_INT (iter->data2);
 
-  if (++index < n_items)
+  index++;
+
+  /* We are 1 based indexing here */
+  if (index <= n_items)
     {
       iter->data1 = GINT_TO_POINTER (index);
       return TRUE;
@@ -79,14 +82,14 @@ list_model_get_value (TmplIterator *iter,
   guint index = GPOINTER_TO_INT (iter->data1);
   GObject *obj;
 
-  obj = g_list_model_get_item (iter->instance, index);
+  g_return_val_if_fail (index > 0, FALSE);
 
-  if (obj != NULL)
-    g_value_init (value, G_OBJECT_TYPE (obj));
-  else
-    g_value_init (value, G_TYPE_OBJECT);
+  obj = g_list_model_get_item (iter->instance, index - 1);
 
-  g_value_take_object (value, obj);
+  g_value_init (value, g_list_model_get_item_type (iter->instance));
+
+  if (obj != NULL)
+    g_value_take_object (value, obj);
 
   return TRUE;
 }
@@ -113,7 +116,17 @@ tmpl_iterator_init (TmplIterator *iter,
       iter->move_next = list_model_move_next;
       iter->get_value = list_model_get_value;
       iter->destroy = NULL;
+
+      if (iter->instance != NULL)
+        {
+          guint n_items;
+
+          n_items = g_list_model_get_n_items (iter->instance);
+          iter->data1 = GUINT_TO_POINTER (iter->data1);
+          iter->data2 = GUINT_TO_POINTER (n_items);
+        }
     }
+
   /* TODO: More iter types */
 }
 
diff --git a/contrib/tmpl/tmpl-template.c b/contrib/tmpl/tmpl-template.c
index e666898..5ff2bb0 100644
--- a/contrib/tmpl/tmpl-template.c
+++ b/contrib/tmpl/tmpl-template.c
@@ -300,7 +300,7 @@ tmpl_template_expand_visitor (TmplNode *node,
   g_assert (TMPL_IS_NODE (node));
   g_assert (state != NULL);
 
-/* Short cirtcuit if an error occurred */
+  /* Short cirtcuit if an error occurred */
   if (state->result == FALSE)
     return;
 
@@ -395,6 +395,9 @@ tmpl_template_expand_visitor (TmplNode *node,
               TMPL_CLEAR_VALUE (&value);
 
               tmpl_node_visit_children (node, tmpl_template_expand_visitor, state);
+
+              if (state->result == FALSE)
+                break;
             }
 
           state->scope = old_scope;
@@ -477,6 +480,8 @@ tmpl_template_expand (TmplTemplate  *self,
   if (local_scope != NULL)
     tmpl_scope_unref (local_scope);
 
+  g_assert (state.result == TRUE || (state.error == NULL || *state.error != NULL));
+
   return state.result;
 }
 
@@ -509,6 +514,13 @@ tmpl_template_expand_string (TmplTemplate  *self,
 
     {
       g_object_unref (stream);
+
+      if (error != NULL && *error == NULL)
+        g_set_error (error,
+                     G_IO_ERROR,
+                     G_IO_ERROR_UNKNOWN,
+                     "An unknown error occurred while expanding the template");
+
       return NULL;
     }
 
diff --git a/libide/ide.h b/libide/ide.h
index eaad665..7f03486 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -117,6 +117,7 @@ G_BEGIN_DECLS
 #include "symbols/ide-symbol.h"
 #include "symbols/ide-tags-builder.h"
 #include "template/ide-project-template.h"
+#include "template/ide-template-base.h"
 #include "template/ide-template-provider.h"
 #include "threading/ide-thread-pool.h"
 #include "tree/ide-tree-builder.h"
diff --git a/libide/template/ide-template-base.c b/libide/template/ide-template-base.c
index b101038..14665bd 100644
--- a/libide/template/ide-template-base.c
+++ b/libide/template/ide-template-base.c
@@ -16,10 +16,13 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define G_LOG_DOMAIN "ide-template-base"
+
 #include <glib/gstdio.h>
 #include <errno.h>
 #include <string.h>
 
+#include "ide-debug.h"
 #include "ide-template-base.h"
 
 #define TIMEOUT_INTERVAL_MSEC 17
@@ -72,6 +75,8 @@ ide_template_base_mkdirs_worker (GTask        *task,
   GError *error = NULL;
   guint i;
 
+  IDE_ENTRY;
+
   g_assert (G_IS_TASK (task));
   g_assert (IDE_IS_TEMPLATE_BASE (self));
 
@@ -87,8 +92,8 @@ ide_template_base_mkdirs_worker (GTask        *task,
         {
           if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
             {
-              g_task_return_error (task, error);
-              return;
+              g_task_return_error (task, g_steal_pointer (&error));
+              IDE_EXIT;
             }
 
           g_clear_error (&error);
@@ -96,6 +101,8 @@ ide_template_base_mkdirs_worker (GTask        *task,
     }
 
   g_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
 }
 
 static void
@@ -271,6 +278,8 @@ ide_template_base_parse_worker (GTask        *task,
   IdeTemplateBasePrivate *priv = ide_template_base_get_instance_private (self);
   guint i;
 
+  IDE_ENTRY;
+
   g_assert (G_IS_TASK (task));
   g_assert (IDE_IS_TEMPLATE_BASE (self));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
@@ -289,13 +298,15 @@ ide_template_base_parse_worker (GTask        *task,
       if (!tmpl_template_parse_file (template, fexp->file, cancellable, &error))
         {
           g_task_return_error (task, error);
-          return;
+          IDE_EXIT;
         }
 
       fexp->template = g_object_ref (template);
     }
 
   g_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
 }
 
 static void
@@ -336,6 +347,8 @@ ide_template_base_replace_cb (GObject      *object,
   FileExpansion *fexp = NULL;
   guint i;
 
+  IDE_ENTRY;
+
   g_assert (G_IS_FILE (file));
   g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (G_IS_TASK (task));
@@ -356,7 +369,8 @@ ide_template_base_replace_cb (GObject      *object,
         g_task_return_error (task, error);
       else
         g_error_free (error);
-      return;
+
+      IDE_EXIT;
     }
 
   /*
@@ -393,8 +407,13 @@ ide_template_base_replace_cb (GObject      *object,
   if (expansion->completed == expansion->files->len)
     {
       if (!g_task_get_completed (task))
-        g_task_return_boolean (task, TRUE);
+        {
+          g_task_return_boolean (task, TRUE);
+          IDE_TRACE_MSG ("Completing task");
+        }
     }
+
+  IDE_EXIT;
 }
 
 static gboolean
@@ -404,6 +423,8 @@ ide_template_base_expand (GTask *task)
   gint64 end;
   gint64 now;
 
+  IDE_ENTRY;
+
   g_assert (G_IS_TASK (task));
 
   expansion = g_task_get_task_data (task);
@@ -440,7 +461,7 @@ ide_template_base_expand (GTask *task)
       if (fexp->result == NULL)
         {
           g_task_return_error (task, error);
-          return G_SOURCE_REMOVE;
+          IDE_RETURN (G_SOURCE_REMOVE);
         }
 
       expansion->index++;
@@ -484,10 +505,10 @@ ide_template_base_expand (GTask *task)
                                          g_object_ref (task));
         }
 
-      return G_SOURCE_REMOVE;
+      IDE_RETURN (G_SOURCE_REMOVE);
     }
 
-  return G_SOURCE_CONTINUE;
+  IDE_RETURN (G_SOURCE_CONTINUE);
 }
 
 static void
@@ -499,12 +520,14 @@ ide_template_base_expand_parse_cb (GObject      *object,
   g_autoptr(GTask) task = user_data;
   GError *error = NULL;
 
+  IDE_ENTRY;
+
   g_assert (IDE_IS_TEMPLATE_BASE (self));
 
   if (!ide_template_base_parse_finish (self, result, &error))
     {
       g_task_return_error (task, error);
-      return;
+      IDE_EXIT;
     }
 
   g_timeout_add_full (G_PRIORITY_LOW,
@@ -512,6 +535,8 @@ ide_template_base_expand_parse_cb (GObject      *object,
                       (GSourceFunc)ide_template_base_expand,
                       g_object_ref (task),
                       g_object_unref);
+
+  IDE_EXIT;
 }
 
 static void
@@ -521,21 +546,25 @@ ide_template_base_expand_mkdirs_cb (GObject      *object,
 {
   IdeTemplateBase *self = (IdeTemplateBase *)object;
   g_autoptr(GTask) task = user_data;
-  GError *error = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
 
   g_assert (IDE_IS_TEMPLATE_BASE (self));
   g_assert (G_IS_TASK (task));
 
   if (!ide_template_base_mkdirs_finish (self, result, &error))
     {
-      g_task_return_error (task, error);
-      return;
+      g_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
     }
 
   ide_template_base_parse_async (self,
                                  g_task_get_cancellable (task),
                                  ide_template_base_expand_parse_cb,
                                  g_object_ref (task));
+
+  IDE_EXIT;
 }
 
 void
@@ -548,6 +577,8 @@ ide_template_base_expand_all_async (IdeTemplateBase     *self,
   g_autoptr(GTask) task = NULL;
   ExpansionTask *task_data;
 
+  IDE_ENTRY;
+
   g_return_if_fail (IDE_IS_TEMPLATE_BASE (self));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
@@ -584,7 +615,7 @@ ide_template_base_expand_all_async (IdeTemplateBase     *self,
                                G_IO_ERROR_PENDING,
                                "%s() has already been called.",
                                G_STRFUNC);
-      return;
+      IDE_EXIT;
     }
 
   priv->has_expanded = TRUE;
@@ -596,13 +627,15 @@ ide_template_base_expand_all_async (IdeTemplateBase     *self,
   if (priv->files->len == 0)
     {
       g_task_return_boolean (task, TRUE);
-      return;
+      IDE_EXIT;
     }
 
   ide_template_base_mkdirs_async (self,
                                   cancellable,
                                   ide_template_base_expand_mkdirs_cb,
                                   g_object_ref (task));
+
+  IDE_EXIT;
 }
 
 gboolean
@@ -610,10 +643,16 @@ ide_template_base_expand_all_finish (IdeTemplateBase  *self,
                                      GAsyncResult     *result,
                                      GError          **error)
 {
+  gboolean ret;
+
+  IDE_ENTRY;
+
   g_return_val_if_fail (IDE_IS_TEMPLATE_BASE (self), FALSE);
   g_return_val_if_fail (G_IS_TASK (result), FALSE);
 
-  return g_task_propagate_boolean (G_TASK (result), error);
+  ret = g_task_propagate_boolean (G_TASK (result), error);
+
+  IDE_RETURN (ret);
 }
 
 static TmplScope *
diff --git a/libidemm/idemm/Makefile.am b/libidemm/idemm/Makefile.am
index 170ea03..5a26774 100644
--- a/libidemm/idemm/Makefile.am
+++ b/libidemm/idemm/Makefile.am
@@ -14,7 +14,9 @@ libide_cflags = \
        $(LIBIDE_CFLAGS) \
        -I$(top_srcdir)/libide \
        -I$(top_srcdir)/contrib/pnl \
+       -I$(top_srcdir)/contrib/tmpl \
        -I$(top_builddir)/contrib/pnl \
+       -I$(top_builddir)/contrib/tmpl \
        -I$(top_builddir)/libide
 
 AM_CPPFLAGS = $(local_cppflags) $(IDEMM_CFLAGS) $(libide_cflags)
diff --git a/libidemm/tools/Makefile.am b/libidemm/tools/Makefile.am
index cb0ee75..285d842 100644
--- a/libidemm/tools/Makefile.am
+++ b/libidemm/tools/Makefile.am
@@ -21,7 +21,9 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/libide \
        -I$(top_builddir)/libide \
        -I$(top_srcdir)/contrib/pnl \
-       -I$(top_builddir)/contrib/pnl
+       -I$(top_srcdir)/contrib/tmpl \
+       -I$(top_builddir)/contrib/pnl \
+       -I$(top_builddir)/contrib/tmpl
 
 endif
 
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 0827d68..db83ac5 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -18,6 +18,7 @@ SUBDIRS = \
        gettext \
        git \
        gnome-code-assistance \
+       gobject-templates \
        hello-cpp \
        html-completion \
        html-preview \
diff --git a/plugins/gobject-templates/Makefile.am b/plugins/gobject-templates/Makefile.am
new file mode 100644
index 0000000..0c49eb2
--- /dev/null
+++ b/plugins/gobject-templates/Makefile.am
@@ -0,0 +1,54 @@
+if ENABLE_GOBJECT_TEMPLATES
+
+DISTCLEANFILES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libgobject-templates.la
+dist_plugin_DATA = gobject-templates.plugin
+
+libgobject_templates_la_SOURCES = \
+       gobject-templates-plugin.c \
+       gbp-gobject-dialog.c \
+       gbp-gobject-dialog.h \
+       gbp-gobject-property.c \
+       gbp-gobject-property.h \
+       gbp-gobject-property-editor.c \
+       gbp-gobject-property-editor.h \
+       gbp-gobject-signal.c \
+       gbp-gobject-signal.h \
+       gbp-gobject-signal-editor.c \
+       gbp-gobject-signal-editor.h \
+       gbp-gobject-spec.c \
+       gbp-gobject-spec.h \
+       gbp-gobject-spec-editor.c \
+       gbp-gobject-spec-editor.h \
+       gbp-gobject-template.c \
+       gbp-gobject-template.h \
+       gbp-gobject-workbench-addin.c \
+       gbp-gobject-workbench-addin.h \
+       $(NULL)
+
+nodist_libgobject_templates_la_SOURCES = \
+       resources.c \
+       resources.h
+
+libgobject_templates_la_CFLAGS = \
+       $(PLUGIN_CFLAGS) \
+       $(NULL)
+
+libgobject_templates_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+
+glib_resources_c = resources.c
+glib_resources_h = resources.h
+glib_resources_xml = gobject-templates.gresource.xml
+glib_resources_namespace = gbp_gobject_templates
+include $(top_srcdir)/build/autotools/Makefile.am.gresources
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/gobject-templates/configure.ac b/plugins/gobject-templates/configure.ac
new file mode 100644
index 0000000..28a0a47
--- /dev/null
+++ b/plugins/gobject-templates/configure.ac
@@ -0,0 +1,12 @@
+# --enable-gobject-templates=yes/no
+AC_ARG_ENABLE([gobject-templates],
+              [AS_HELP_STRING([--enable-gobject-templates=@<:@yes/no@:>@],
+                              [Build with support for gobject templates.])],
+              [enable_gobject_templates=$enableval],
+              [enable_gobject_templates=yes])
+
+# for if ENABLE_GOBJECT_TEMPLATES in Makefile.am
+AM_CONDITIONAL(ENABLE_GOBJECT_TEMPLATES, test x$enable_gobject_templates != xno)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/gobject-templates/Makefile])
diff --git a/plugins/gobject-templates/gbp-gobject-dialog.c b/plugins/gobject-templates/gbp-gobject-dialog.c
new file mode 100644
index 0000000..a17e46c
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-dialog.c
@@ -0,0 +1,478 @@
+/* gbp-gobject-dialog.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-dialog"
+
+#include <glib/gi18n.h>
+#include <ide.h>
+
+#include "gbp-gobject-dialog.h"
+#include "gbp-gobject-property.h"
+#include "gbp-gobject-property-editor.h"
+#include "gbp-gobject-signal.h"
+#include "gbp-gobject-signal-editor.h"
+#include "gbp-gobject-spec.h"
+#include "gbp-gobject-spec-editor.h"
+
+struct _GbpGobjectDialog
+{
+  GtkAssistant              parent_instance;
+
+  GbpGobjectSpec           *spec;
+  GSimpleActionGroup       *actions;
+
+  GbpGobjectSpecEditor     *editor;
+  GtkWidget                *editor_page;
+  GbpGobjectPropertyEditor *property_editor;
+  GtkListBox               *properties_list_box;
+  GtkStack                 *property_stack;
+};
+
+G_DEFINE_TYPE (GbpGobjectDialog, gbp_gobject_dialog, GTK_TYPE_ASSISTANT)
+
+enum {
+  PROP_0,
+  PROP_DIRECTORY,
+  PROP_SPEC,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+GtkWidget *
+gbp_gobject_dialog_new (void)
+{
+  return g_object_new (GBP_TYPE_GOBJECT_DIALOG, NULL);
+}
+
+static void
+spec_notify_ready (GbpGobjectDialog *self,
+                   GParamSpec       *pspec,
+                   GbpGobjectSpec   *spec)
+{
+  gboolean complete;
+
+  g_assert (GBP_IS_GOBJECT_DIALOG (self));
+  g_assert (pspec != NULL);
+  g_assert (GBP_IS_GOBJECT_SPEC (spec));
+
+  complete = gbp_gobject_spec_get_ready (spec);
+
+  gtk_container_child_set (GTK_CONTAINER (self), self->editor_page,
+                           "complete", complete,
+                           NULL);
+}
+
+static gboolean
+name_to_label (GBinding     *binding,
+               const GValue *from_value,
+               GValue       *to_value,
+               gpointer      user_data)
+{
+  const gchar *str;
+
+  g_assert (G_IS_BINDING (binding));
+  g_assert (from_value != NULL);
+
+  str = g_value_get_string (from_value);
+
+  if (str == NULL)
+    g_value_set_static_string (to_value, _("New Property"));
+  else
+    g_value_set_string (to_value, str);
+
+  return TRUE;
+}
+
+static GtkWidget *
+create_property_row (gpointer item,
+                     gpointer user_data)
+{
+  GbpGobjectDialog *self = user_data;
+  GbpGobjectProperty *property = item;
+  GtkLabel *label;
+  GtkListBoxRow *row;
+
+  g_assert (GBP_IS_GOBJECT_DIALOG (self));
+  g_assert (GBP_IS_GOBJECT_PROPERTY (property));
+
+  row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+                      "visible", TRUE,
+                      NULL);
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "margin", 6,
+                        "visible", TRUE,
+                        "xalign", 0.0f,
+                        NULL);
+
+  gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (label));
+
+  g_object_bind_property_full (property, "name", label, "label", G_BINDING_SYNC_CREATE,
+                               name_to_label, NULL, NULL, NULL);
+
+  g_object_set_data_full (G_OBJECT (row),
+                          "GBP_GOBJECT_PROPERTY",
+                          g_object_ref (property),
+                          g_object_unref);
+
+  return GTK_WIDGET (row);
+}
+
+GbpGobjectSpec *
+gbp_gobject_dialog_get_spec (GbpGobjectDialog *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_DIALOG (self), NULL);
+
+  return self->spec;
+}
+
+void
+gbp_gobject_dialog_set_spec (GbpGobjectDialog *self,
+                             GbpGobjectSpec   *spec)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_DIALOG (self));
+  g_return_if_fail (GBP_IS_GOBJECT_SPEC (spec));
+
+  if (g_set_object (&self->spec, spec))
+    {
+      GListModel *props;
+
+      g_signal_connect_object (spec,
+                               "notify::ready",
+                               G_CALLBACK (spec_notify_ready),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      gbp_gobject_spec_editor_set_spec (self->editor, spec);
+
+      if (spec != NULL)
+        {
+          props = gbp_gobject_spec_get_properties (spec);
+          gtk_list_box_bind_model (self->properties_list_box,
+                                   props,
+                                   create_property_row,
+                                   self,
+                                   NULL);
+        }
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SPEC]);
+    }
+}
+
+/**
+ * gbp_gobject_dialog_get_directory:
+ *
+ * Returns: (nullable) (transfer full): A #GFile or %NULL.
+ */
+GFile *
+gbp_gobject_dialog_get_directory (GbpGobjectDialog *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_DIALOG (self), NULL);
+
+  return gbp_gobject_spec_editor_get_directory (self->editor);
+}
+
+void
+gbp_gobject_dialog_set_directory (GbpGobjectDialog *self,
+                                  GFile            *directory)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_DIALOG (self));
+  g_return_if_fail (!directory || G_IS_FILE (directory));
+
+  gbp_gobject_spec_editor_set_directory (self->editor, directory);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIRECTORY]);
+}
+
+static void
+gbp_gobject_dialog_property_selected (GbpGobjectDialog *self,
+                                      GtkListBoxRow    *row,
+                                      GtkListBox       *list_box)
+{
+  GAction *action;
+
+  g_assert (GBP_IS_GOBJECT_DIALOG (self));
+  g_assert (!row || GTK_IS_LIST_BOX_ROW (row));
+  g_assert (GTK_IS_LIST_BOX (list_box));
+
+  action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "remove-property");
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+
+  if (row != NULL)
+    {
+      GbpGobjectProperty *prop;
+
+      prop = g_object_get_data (G_OBJECT (row), "GBP_GOBJECT_PROPERTY");
+      g_assert (!prop || GBP_IS_GOBJECT_PROPERTY (prop));
+      gbp_gobject_property_editor_set_property (self->property_editor, prop);
+      gtk_stack_set_visible_child_name (self->property_stack, "property");
+      g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+    }
+  else
+    {
+      gbp_gobject_property_editor_set_property (self->property_editor, NULL);
+      gtk_stack_set_visible_child_name (self->property_stack, "empty");
+      g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+    }
+}
+
+static void
+gbp_gobject_dialog_constructed (GObject *object)
+{
+  GbpGobjectDialog *self = (GbpGobjectDialog *)object;
+
+  g_assert (GBP_IS_GOBJECT_DIALOG (self));
+
+  G_OBJECT_CLASS (gbp_gobject_dialog_parent_class)->constructed (object);
+
+  if (self->spec == NULL)
+    {
+      g_autoptr(GbpGobjectSpec) spec = gbp_gobject_spec_new ();
+
+      gbp_gobject_dialog_set_spec (self, spec);
+    }
+}
+
+static void
+gbp_gobject_dialog_destroy (GtkWidget *widget)
+{
+  GbpGobjectDialog *self = (GbpGobjectDialog *)widget;
+
+  if (self->properties_list_box != NULL)
+    g_signal_handlers_disconnect_by_func (self->properties_list_box,
+                                          G_CALLBACK (gbp_gobject_dialog_property_selected),
+                                          self);
+
+  GTK_WIDGET_CLASS (gbp_gobject_dialog_parent_class)->destroy (widget);
+}
+
+static void
+gbp_gobject_dialog_finalize (GObject *object)
+{
+  GbpGobjectDialog *self = (GbpGobjectDialog *)object;
+
+  g_clear_object (&self->spec);
+  g_clear_object (&self->actions);
+
+  G_OBJECT_CLASS (gbp_gobject_dialog_parent_class)->finalize (object);
+}
+
+static void
+gbp_gobject_dialog_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  GbpGobjectDialog *self = GBP_GOBJECT_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIRECTORY:
+      g_value_set_object (value, gbp_gobject_dialog_get_directory (self));
+      break;
+
+    case PROP_SPEC:
+      g_value_set_object (value, gbp_gobject_dialog_get_spec (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_dialog_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  GbpGobjectDialog *self = GBP_GOBJECT_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIRECTORY:
+      gbp_gobject_dialog_set_directory (self, g_value_get_object (value));
+      break;
+
+    case PROP_SPEC:
+      gbp_gobject_dialog_set_spec (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_dialog_class_init (GbpGobjectDialogClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = gbp_gobject_dialog_constructed;
+  object_class->finalize = gbp_gobject_dialog_finalize;
+  object_class->get_property = gbp_gobject_dialog_get_property;
+  object_class->set_property = gbp_gobject_dialog_set_property;
+
+  widget_class->destroy = gbp_gobject_dialog_destroy;
+
+  properties [PROP_DIRECTORY] =
+    g_param_spec_object ("directory",
+                         "Directory",
+                         "Directory",
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SPEC] =
+    g_param_spec_object ("spec",
+                         "Spec",
+                         "The GObject Specification",
+                         GBP_TYPE_GOBJECT_SPEC,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/gobject-templates/gbp-gobject-dialog.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectDialog, editor);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectDialog, editor_page);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectDialog, properties_list_box);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectDialog, property_editor);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectDialog, property_stack);
+
+  g_type_ensure (GBP_TYPE_GOBJECT_PROPERTY);
+  g_type_ensure (GBP_TYPE_GOBJECT_PROPERTY_EDITOR);
+  g_type_ensure (GBP_TYPE_GOBJECT_SIGNAL);
+  g_type_ensure (GBP_TYPE_GOBJECT_SIGNAL_EDITOR);
+  g_type_ensure (GBP_TYPE_GOBJECT_SPEC);
+  g_type_ensure (GBP_TYPE_GOBJECT_SPEC_EDITOR);
+}
+
+static void
+force_sidebar_hidden (GtkWidget  *widget,
+                      GParamSpec *pspec,
+                      gpointer    user_data)
+{
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (pspec != NULL);
+
+  if (gtk_widget_get_visible (widget))
+    gtk_widget_hide (widget);
+}
+
+static void
+add_property (GSimpleAction *action,
+              GVariant      *variant,
+              gpointer       user_data)
+{
+  GbpGobjectDialog *self = user_data;
+  g_autoptr(GbpGobjectProperty) property = NULL;
+  GListModel *model;
+  guint n_props;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (variant == NULL);
+  g_assert (GBP_IS_GOBJECT_DIALOG (self));
+
+  if (self->spec == NULL)
+    return;
+
+  property = gbp_gobject_property_new ();
+
+  gbp_gobject_spec_add_property (self->spec, property);
+
+  model = gbp_gobject_spec_get_properties (self->spec);
+  n_props = g_list_model_get_n_items (model);
+
+  if (n_props > 0)
+    {
+      GtkListBoxRow *row;
+
+      row = gtk_list_box_get_row_at_index (self->properties_list_box, n_props - 1);
+
+      if (row != NULL)
+        {
+          gtk_list_box_select_row (self->properties_list_box, row);
+          gtk_widget_grab_focus (GTK_WIDGET (self->property_editor));
+        }
+    }
+}
+
+static void
+remove_property (GSimpleAction *action,
+                 GVariant      *variant,
+                 gpointer       user_data)
+{
+  GbpGobjectDialog *self = user_data;
+  GtkListBoxRow *row;
+
+  g_assert (GBP_IS_GOBJECT_DIALOG (self));
+
+  row = gtk_list_box_get_selected_row (self->properties_list_box);
+
+  if (row != NULL)
+    {
+      GbpGobjectProperty *prop;
+
+      prop = g_object_get_data (G_OBJECT (row), "GBP_GOBJECT_PROPERTY");
+      gbp_gobject_spec_remove_property (self->spec, prop);
+    }
+}
+
+static void
+gbp_gobject_dialog_init (GbpGobjectDialog *self)
+{
+  g_autoptr(GSimpleActionGroup) group = NULL;
+  GActionEntry entries[] = {
+    { "add-property", add_property },
+    { "remove-property", remove_property },
+  };
+  GtkWidget *child1;
+  GtkWidget *child2;
+  GAction *action;
+  GList *list;
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (self->properties_list_box,
+                           "row-selected",
+                           G_CALLBACK (gbp_gobject_dialog_property_selected),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  /* Hide the sidebar widget */
+  child1 = gtk_bin_get_child (GTK_BIN (self));
+  list = gtk_container_get_children (GTK_CONTAINER (child1));
+  child2 = list->data;
+  g_signal_connect_after (child2,
+                          "notify::visible",
+                          G_CALLBACK (force_sidebar_hidden),
+                          NULL);
+  gtk_widget_hide (child2);
+  g_list_free (list);
+
+  /* Add actions needed by UI */
+  group = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), self);
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "dialog", G_ACTION_GROUP (group));
+  self->actions = g_steal_pointer (&group);
+
+  /* Disable remove-property by default */
+  action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "remove-property");
+  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+}
diff --git a/plugins/gobject-templates/gbp-gobject-dialog.h b/plugins/gobject-templates/gbp-gobject-dialog.h
new file mode 100644
index 0000000..7f6719a
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-dialog.h
@@ -0,0 +1,40 @@
+/* gbp-gobject-dialog.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_DIALOG_H
+#define GBP_GOBJECT_DIALOG_H
+
+#include <gtk/gtk.h>
+
+#include "gbp-gobject-spec.h"
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GOBJECT_DIALOG (gbp_gobject_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectDialog, gbp_gobject_dialog, GBP, GOBJECT_DIALOG, GtkAssistant)
+
+GtkWidget      *gbp_gobject_dialog_new           (void);
+GFile          *gbp_gobject_dialog_get_directory (GbpGobjectDialog *self);
+void            gbp_gobject_dialog_set_directory (GbpGobjectDialog *self,
+                                                  GFile            *directory);
+GbpGobjectSpec *gbp_gobject_dialog_get_spec      (GbpGobjectDialog *self);
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_DIALOG_H */
diff --git a/plugins/gobject-templates/gbp-gobject-dialog.ui b/plugins/gobject-templates/gbp-gobject-dialog.ui
new file mode 100644
index 0000000..be219e9
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-dialog.ui
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpGobjectDialog" parent="GtkAssistant">
+    <property name="default-width">825</property>
+    <property name="default-height">500</property>
+    <child>
+      <object class="GtkScrolledWindow" id="editor_page">
+        <property name="hscrollbar-policy">never</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GbpGobjectSpecEditor" id="editor">
+            <property name="border-width">36</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="has-padding">false</property>
+        <property name="page-type">content</property>
+        <property name="title" translatable="yes">New Class</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="border-width">36</property>
+        <property name="spacing">36</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">vertical</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkScrolledWindow">
+                <property name="hscrollbar-policy">never</property>
+                <property name="shadow-type">in</property>
+                <property name="visible">true</property>
+                <property name="width-request">275</property>
+                <property name="vexpand">true</property>
+                <child>
+                  <object class="GtkListBox" id="properties_list_box">
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">horizontal</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="inline-toolbar"/>
+                </style>
+                <child>
+                  <object class="GtkButton">
+                    <property name="action-name">dialog.add-property</property>
+                    <property name="visible">true</property>
+                    <property name="hexpand">false</property>
+                    <property name="halign">start</property>
+                    <style>
+                      <class name="image-button"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="icon-name">list-add-symbolic</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="action-name">dialog.remove-property</property>
+                    <property name="visible">true</property>
+                    <property name="hexpand">false</property>
+                    <property name="halign">start</property>
+                    <style>
+                      <class name="image-button"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="icon-name">list-remove-symbolic</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="hexpand">true</property>
+            <property name="orientation">vertical</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkStack" id="property_stack">
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkButton" id="add_property_button">
+                        <property name="action-name">dialog.add-property</property>
+                        <property name="label" translatable="yes">Add Property</property>
+                        <property name="halign">center</property>
+                        <property name="valign">center</property>
+                        <property name="expand">true</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name">empty</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GbpGobjectPropertyEditor" id="property_editor">
+                    <property name="visible">true</property>
+                  </object>
+                  <packing>
+                    <property name="name">property</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="complete">true</property>
+        <property name="has-padding">false</property>
+        <property name="page-type">content</property>
+        <property name="title" translatable="yes">Properties</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="border-width">36</property>
+        <property name="spacing">36</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">vertical</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkScrolledWindow">
+                <property name="hscrollbar-policy">never</property>
+                <property name="shadow-type">in</property>
+                <property name="visible">true</property>
+                <property name="width-request">275</property>
+                <property name="vexpand">true</property>
+                <child>
+                  <object class="GtkListBox" id="signals_list_box">
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">horizontal</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="inline-toolbar"/>
+                </style>
+                <child>
+                  <object class="GtkButton">
+                    <property name="action-name">dialog.add-signal</property>
+                    <property name="visible">true</property>
+                    <property name="hexpand">false</property>
+                    <property name="halign">start</property>
+                    <style>
+                      <class name="image-button"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="icon-name">list-add-symbolic</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="action-name">dialog.remove-signal</property>
+                    <property name="visible">true</property>
+                    <property name="hexpand">false</property>
+                    <property name="halign">start</property>
+                    <style>
+                      <class name="image-button"/>
+                    </style>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="icon-name">list-remove-symbolic</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="hexpand">true</property>
+            <property name="orientation">vertical</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkStack" id="signal_stack">
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">vertical</property>
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkButton" id="add_signal_button">
+                        <property name="action-name">dialog.add-signal</property>
+                        <property name="label" translatable="yes">Add Signal</property>
+                        <property name="halign">center</property>
+                        <property name="valign">center</property>
+                        <property name="expand">true</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name">empty</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GbpGobjectSignalEditor" id="signal_editor">
+                    <property name="visible">true</property>
+                  </object>
+                  <packing>
+                    <property name="name">signal</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="complete">true</property>
+        <property name="has-padding">false</property>
+        <property name="page-type">content</property>
+        <property name="title" translatable="yes">Signals</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel">
+        <property name="label">Summary</property>
+        <property name="visible">true</property>
+      </object>
+      <packing>
+        <property name="complete">true</property>
+        <property name="has-padding">false</property>
+        <property name="page-type">summary</property>
+        <property name="title" translatable="yes">Summary</property>
+      </packing>
+    </child>
+  </template>
+</interface>
diff --git a/plugins/gobject-templates/gbp-gobject-property-editor.c 
b/plugins/gobject-templates/gbp-gobject-property-editor.c
new file mode 100644
index 0000000..5fb434e
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-property-editor.c
@@ -0,0 +1,280 @@
+/* gbp-gobject-property-editor.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-property-editor"
+
+#include <egg-binding-group.h>
+#include <egg-state-machine.h>
+
+#include "gbp-gobject-property.h"
+#include "gbp-gobject-property-editor.h"
+
+struct _GbpGobjectPropertyEditor
+{
+  GtkBin              parent_instance;
+
+  GbpGobjectProperty *property;
+  EggBindingGroup    *property_bindings;
+
+  GtkEntry           *default_entry;
+  GtkEntry           *max_entry;
+  GtkEntry           *min_entry;
+  GtkEntry           *name_entry;
+  GtkEntry           *ctype_entry;
+  GtkComboBoxText    *kind_combobox;
+  EggStateMachine    *kind_state;
+  GtkSwitch          *readable_switch;
+  GtkSwitch          *writable_switch;
+  GtkSwitch          *construct_only_switch;
+};
+
+G_DEFINE_TYPE (GbpGobjectPropertyEditor, gbp_gobject_property_editor, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  PROP_PROPERTY,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+void
+gbp_gobject_property_editor_set_property (GbpGobjectPropertyEditor *self,
+                                          GbpGobjectProperty       *property)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_PROPERTY_EDITOR (self));
+  g_return_if_fail (!property || GBP_IS_GOBJECT_PROPERTY (property));
+
+  if (g_set_object (&self->property, property))
+    {
+      egg_binding_group_set_source (self->property_bindings, property);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROPERTY]);
+    }
+}
+
+static void
+gbp_gobject_property_editor_grab_focus (GtkWidget *widget)
+{
+  GbpGobjectPropertyEditor *self = (GbpGobjectPropertyEditor *)widget;
+
+  g_assert (GBP_IS_GOBJECT_PROPERTY_EDITOR (self));
+
+  gtk_widget_grab_focus (GTK_WIDGET (self->name_entry));
+}
+
+static void
+gbp_gobject_property_editor_finalize (GObject *object)
+{
+  GbpGobjectPropertyEditor *self = (GbpGobjectPropertyEditor *)object;
+
+  g_clear_object (&self->property);
+  g_clear_object (&self->property_bindings);
+
+  G_OBJECT_CLASS (gbp_gobject_property_editor_parent_class)->finalize (object);
+}
+
+static void
+gbp_gobject_property_editor_do_get_property (GObject    *object,
+                                             guint       prop_id,
+                                             GValue     *value,
+                                             GParamSpec *pspec)
+{
+  GbpGobjectPropertyEditor *self = GBP_GOBJECT_PROPERTY_EDITOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROPERTY:
+      g_value_set_object (value, self->property);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_property_editor_do_set_property (GObject      *object,
+                                             guint         prop_id,
+                                             const GValue *value,
+                                             GParamSpec   *pspec)
+{
+  GbpGobjectPropertyEditor *self = GBP_GOBJECT_PROPERTY_EDITOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_PROPERTY:
+      gbp_gobject_property_editor_set_property (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_property_editor_class_init (GbpGobjectPropertyEditorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gbp_gobject_property_editor_finalize;
+  object_class->get_property = gbp_gobject_property_editor_do_get_property;
+  object_class->set_property = gbp_gobject_property_editor_do_set_property;
+
+  widget_class->grab_focus = gbp_gobject_property_editor_grab_focus;
+
+  properties [PROP_PROPERTY] =
+    g_param_spec_object ("property",
+                         "Property",
+                         "Property",
+                         GBP_TYPE_GOBJECT_PROPERTY,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/gobject-templates/gbp-gobject-property-editor.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, construct_only_switch);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, ctype_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, default_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, kind_combobox);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, kind_state);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, max_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, min_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, name_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, readable_switch);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, writable_switch);
+}
+
+static gboolean
+string_to_text (GBinding     *binding,
+                const GValue *from_value,
+                GValue       *to_value,
+                gpointer      user_data)
+{
+  const gchar *str;
+
+  str = g_value_get_string (from_value);
+
+  if (str == NULL)
+    g_value_set_static_string (to_value, "");
+  else
+    g_value_set_string (to_value, str);
+
+  return TRUE;
+}
+
+static gboolean
+kind_to_string (GBinding     *binding,
+                const GValue *from_value,
+                GValue       *to_value,
+                gpointer      user_data)
+{
+  const GEnumValue *value;
+  GEnumClass *klass;
+
+  /* klass should always be available here */
+  klass = g_type_class_peek (GBP_TYPE_GOBJECT_PROPERTY_KIND);
+  g_assert (klass != NULL);
+
+  value = g_enum_get_value (klass, g_value_get_enum (from_value));
+
+  if (value == NULL)
+    return FALSE;
+
+  g_value_set_string (to_value, value->value_nick);
+
+  return TRUE;
+}
+
+static gboolean
+string_to_kind (GBinding     *binding,
+                const GValue *from_value,
+                GValue       *to_value,
+                gpointer      user_data)
+{
+  const GEnumValue *value;
+  GEnumClass *klass;
+
+  /* klass should always be available here */
+  klass = g_type_class_peek (GBP_TYPE_GOBJECT_PROPERTY_KIND);
+  g_assert (klass != NULL);
+
+  value = g_enum_get_value_by_nick (klass, g_value_get_string (from_value));
+
+  if (value == NULL)
+    return FALSE;
+
+  g_value_set_enum (to_value, value->value);
+
+  return TRUE;
+}
+
+static void
+gbp_gobject_property_editor_init (GbpGobjectPropertyEditor *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->property_bindings = egg_binding_group_new ();
+
+  egg_binding_group_bind_full (self->property_bindings, "name",
+                               self->name_entry, "text",
+                               G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                               string_to_text, NULL, NULL, NULL);
+
+  egg_binding_group_bind_full (self->property_bindings, "kind",
+                               self->kind_combobox, "active-id",
+                               G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                               kind_to_string, string_to_kind, NULL, NULL);
+
+  egg_binding_group_bind_full (self->property_bindings, "kind",
+                               self->kind_state, "state",
+                               G_BINDING_SYNC_CREATE,
+                               kind_to_string, string_to_kind, NULL, NULL);
+
+  egg_binding_group_bind_full (self->property_bindings, "ctype",
+                               self->ctype_entry, "text",
+                               G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                               string_to_text, NULL, NULL, NULL);
+
+  egg_binding_group_bind_full (self->property_bindings, "minimum",
+                               self->min_entry, "text",
+                               G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                               string_to_text, NULL, NULL, NULL);
+
+  egg_binding_group_bind_full (self->property_bindings, "maximum",
+                               self->max_entry, "text",
+                               G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                               string_to_text, NULL, NULL, NULL);
+
+  egg_binding_group_bind_full (self->property_bindings, "default",
+                               self->default_entry, "text",
+                               G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
+                               string_to_text, NULL, NULL, NULL);
+
+  egg_binding_group_bind (self->property_bindings, "construct-only",
+                          self->construct_only_switch, "active",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+  egg_binding_group_bind (self->property_bindings, "readable",
+                          self->readable_switch, "active",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+  egg_binding_group_bind (self->property_bindings, "writable",
+                          self->writable_switch, "active",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+}
diff --git a/plugins/gobject-templates/gbp-gobject-property-editor.h 
b/plugins/gobject-templates/gbp-gobject-property-editor.h
new file mode 100644
index 0000000..fe5b1ba
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-property-editor.h
@@ -0,0 +1,36 @@
+/* gbp-gobject-property-editor.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_PROPERTY_EDITOR_H
+#define GBP_GOBJECT_PROPERTY_EDITOR_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GOBJECT_PROPERTY_EDITOR (gbp_gobject_property_editor_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectPropertyEditor, gbp_gobject_property_editor, GBP, GOBJECT_PROPERTY_EDITOR, 
GtkBin)
+
+void gbp_gobject_property_editor_set_property (GbpGobjectPropertyEditor *self,
+                                               GbpGobjectProperty       *property);
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_PROPERTY_EDITOR_H */
+
diff --git a/plugins/gobject-templates/gbp-gobject-property-editor.ui 
b/plugins/gobject-templates/gbp-gobject-property-editor.ui
new file mode 100644
index 0000000..049888e
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-property-editor.ui
@@ -0,0 +1,724 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpGobjectPropertyEditor" parent="GtkBin">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="visible">true</property>
+
+        <!-- Name -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label1">
+                <property name="label" translatable="yes">Name</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkEntry" id="name_entry">
+                <property name="width-chars">25</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Name Description Text -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="margin-top">6</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="name_align_label">
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+                <property name="xalign">1.0</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkLabel" id="name_help_label">
+                <property name="label" translatable="yes">Unique name that should only contain alpha 
characters or - such as “name” or “item-type”.</property>
+                <property name="xalign">0.0</property>
+                <property name="wrap">true</property>
+                <property name="visible">true</property>
+                <property name="max-width-chars">25</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+                <attributes>
+                  <attribute name="scale" value="0.833333"/>
+                </attributes>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Type -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="margin-top">18</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label2">
+                <property name="label" translatable="yes">Kind</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkComboBoxText" id="kind_combobox">
+                <property name="visible">true</property>
+                <items>
+                  <item translatable="yes" id="boolean">Boolean</item>
+                  <item translatable="yes" id="boxed">Boxed</item>
+                  <item translatable="yes" id="char">Character</item>
+                  <item translatable="yes" id="double">Double</item>
+                  <item translatable="yes" id="enum">Enum</item>
+                  <item translatable="yes" id="flags">Flags</item>
+                  <item translatable="yes" id="float">Float</item>
+                  <item translatable="yes" id="int">Signed Integer</item>
+                  <item translatable="yes" id="int64">Signed 64-bit Integer</item>
+                  <item translatable="yes" id="long">Signed Long</item>
+                  <item translatable="yes" id="object">Object</item>
+                  <item translatable="yes" id="pointer">Pointer</item>
+                  <item translatable="yes" id="string">String</item>
+                  <item translatable="yes" id="uint">Unsinged Integer</item>
+                  <item translatable="yes" id="uint64">Unsigned 64-bit Integer</item>
+                  <item translatable="yes" id="ulong">Unsigned Long</item>
+                  <item translatable="yes" id="unichar">Unicode Character</item>
+                  <item translatable="yes" id="variant">Variant</item>
+                </items>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Type -->
+        <child>
+          <object class="GtkBox" id="ctype_box">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="margin-top">18</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label9">
+                <property name="label" translatable="yes">Type</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkEntry" id="ctype_entry">
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Default -->
+        <child>
+          <object class="GtkBox" id="default_box">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="margin-top">18</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label3">
+                <property name="label" translatable="yes">Default</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkEntry" id="default_entry">
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Minimum -->
+        <child>
+          <object class="GtkBox" id="min_box">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="margin-top">12</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label4">
+                <property name="label" translatable="yes">Minimum</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkEntry" id="min_entry">
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Maximum -->
+        <child>
+          <object class="GtkBox" id="max_box">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="margin-top">12</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label5">
+                <property name="label" translatable="yes">Maximum</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkEntry" id="max_entry">
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Read -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <property name="margin-top">18</property>
+            <child>
+              <object class="GtkLabel" id="label6">
+                <property name="label" translatable="yes">Readable</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkBox" id="readable_container">
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkSwitch" id="readable_switch">
+                    <property name="active">true</property>
+                    <property name="halign">start</property>
+                    <property name="valign">center</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Write -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <property name="margin-top">6</property>
+            <child>
+              <object class="GtkLabel" id="label7">
+                <property name="label" translatable="yes">Writable</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkBox" id="writable_container">
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkSwitch" id="writable_switch">
+                    <property name="active">true</property>
+                    <property name="halign">start</property>
+                    <property name="valign">center</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Construct Only -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <property name="margin-top">6</property>
+            <child>
+              <object class="GtkLabel" id="label8">
+                <property name="label" translatable="yes">Construct Only</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkBox" id="construct_only_container">
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkSwitch" id="construct_only_switch">
+                    <property name="active">true</property>
+                    <property name="halign">start</property>
+                    <property name="valign">center</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+      </object>
+    </child>
+  </template>
+
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="name_entry"/>
+      <widget name="name_help_label"/>
+      <widget name="kind_combobox"/>
+      <widget name="max_entry"/>
+      <widget name="min_entry"/>
+      <widget name="default_entry"/>
+      <widget name="readable_container"/>
+      <widget name="writable_container"/>
+      <widget name="construct_only_container"/>
+      <widget name="ctype_entry"/>
+    </widgets>
+  </object>
+
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="label1"/>
+      <widget name="label2"/>
+      <widget name="label3"/>
+      <widget name="label4"/>
+      <widget name="label5"/>
+      <widget name="label6"/>
+      <widget name="label7"/>
+      <widget name="label8"/>
+      <widget name="label9"/>
+      <widget name="name_align_label"/>
+    </widgets>
+  </object>
+
+  <object class="EggStateMachine" id="kind_state">
+    <states>
+
+      <state name="boolean">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">FALSE</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="boxed">
+        <object id="default_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="ctype_entry">
+          <property name="placeholder-text">MyBoxed</property>
+        </object>
+      </state>
+
+      <state name="char">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXINT8</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">G_MININT8</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="double">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0.0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXDOUBLE</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">-G_MINDOUBLE</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="enum">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="ctype_entry">
+          <property name="placeholder-text">MyEnum</property>
+        </object>
+      </state>
+
+      <state name="flags">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="ctype_entry">
+          <property name="placeholder-text">MyFlags</property>
+        </object>
+      </state>
+
+      <state name="float">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0.0f</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXFLOAT</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">-G_MAXFLOAT</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="int">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXINT</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">G_MININT</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="int64">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXINT64</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">G_MININT64</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="long">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXLONG</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">G_MINLONG</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="object">
+        <object id="default_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="ctype_entry">
+          <property name="placeholder-text">MyObject</property>
+        </object>
+      </state>
+
+      <state name="pointer">
+        <object id="default_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="ctype_entry">
+          <property name="text">gpointer</property>
+          <property name="placeholder-text">MyPointerType</property>
+        </object>
+      </state>
+
+      <state name="string">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">NULL</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="uint">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXUINT</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="uint64">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXUINT64</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="ulong">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXULONG</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="unichar">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="max_entry">
+          <property name="text">G_MAXUINT</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="min_entry">
+          <property name="text">0</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+      <state name="variant">
+        <object id="default_box">
+          <property name="visible">true</property>
+        </object>
+        <object id="default_entry">
+          <property name="text">NULL</property>
+        </object>
+        <object id="max_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="min_box">
+          <property name="visible">false</property>
+        </object>
+        <object id="ctype_box">
+          <property name="visible">false</property>
+        </object>
+      </state>
+
+    </states>
+  </object>
+</interface>
diff --git a/plugins/gobject-templates/gbp-gobject-property.c 
b/plugins/gobject-templates/gbp-gobject-property.c
new file mode 100644
index 0000000..bd73fe3
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-property.c
@@ -0,0 +1,515 @@
+/* gbp-gobject-property.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-property"
+
+#include "gbp-gobject-property.h"
+
+struct _GbpGobjectProperty
+{
+  GObject parent_instance;
+
+  GbpGobjectPropertyKind kind;
+
+  gchar *ctype;
+  gchar *name;
+  gchar *default_;
+  gchar *minimum;
+  gchar *maximum;
+
+  guint readable : 1;
+  guint writable : 1;
+  guint construct_only : 1;
+};
+
+enum {
+  PROP_0,
+  PROP_CNAME,
+  PROP_CONSTRUCT_ONLY,
+  PROP_CTYPE,
+  PROP_DEFAULT,
+  PROP_GTYPE,
+  PROP_KIND,
+  PROP_MAXIMUM,
+  PROP_MINIMUM,
+  PROP_NAME,
+  PROP_READABLE,
+  PROP_WRITABLE,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (GbpGobjectProperty, gbp_gobject_property, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_gobject_property_set_ctype (GbpGobjectProperty *self,
+                                const gchar        *ctype)
+{
+  g_assert (GBP_IS_GOBJECT_PROPERTY (self));
+
+  if (g_strcmp0 (ctype, self->ctype) != 0)
+    {
+      g_free (self->ctype);
+      self->ctype = g_strdup (ctype);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CTYPE]);
+    }
+}
+
+static gchar *
+mangle_name (const gchar *name)
+{
+  GString *symbol_name = g_string_new ("");
+  gint i;
+
+  /* copied from gtkbuilder.c */
+
+  for (i = 0; name[i] != '\0'; i++)
+    {
+      /* skip if uppercase, first or previous is uppercase */
+      if ((name[i] == g_ascii_toupper (name[i]) &&
+           i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) ||
+          (i > 2 && name[i]   == g_ascii_toupper (name[i]) &&
+           name[i-1] == g_ascii_toupper (name[i-1]) &&
+           name[i-2] == g_ascii_toupper (name[i-2])))
+        g_string_append_c (symbol_name, '_');
+      g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
+    }
+
+  return g_string_free (symbol_name, FALSE);
+}
+
+static gchar *
+gbp_gobject_property_get_gtype (GbpGobjectProperty *self)
+{
+  g_autofree gchar *mangled = NULL;
+  g_autofree gchar *nsupper = NULL;
+  g_autofree gchar *clsupper = NULL;
+  g_auto(GStrv) parts = NULL;
+
+  g_assert (GBP_IS_GOBJECT_PROPERTY (self));
+
+  if (self->ctype == NULL)
+    return NULL;
+
+  mangled = mangle_name (self->ctype);
+
+  if (mangled == NULL || *mangled == '\0')
+    return NULL;
+
+  parts = g_strsplit (mangled, "_", 2);
+
+  if (!parts[0] || !parts[1])
+    return NULL;
+
+  nsupper = g_utf8_strup (parts[0], -1);
+  clsupper = g_utf8_strup (parts[1], -1);
+
+  return g_strdup_printf ("%s_TYPE_%s", nsupper, clsupper);
+}
+
+static void
+gbp_gobject_property_finalize (GObject *object)
+{
+  GbpGobjectProperty *self = (GbpGobjectProperty *)object;
+
+  g_clear_pointer (&self->ctype, g_free);
+  g_clear_pointer (&self->name, g_free);
+  g_clear_pointer (&self->default_, g_free);
+  g_clear_pointer (&self->minimum, g_free);
+  g_clear_pointer (&self->maximum, g_free);
+
+  G_OBJECT_CLASS (gbp_gobject_property_parent_class)->finalize (object);
+}
+
+static void
+gbp_gobject_property_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  GbpGobjectProperty *self = GBP_GOBJECT_PROPERTY (object);
+
+  switch (prop_id)
+    {
+    case PROP_CNAME:
+      g_value_take_string (value, g_strdelimit (g_strdup (self->name ?: ""), "-", '_'));
+      break;
+
+    case PROP_CTYPE:
+      g_value_set_string (value, self->ctype ?: "");
+      break;
+
+    case PROP_CONSTRUCT_ONLY:
+      g_value_set_boolean (value, self->construct_only);
+      break;
+
+    case PROP_GTYPE:
+      g_value_take_string (value, gbp_gobject_property_get_gtype (self));
+      break;
+
+    case PROP_NAME:
+      g_value_set_string (value, self->name);
+      break;
+
+    case PROP_DEFAULT:
+      g_value_set_string (value, self->default_);
+      break;
+
+    case PROP_KIND:
+      g_value_set_enum (value, self->kind);
+      break;
+
+    case PROP_MINIMUM:
+      g_value_set_string (value, self->minimum);
+      break;
+
+    case PROP_MAXIMUM:
+      g_value_set_string (value, self->maximum);
+      break;
+
+    case PROP_READABLE:
+      g_value_set_boolean (value, self->readable);
+      break;
+
+    case PROP_WRITABLE:
+      g_value_set_boolean (value, self->writable);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_property_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  GbpGobjectProperty *self = GBP_GOBJECT_PROPERTY (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONSTRUCT_ONLY:
+      self->construct_only = g_value_get_boolean (value);
+      break;
+
+    case PROP_CTYPE:
+      gbp_gobject_property_set_ctype (self, g_value_get_string (value));
+      break;
+
+    case PROP_NAME:
+      gbp_gobject_property_set_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_DEFAULT:
+      gbp_gobject_property_set_default (self, g_value_get_string (value));
+      break;
+
+    case PROP_KIND:
+      gbp_gobject_property_set_kind (self, g_value_get_enum (value));
+      break;
+
+    case PROP_MINIMUM:
+      gbp_gobject_property_set_minimum (self, g_value_get_string (value));
+      break;
+
+    case PROP_MAXIMUM:
+      gbp_gobject_property_set_maximum (self, g_value_get_string (value));
+      break;
+
+    case PROP_READABLE:
+      gbp_gobject_property_set_readable (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_WRITABLE:
+      gbp_gobject_property_set_writable (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_property_class_init (GbpGobjectPropertyClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_gobject_property_finalize;
+  object_class->get_property = gbp_gobject_property_get_property;
+  object_class->set_property = gbp_gobject_property_set_property;
+
+  properties [PROP_CNAME] =
+    g_param_spec_string ("cname",
+                         NULL,
+                         NULL,
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_CTYPE] =
+    g_param_spec_string ("ctype",
+                         NULL,
+                         NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_GTYPE] =
+    g_param_spec_string ("gtype",
+                         NULL,
+                         NULL,
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_KIND] =
+    g_param_spec_enum ("kind",
+                       NULL,
+                       NULL,
+                       GBP_TYPE_GOBJECT_PROPERTY_KIND,
+                       GBP_GOBJECT_PROPERTY_STRING,
+                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_CONSTRUCT_ONLY] =
+    g_param_spec_boolean ("construct-only",
+                          NULL,
+                          NULL,
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         NULL,
+                         NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DEFAULT] =
+    g_param_spec_string ("default",
+                         NULL,
+                         NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_MINIMUM] =
+    g_param_spec_string ("minimum",
+                         NULL,
+                         NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_MAXIMUM] =
+    g_param_spec_string ("maximum",
+                         NULL,
+                         NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_READABLE] =
+    g_param_spec_boolean ("readable",
+                          NULL,
+                          NULL,
+                          TRUE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_WRITABLE] =
+    g_param_spec_boolean ("writable",
+                          NULL,
+                          NULL,
+                          TRUE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_gobject_property_init (GbpGobjectProperty *self)
+{
+  self->readable = TRUE;
+  self->writable = TRUE;
+  self->kind = GBP_GOBJECT_PROPERTY_STRING;
+  self->default_ = g_strdup ("NULL");
+}
+
+GbpGobjectProperty *
+gbp_gobject_property_new (void)
+{
+  return g_object_new (GBP_TYPE_GOBJECT_PROPERTY, NULL);
+}
+
+GbpGobjectPropertyKind
+gbp_gobject_property_get_kind (GbpGobjectProperty     *self)
+{
+  return self->kind;
+}
+
+void
+gbp_gobject_property_set_kind (GbpGobjectProperty     *self,
+                               GbpGobjectPropertyKind  kind)
+{
+  if (kind != self->kind)
+    {
+      self->kind = kind;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_KIND]);
+    }
+}
+
+const gchar *
+gbp_gobject_property_get_name (GbpGobjectProperty *self)
+{
+  return self->name;
+}
+
+void
+gbp_gobject_property_set_name (GbpGobjectProperty *self,
+                               const gchar        *name)
+{
+  if (g_strcmp0 (name, self->name) != 0)
+    {
+      g_free (self->name);
+      self->name = g_strdup (name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+    }
+}
+
+const gchar *
+gbp_gobject_property_get_default (GbpGobjectProperty *self)
+{
+  return self->default_;
+}
+
+void
+gbp_gobject_property_set_default (GbpGobjectProperty *self,
+                                  const gchar        *default_)
+{
+  if (g_strcmp0 (default_, self->default_) != 0)
+    {
+      g_free (self->default_);
+      self->default_ = g_strdup (default_);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEFAULT]);
+    }
+}
+
+const gchar *
+gbp_gobject_property_get_minimum (GbpGobjectProperty *self)
+{
+  return self->minimum;
+}
+
+void
+gbp_gobject_property_set_minimum (GbpGobjectProperty *self,
+                                  const gchar        *minimum)
+{
+  if (g_strcmp0 (minimum, self->minimum) != 0)
+    {
+      g_free (self->minimum);
+      self->minimum = g_strdup (minimum);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MINIMUM]);
+    }
+}
+
+const gchar *
+gbp_gobject_property_get_maximum (GbpGobjectProperty *self)
+{
+  return self->maximum;
+}
+
+void
+gbp_gobject_property_set_maximum (GbpGobjectProperty *self,
+                                  const gchar        *maximum)
+{
+  if (g_strcmp0 (maximum, self->maximum) != 0)
+    {
+      g_free (self->maximum);
+      self->maximum = g_strdup (maximum);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MAXIMUM]);
+    }
+}
+
+gboolean
+gbp_gobject_property_get_readable (GbpGobjectProperty *self)
+{
+  return self->readable;
+}
+
+void
+gbp_gobject_property_set_readable (GbpGobjectProperty *self,
+                                   gboolean            readable)
+{
+  readable = !!readable;
+
+  if (self->readable != readable)
+    {
+      self->readable = readable;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READABLE]);
+    }
+}
+
+gboolean
+gbp_gobject_property_get_writable (GbpGobjectProperty *self)
+{
+  return self->writable;
+}
+
+void
+gbp_gobject_property_set_writable (GbpGobjectProperty *self,
+                                   gboolean            writable)
+{
+  writable = !!writable;
+
+  if (self->writable != writable)
+    {
+      self->writable = writable;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_WRITABLE]);
+    }
+}
+
+GType
+gbp_gobject_property_kind_get_type (void)
+{
+  static GType type_id = 0;
+
+  if (g_once_init_enter (&type_id))
+    {
+      static const GEnumValue values[] = {
+        { GBP_GOBJECT_PROPERTY_BOOLEAN, "GBP_GOBJECT_PROPERTY_BOOLEAN", "boolean" },
+        { GBP_GOBJECT_PROPERTY_BOXED, "GBP_GOBJECT_PROPERTY_BOXED", "boxed" },
+        { GBP_GOBJECT_PROPERTY_CHAR, "GBP_GOBJECT_PROPERTY_CHAR", "char" },
+        { GBP_GOBJECT_PROPERTY_DOUBLE, "GBP_GOBJECT_PROPERTY_DOUBLE", "double" },
+        { GBP_GOBJECT_PROPERTY_ENUM, "GBP_GOBJECT_PROPERTY_ENUM", "enum" },
+        { GBP_GOBJECT_PROPERTY_FLAGS, "GBP_GOBJECT_PROPERTY_FLAGS", "flags" },
+        { GBP_GOBJECT_PROPERTY_FLOAT, "GBP_GOBJECT_PROPERTY_FLOAT", "float" },
+        { GBP_GOBJECT_PROPERTY_INT, "GBP_GOBJECT_PROPERTY_INT", "int" },
+        { GBP_GOBJECT_PROPERTY_INT64, "GBP_GOBJECT_PROPERTY_INT64", "int64" },
+        { GBP_GOBJECT_PROPERTY_LONG, "GBP_GOBJECT_PROPERTY_LONG", "long" },
+        { GBP_GOBJECT_PROPERTY_OBJECT, "GBP_GOBJECT_PROPERTY_OBJECT", "object" },
+        { GBP_GOBJECT_PROPERTY_POINTER, "GBP_GOBJECT_PROPERTY_POINTER", "pointer" },
+        { GBP_GOBJECT_PROPERTY_STRING, "GBP_GOBJECT_PROPERTY_STRING", "string" },
+        { GBP_GOBJECT_PROPERTY_UINT, "GBP_GOBJECT_PROPERTY_UINT", "uint" },
+        { GBP_GOBJECT_PROPERTY_UINT64, "GBP_GOBJECT_PROPERTY_UINT64", "uint64" },
+        { GBP_GOBJECT_PROPERTY_ULONG, "GBP_GOBJECT_PROPERTY_ULONG", "ulong" },
+        { GBP_GOBJECT_PROPERTY_UNICHAR, "GBP_GOBJECT_PROPERTY_UNICHAR", "unichar" },
+        { GBP_GOBJECT_PROPERTY_VARIANT, "GBP_GOBJECT_PROPERTY_VARIANT", "variant" },
+        { 0 }
+      };
+      GType _type_id = g_enum_register_static ("GbpGobjectPropertyKind", values);
+      g_once_init_leave (&type_id, _type_id);
+    }
+
+  return type_id;
+}
diff --git a/plugins/gobject-templates/gbp-gobject-property.h 
b/plugins/gobject-templates/gbp-gobject-property.h
new file mode 100644
index 0000000..79cc8d9
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-property.h
@@ -0,0 +1,79 @@
+/* gbp-gobject-property.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_PROPERTY_H
+#define GBP_GOBJECT_PROPERTY_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  GBP_GOBJECT_PROPERTY_BOOLEAN,
+  GBP_GOBJECT_PROPERTY_BOXED,
+  GBP_GOBJECT_PROPERTY_CHAR,
+  GBP_GOBJECT_PROPERTY_DOUBLE,
+  GBP_GOBJECT_PROPERTY_ENUM,
+  GBP_GOBJECT_PROPERTY_FLAGS,
+  GBP_GOBJECT_PROPERTY_FLOAT,
+  GBP_GOBJECT_PROPERTY_INT,
+  GBP_GOBJECT_PROPERTY_INT64,
+  GBP_GOBJECT_PROPERTY_LONG,
+  GBP_GOBJECT_PROPERTY_OBJECT,
+  GBP_GOBJECT_PROPERTY_POINTER,
+  GBP_GOBJECT_PROPERTY_STRING,
+  GBP_GOBJECT_PROPERTY_UINT,
+  GBP_GOBJECT_PROPERTY_UINT64,
+  GBP_GOBJECT_PROPERTY_ULONG,
+  GBP_GOBJECT_PROPERTY_UNICHAR,
+  GBP_GOBJECT_PROPERTY_VARIANT,
+} GbpGobjectPropertyKind;
+
+#define GBP_TYPE_GOBJECT_PROPERTY (gbp_gobject_property_get_type())
+#define GBP_TYPE_GOBJECT_PROPERTY_KIND (gbp_gobject_property_kind_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectProperty, gbp_gobject_property, GBP, GOBJECT_PROPERTY, GObject)
+
+GType                   gbp_gobject_property_kind_get_type (void);
+GbpGobjectProperty     *gbp_gobject_property_new           (void);
+GbpGobjectPropertyKind  gbp_gobject_property_get_kind      (GbpGobjectProperty     *self);
+void                    gbp_gobject_property_set_kind      (GbpGobjectProperty     *self,
+                                                            GbpGobjectPropertyKind  kind);
+const gchar            *gbp_gobject_property_get_name      (GbpGobjectProperty     *self);
+void                    gbp_gobject_property_set_name      (GbpGobjectProperty     *self,
+                                                            const gchar            *name);
+const gchar            *gbp_gobject_property_get_default   (GbpGobjectProperty     *self);
+void                    gbp_gobject_property_set_default   (GbpGobjectProperty     *self,
+                                                            const gchar            *default_);
+const gchar            *gbp_gobject_property_get_minimum   (GbpGobjectProperty     *self);
+void                    gbp_gobject_property_set_minimum   (GbpGobjectProperty     *self,
+                                                            const gchar            *minimum);
+const gchar            *gbp_gobject_property_get_maximum   (GbpGobjectProperty     *self);
+void                    gbp_gobject_property_set_maximum   (GbpGobjectProperty     *self,
+                                                            const gchar            *maximum);
+gboolean                gbp_gobject_property_get_readable  (GbpGobjectProperty     *self);
+void                    gbp_gobject_property_set_readable  (GbpGobjectProperty     *self,
+                                                            gboolean                readable);
+gboolean                gbp_gobject_property_get_writable  (GbpGobjectProperty     *self);
+void                    gbp_gobject_property_set_writable  (GbpGobjectProperty     *self,
+                                                            gboolean                writable);
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_PROPERTY_H */
diff --git a/plugins/gobject-templates/gbp-gobject-signal-editor.c 
b/plugins/gobject-templates/gbp-gobject-signal-editor.c
new file mode 100644
index 0000000..c83904d
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-signal-editor.c
@@ -0,0 +1,137 @@
+/* gbp-gobject-signal-editor.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-signal-editor"
+
+#include <glib/gi18n.h>
+
+#include "gbp-gobject-signal-editor.h"
+
+struct _GbpGobjectSignalEditor
+{
+  GtkBin parent_instance;
+
+  GbpGobjectSignal *signal;
+};
+
+enum {
+  PROP_0,
+  PROP_SIGNAL,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (GbpGobjectSignalEditor, gbp_gobject_signal_editor, GTK_TYPE_BIN)
+
+static GParamSpec *properties [N_PROPS];
+
+GbpGobjectSignal *
+gbp_gobject_signal_editor_get_signal (GbpGobjectSignalEditor *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SIGNAL_EDITOR (self), NULL);
+
+  return self->signal;
+}
+
+void
+gbp_gobject_signal_editor_set_signal (GbpGobjectSignalEditor *self,
+                                      GbpGobjectSignal       *signal)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_SIGNAL_EDITOR (self));
+  g_return_if_fail (!signal || GBP_IS_GOBJECT_SIGNAL (signal));
+
+  if (g_set_object (&self->signal, signal))
+    {
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SIGNAL]);
+    }
+}
+
+static void
+gbp_gobject_signal_editor_finalize (GObject *object)
+{
+  GbpGobjectSignalEditor *self = (GbpGobjectSignalEditor *)object;
+
+  g_clear_object (&self->signal);
+
+  G_OBJECT_CLASS (gbp_gobject_signal_editor_parent_class)->finalize (object);
+}
+
+static void
+gbp_gobject_signal_editor_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+  GbpGobjectSignalEditor *self = GBP_GOBJECT_SIGNAL_EDITOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_SIGNAL:
+      g_value_set_object (value, self->signal);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_signal_editor_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+  GbpGobjectSignalEditor *self = GBP_GOBJECT_SIGNAL_EDITOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_SIGNAL:
+      gbp_gobject_signal_editor_set_signal (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_signal_editor_class_init (GbpGobjectSignalEditorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gbp_gobject_signal_editor_finalize;
+  object_class->get_property = gbp_gobject_signal_editor_get_property;
+  object_class->set_property = gbp_gobject_signal_editor_set_property;
+
+  properties [PROP_SIGNAL] =
+    g_param_spec_object ("signal",
+                         "Signal",
+                         "The signal to be edited",
+                         GBP_TYPE_GOBJECT_SIGNAL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/gobject-templates/gbp-gobject-signal-editor.ui");
+}
+
+static void
+gbp_gobject_signal_editor_init (GbpGobjectSignalEditor *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/plugins/gobject-templates/gbp-gobject-signal-editor.h 
b/plugins/gobject-templates/gbp-gobject-signal-editor.h
new file mode 100644
index 0000000..7c96543
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-signal-editor.h
@@ -0,0 +1,39 @@
+/* gbp-gobject-signal-editor.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_SIGNAL_EDITOR_H
+#define GBP_GOBJECT_SIGNAL_EDITOR_H
+
+#include <gtk/gtk.h>
+
+#include "gbp-gobject-signal.h"
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GOBJECT_SIGNAL_EDITOR (gbp_gobject_signal_editor_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectSignalEditor, gbp_gobject_signal_editor, GBP, GOBJECT_SIGNAL_EDITOR, GtkBin)
+
+GtkWidget        *gbp_gobject_signal_editor_new        (void);
+GbpGobjectSignal *gbp_gobject_signal_editor_get_signal (GbpGobjectSignalEditor *self);
+void              gbp_gobject_signal_editor_set_signal (GbpGobjectSignalEditor *self,
+                                                        GbpGobjectSignal       *signal);
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_SIGNAL_EDITOR_H */
diff --git a/plugins/gobject-templates/gbp-gobject-signal-editor.ui 
b/plugins/gobject-templates/gbp-gobject-signal-editor.ui
new file mode 100644
index 0000000..b8c442d
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-signal-editor.ui
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpGobjectSignalEditor" parent="GtkBin">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="border-width">24</property>
+        <property name="visible">true</property>
+
+        <!-- Name -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label1">
+                <property name="label" translatable="yes">Name</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkEntry" id="name_entry">
+                <property name="width-chars">25</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Name Description Text -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="margin-top">6</property>
+            <property name="visible">true</property>
+            <child type="center">
+              <object class="GtkLabel" id="name_help_label">
+                <property name="label" translatable="yes">Unique name that should only contain alpha 
characters or - such as “activate” or “items-changed”.</property>
+                <property name="wrap">true</property>
+                <property name="visible">true</property>
+                <property name="xalign">0.0</property>
+                <property name="max-width-chars">25</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+                <attributes>
+                  <attribute name="scale" value="0.833333"/>
+                </attributes>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Returns -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="margin-top">24</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label2">
+                <property name="label" translatable="yes">Returns</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkEntry" id="returns_entry">
+                <property name="width-chars">25</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+      </object>
+    </child>
+  </template>
+
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="name_entry"/>
+      <widget name="name_help_label"/>
+      <widget name="returns_entry"/>
+    </widgets>
+  </object>
+
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="label1"/>
+      <widget name="label2"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/plugins/gobject-templates/gbp-gobject-signal.c b/plugins/gobject-templates/gbp-gobject-signal.c
new file mode 100644
index 0000000..a5eb99c
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-signal.c
@@ -0,0 +1,111 @@
+/* gbp-gobject-signal.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-signal"
+
+#include "gbp-gobject-signal.h"
+
+struct _GbpGobjectSignal
+{
+  GObject parent_instance;
+
+  gchar *name;
+};
+
+G_DEFINE_TYPE (GbpGobjectSignal, gbp_gobject_signal, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_NAME,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_gobject_signal_finalize (GObject *object)
+{
+  GbpGobjectSignal *self = (GbpGobjectSignal *)object;
+
+  g_clear_pointer (&self->name, g_free);
+
+  G_OBJECT_CLASS (gbp_gobject_signal_parent_class)->finalize (object);
+}
+
+static void
+gbp_gobject_signal_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  GbpGobjectSignal *self = GBP_GOBJECT_SIGNAL (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, self->name);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_signal_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  GbpGobjectSignal *self = GBP_GOBJECT_SIGNAL (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_free (self->name);
+      self->name = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_signal_class_init (GbpGobjectSignalClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_gobject_signal_finalize;
+  object_class->get_property = gbp_gobject_signal_get_property;
+  object_class->set_property = gbp_gobject_signal_set_property;
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_gobject_signal_init (GbpGobjectSignal *self)
+{
+}
diff --git a/plugins/gobject-templates/gbp-gobject-signal.h b/plugins/gobject-templates/gbp-gobject-signal.h
new file mode 100644
index 0000000..1217d45
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-signal.h
@@ -0,0 +1,33 @@
+/* gbp-gobject-signal.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_SIGNAL_H
+#define GBP_GOBJECT_SIGNAL_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GOBJECT_SIGNAL (gbp_gobject_signal_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectSignal, gbp_gobject_signal, GBP, GOBJECT_SIGNAL, GObject)
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_SIGNAL_H */
+
diff --git a/plugins/gobject-templates/gbp-gobject-spec-editor.c 
b/plugins/gobject-templates/gbp-gobject-spec-editor.c
new file mode 100644
index 0000000..5d47025
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-spec-editor.c
@@ -0,0 +1,212 @@
+/* gbp-gobject-spec-editor.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-spec-editor"
+
+#include <egg-binding-group.h>
+#include <egg-file-chooser-entry.h>
+#include <egg-state-machine.h>
+
+#include "gbp-gobject-spec-editor.h"
+
+struct _GbpGobjectSpecEditor
+{
+  GtkBin               parent_instance;
+
+  GbpGobjectSpec      *spec;
+  EggBindingGroup     *spec_bindings;
+
+  GtkSwitch           *derive_switch;
+  GtkEntry            *class_entry;
+  EggStateMachine     *language_state;
+  EggFileChooserEntry *location_entry;
+  GtkEntry            *name_entry;
+  GtkEntry            *namespace_entry;
+  GtkEntry            *parent_entry;
+};
+
+enum {
+  PROP_0,
+  PROP_SPEC,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (GbpGobjectSpecEditor, gbp_gobject_spec_editor, GTK_TYPE_BIN)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_gobject_spec_editor_finalize (GObject *object)
+{
+  GbpGobjectSpecEditor *self = (GbpGobjectSpecEditor *)object;
+
+  g_clear_object (&self->spec_bindings);
+  g_clear_object (&self->spec);
+
+  G_OBJECT_CLASS (gbp_gobject_spec_editor_parent_class)->finalize (object);
+}
+
+static void
+gbp_gobject_spec_editor_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  GbpGobjectSpecEditor *self = GBP_GOBJECT_SPEC_EDITOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_SPEC:
+      g_value_set_object (value, gbp_gobject_spec_editor_get_spec (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_spec_editor_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  GbpGobjectSpecEditor *self = GBP_GOBJECT_SPEC_EDITOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_SPEC:
+      gbp_gobject_spec_editor_set_spec (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_spec_editor_class_init (GbpGobjectSpecEditorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gbp_gobject_spec_editor_finalize;
+  object_class->get_property = gbp_gobject_spec_editor_get_property;
+  object_class->set_property = gbp_gobject_spec_editor_set_property;
+
+  properties [PROP_SPEC] =
+    g_param_spec_object ("spec",
+                         "Spec",
+                         "The gobject specification",
+                         GBP_TYPE_GOBJECT_SPEC,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/gobject-templates/gbp-gobject-spec-editor.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectSpecEditor, class_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectSpecEditor, derive_switch);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectSpecEditor, language_state);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectSpecEditor, location_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectSpecEditor, name_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectSpecEditor, namespace_entry);
+  gtk_widget_class_bind_template_child (widget_class, GbpGobjectSpecEditor, parent_entry);
+}
+
+static void
+gbp_gobject_spec_editor_init (GbpGobjectSpecEditor *self)
+{
+  g_autoptr(GSimpleActionGroup) group = NULL;
+  g_autoptr(GAction) language_state_action = NULL;
+
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  group = g_simple_action_group_new ();
+  language_state_action = egg_state_machine_create_action (self->language_state, "language");
+  g_action_map_add_action (G_ACTION_MAP (group), language_state_action);
+
+  gtk_widget_insert_action_group (GTK_WIDGET (self), "spec", G_ACTION_GROUP (group));
+
+  self->spec_bindings = egg_binding_group_new ();
+
+  egg_binding_group_bind (self->spec_bindings, "name",
+                          self->name_entry, "text",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+  egg_binding_group_bind (self->spec_bindings, "class-name",
+                          self->class_entry, "text",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+  egg_binding_group_bind (self->spec_bindings, "namespace",
+                          self->namespace_entry, "text",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+  egg_binding_group_bind (self->spec_bindings, "final",
+                          self->derive_switch, "active",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL | G_BINDING_INVERT_BOOLEAN);
+}
+
+/**
+ * gbp_gobject_spec_editor_get_spec:
+ *
+ * Returns: (transfer none): A #GbpGobjectSpecEditor.
+ */
+GbpGobjectSpec *
+gbp_gobject_spec_editor_get_spec (GbpGobjectSpecEditor *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC_EDITOR (self), NULL);
+
+  return self->spec;
+}
+
+void
+gbp_gobject_spec_editor_set_spec (GbpGobjectSpecEditor *self,
+                                  GbpGobjectSpec       *spec)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_SPEC_EDITOR (self));
+  g_return_if_fail (!spec || GBP_IS_GOBJECT_SPEC (spec));
+
+  if (g_set_object (&self->spec, spec))
+    {
+      egg_binding_group_set_source (self->spec_bindings, spec);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SPEC]);
+    }
+}
+
+void
+gbp_gobject_spec_editor_set_directory (GbpGobjectSpecEditor *self,
+                                       GFile                *directory)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_SPEC_EDITOR (self));
+  g_return_if_fail (!directory || G_IS_FILE (directory));
+
+  egg_file_chooser_entry_set_file (self->location_entry, directory);
+}
+
+/**
+ * gbp_gobject_spec_editor_get_directory:
+ *
+ * Returns: (transfer full) (nullable): A #GFile or %NULL.
+ */
+GFile *
+gbp_gobject_spec_editor_get_directory (GbpGobjectSpecEditor *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC_EDITOR (self), NULL);
+
+  return egg_file_chooser_entry_get_file (self->location_entry);
+}
diff --git a/plugins/gobject-templates/gbp-gobject-spec-editor.h 
b/plugins/gobject-templates/gbp-gobject-spec-editor.h
new file mode 100644
index 0000000..079d146
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-spec-editor.h
@@ -0,0 +1,42 @@
+/* gbp-gobject-spec-editor.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_SPEC_EDITOR_H
+#define GBP_GOBJECT_SPEC_EDITOR_H
+
+#include <gtk/gtk.h>
+
+#include "gbp-gobject-spec.h"
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GOBJECT_SPEC_EDITOR (gbp_gobject_spec_editor_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectSpecEditor, gbp_gobject_spec_editor, GBP, GOBJECT_SPEC_EDITOR, GtkBin)
+
+GtkWidget      *gbp_gobject_spec_editor_new           (GbpGobjectSpecEditor *self);
+GbpGobjectSpec *gbp_gobject_spec_editor_get_spec      (GbpGobjectSpecEditor *self);
+void            gbp_gobject_spec_editor_set_spec      (GbpGobjectSpecEditor *self,
+                                                       GbpGobjectSpec       *spec);
+GFile          *gbp_gobject_spec_editor_get_directory (GbpGobjectSpecEditor *self);
+void            gbp_gobject_spec_editor_set_directory (GbpGobjectSpecEditor *self,
+                                                       GFile                *file);
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_SPEC_EDITOR_H */
diff --git a/plugins/gobject-templates/gbp-gobject-spec-editor.ui 
b/plugins/gobject-templates/gbp-gobject-spec-editor.ui
new file mode 100644
index 0000000..bb1d2fc
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-spec-editor.ui
@@ -0,0 +1,351 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpGobjectSpecEditor" parent="GtkBin">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="visible">true</property>
+
+        <!-- Location -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">10</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="label" translatable="yes">Location</property>
+                <property name="halign">end</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="EggFileChooserEntry" id="location_entry">
+                <property name="action">select-folder</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Name -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="margin-top">24</property>
+            <property name="spacing">10</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="label" translatable="yes">Name</property>
+                <property name="halign">end</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkEntry" id="name_entry">
+                <property name="sensitive" bind-source="details_revealer" bind-property="reveal-child" 
bind-flags="bidirectional|invert-boolean"/>
+                <property name="visible">true</property>
+                <property name="width-chars">40</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkToggleButton">
+                <property name="active" bind-source="details_revealer" bind-property="reveal-child" 
bind-flags="bidirectional">false</property>
+                <property name="focus-on-click">false</property>
+                <property name="halign">start</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="flat"/>
+                  <class name="image-button"/>
+                </style>
+                <child>
+                  <object class="GtkImage">
+                    <property name="icon-name">view-more-symbolic</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="pack-type">end</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+
+        <!-- Description of name entry -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">10</property>
+            <property name="visible">true</property>
+            <property name="margin-top">6</property>
+            <child type="center">
+              <object class="GtkLabel" id="name_desc_label">
+                <property name="label" translatable="yes">Unique name for the class including the namespace 
such as “MyObject” or “MyWidget”.</property>
+                <property name="max-width-chars">40</property>
+                <property name="visible">true</property>
+                <property name="xalign">0.0</property>
+                <property name="wrap">true</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+                <attributes>
+                  <attribute name="scale" value="0.833333"/>
+                </attributes>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Revealer for manual namespace/class entry -->
+        <child>
+          <object class="GtkRevealer" id="details_revealer">
+            <property name="reveal-child">false</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <property name="visible">true</property>
+
+                <!-- Namespace -->
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">horizontal</property>
+                    <property name="spacing">10</property>
+                    <property name="visible">true</property>
+                    <property name="margin-top">24</property>
+                    <child>
+                      <object class="GtkLabel" id="namespace_label">
+                        <property name="label" translatable="yes">Namespace</property>
+                        <property name="halign">end</property>
+                        <property name="hexpand">true</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                    <child type="center">
+                      <object class="GtkEntry" id="namespace_entry">
+                        <property name="visible">true</property>
+                        <property name="width-chars">40</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+
+                <!-- Class -->
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">horizontal</property>
+                    <property name="spacing">10</property>
+                    <property name="visible">true</property>
+                    <property name="margin-top">12</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="label" translatable="yes">Class</property>
+                        <property name="halign">end</property>
+                        <property name="hexpand">true</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                    <child type="center">
+                      <object class="GtkEntry" id="class_entry">
+                        <property name="visible">true</property>
+                        <property name="width-chars">40</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+
+                <!-- Parent Type -->
+                <child>
+                  <object class="GtkBox">
+                    <property name="orientation">horizontal</property>
+                    <property name="spacing">10</property>
+                    <property name="visible">true</property>
+                    <property name="margin-top">12</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="label" translatable="yes">Inherits</property>
+                        <property name="halign">end</property>
+                        <property name="hexpand">true</property>
+                        <property name="visible">true</property>
+                      </object>
+                    </child>
+                    <child type="center">
+                      <object class="GtkEntry" id="parent_entry">
+                        <property name="text">GObject</property>
+                        <property name="visible">true</property>
+                        <property name="width-chars">40</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Language -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">10</property>
+            <property name="visible">true</property>
+            <property name="margin-top">24</property>
+            <child>
+              <object class="GtkLabel">
+                <property name="label" translatable="yes">Language</property>
+                <property name="halign">end</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkBox" id="language_box">
+                <property name="orientation">horizontal</property>
+                <property name="homogeneous">true</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="linked"/>
+                </style>
+                <child>
+                  <object class="GtkRadioButton" id="lang_c">
+                    <property name="draw-indicator">false</property>
+                    <property name="group">lang_c</property>
+                    <property name="action-name">spec.language</property>
+                    <property name="action-target">'c'</property>
+                    <property name="hexpand">true</property>
+                    <property name="label" translatable="yes">C</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkRadioButton" id="lang_cpp">
+                    <property name="draw-indicator">false</property>
+                    <property name="group">lang_c</property>
+                    <property name="action-name">spec.language</property>
+                    <property name="action-target">'cpp'</property>
+                    <property name="hexpand">true</property>
+                    <property name="label" translatable="yes">C++</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkRadioButton" id="lang_python">
+                    <property name="draw-indicator">false</property>
+                    <property name="group">lang_c</property>
+                    <property name="action-name">spec.language</property>
+                    <property name="action-target">'python'</property>
+                    <property name="hexpand">true</property>
+                    <property name="label" translatable="yes">Python</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkRadioButton" id="lang_vala">
+                    <property name="draw-indicator">false</property>
+                    <property name="group">lang_c</property>
+                    <property name="action-name">spec.language</property>
+                    <property name="action-target">'vala'</property>
+                    <property name="hexpand">true</property>
+                    <property name="label" translatable="yes">Vala</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+        <!-- Write -->
+        <child>
+          <object class="GtkBox">
+            <property name="orientation">horizontal</property>
+            <property name="spacing">12</property>
+            <property name="visible">true</property>
+            <property name="margin-top">24</property>
+            <child>
+              <object class="GtkLabel" id="derive_label">
+                <property name="label" translatable="yes">Derivable</property>
+                <property name="xalign">1.0</property>
+                <property name="hexpand">true</property>
+                <property name="visible">true</property>
+              </object>
+            </child>
+            <child type="center">
+              <object class="GtkBox" id="derive_container">
+                <property name="visible">true</property>
+                <child>
+                  <object class="GtkSwitch" id="derive_switch">
+                    <property name="active">false</property>
+                    <property name="halign">start</property>
+                    <property name="valign">center</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+
+      </object>
+    </child>
+  </template>
+
+  <!-- Sizing to keep the center column all the same -->
+  <object class="GtkSizeGroup">
+    <property name="mode">horizontal</property>
+    <widgets>
+      <widget name="language_box"/>
+      <widget name="location_entry"/>
+      <widget name="namespace_entry"/>
+      <widget name="name_desc_label"/>
+      <widget name="parent_entry"/>
+      <widget name="class_entry"/>
+      <widget name="derive_container"/>
+    </widgets>
+  </object>
+
+  <object class="EggStateMachine" id="language_state">
+    <property name="state">c</property>
+    <states>
+      <state name="c">
+        <object id="namespace_label">
+          <property name="label" translatable="yes">Namespace</property>
+        </object>
+        <object id="derive_switch">
+          <property name="sensitive">true</property>
+        </object>
+      </state>
+      <state name="cpp">
+        <object id="namespace_label">
+          <property name="label" translatable="yes">Namespace</property>
+        </object>
+        <object id="derive_switch">
+          <property name="sensitive">false</property>
+        </object>
+      </state>
+      <state name="python">
+        <object id="namespace_label">
+          <property name="label" translatable="yes">Package</property>
+        </object>
+        <object id="derive_switch">
+          <property name="sensitive">false</property>
+        </object>
+      </state>
+      <state name="vala">
+        <object id="namespace_label">
+          <property name="label" translatable="yes">Namespace</property>
+        </object>
+        <object id="derive_switch">
+          <property name="sensitive">false</property>
+        </object>
+      </state>
+    </states>
+  </object>
+
+</interface>
diff --git a/plugins/gobject-templates/gbp-gobject-spec.c b/plugins/gobject-templates/gbp-gobject-spec.c
new file mode 100644
index 0000000..1af559d
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-spec.c
@@ -0,0 +1,512 @@
+/* gbp-gobject-spec.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-spec"
+
+#include <string.h>
+
+#include "gbp-gobject-spec.h"
+
+struct _GbpGobjectSpec
+{
+  GObject     parent_instance;
+
+  gchar      *class_name;
+  gchar      *name;
+  gchar      *namespace;
+  gchar      *parent_name;
+
+  GListStore *properties;
+  GListStore *signals;
+
+  guint       final : 1;
+};
+
+G_DEFINE_TYPE (GbpGobjectSpec, gbp_gobject_spec, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_CLASS_NAME,
+  PROP_FINAL,
+  PROP_NAME,
+  PROP_NAMESPACE,
+  PROP_PARENT_NAME,
+  PROP_PROPERTIES,
+  PROP_READY,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_gobject_spec_rebuild (GbpGobjectSpec *self)
+{
+  g_assert (GBP_IS_GOBJECT_SPEC (self));
+
+  g_free (self->name);
+  self->name = g_strdup_printf ("%s%s",
+                                self->namespace ?: "",
+                                self->class_name ?: "");
+}
+
+static void
+gbp_gobject_spec_set_class_name (GbpGobjectSpec *self,
+                                 const gchar    *class_name)
+{
+  g_assert (GBP_IS_GOBJECT_SPEC (self));
+
+  g_free (self->class_name);
+  self->class_name = g_strdup (class_name);
+
+  gbp_gobject_spec_rebuild (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLASS_NAME]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READY]);
+}
+
+static gchar *
+mangle_name (const gchar *name)
+{
+  GString *symbol_name = g_string_new ("");
+  gint i;
+
+  /* copied from gtkbuilder.c */
+
+  for (i = 0; name[i] != '\0'; i++)
+    {
+      /* skip if uppercase, first or previous is uppercase */
+      if ((name[i] == g_ascii_toupper (name[i]) &&
+           i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) ||
+          (i > 2 && name[i]   == g_ascii_toupper (name[i]) &&
+           name[i-1] == g_ascii_toupper (name[i-1]) &&
+           name[i-2] == g_ascii_toupper (name[i-2])))
+        g_string_append_c (symbol_name, '_');
+      g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
+    }
+
+  return g_string_free (symbol_name, FALSE);
+}
+
+static gboolean
+parse_name (const gchar  *name,
+            gchar       **namespace,
+            gchar       **class_name)
+{
+  g_autofree gchar *mangled = NULL;
+  g_auto(GStrv) parts = NULL;
+
+  g_assert (namespace != NULL);
+  g_assert (class_name != NULL);
+
+  *namespace = NULL;
+  *class_name = NULL;
+
+  if (name == NULL)
+    return FALSE;
+
+  mangled = mangle_name (name);
+  parts = g_strsplit (mangled, "_", 2);
+
+  if (parts == NULL || parts[0] == NULL)
+    return FALSE;
+
+  *namespace = g_strndup (name, strlen (parts[0]));
+
+  if (parts[1])
+    *class_name = g_strndup (name + strlen (parts[0]), strlen (parts[1]));
+  else
+    *class_name = g_strdup ("");
+
+  return TRUE;
+}
+
+static void
+gbp_gobject_spec_set_name (GbpGobjectSpec *self,
+                           const gchar    *name)
+{
+  g_autofree gchar *namespace = NULL;
+  g_autofree gchar *class_name = NULL;
+
+  g_assert (GBP_IS_GOBJECT_SPEC (self));
+
+  g_free (self->name);
+  self->name = g_strdup (name);
+
+  if (parse_name (name, &namespace, &class_name))
+    {
+      g_free (self->namespace);
+      g_free (self->class_name);
+
+      self->namespace = g_steal_pointer (&namespace);
+      self->class_name = g_steal_pointer (&class_name);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLASS_NAME]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAMESPACE]);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READY]);
+}
+
+static void
+gbp_gobject_spec_set_namespace (GbpGobjectSpec *self,
+                                const gchar    *namespace)
+{
+  g_assert (GBP_IS_GOBJECT_SPEC (self));
+
+  g_free (self->namespace);
+  self->namespace = g_strdup (namespace);
+
+  gbp_gobject_spec_rebuild (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAMESPACE]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READY]);
+}
+
+static void
+gbp_gobject_spec_set_parent_name (GbpGobjectSpec *self,
+                                  const gchar    *parent_name)
+{
+  g_assert (GBP_IS_GOBJECT_SPEC (self));
+
+  g_free (self->parent_name);
+  self->parent_name = g_strdup (parent_name);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PARENT_NAME]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READY]);
+}
+
+static void
+gbp_gobject_spec_finalize (GObject *object)
+{
+  GbpGobjectSpec *self = (GbpGobjectSpec *)object;
+
+  g_clear_object (&self->properties);
+  g_clear_object (&self->signals);
+  g_clear_pointer (&self->class_name, g_free);
+  g_clear_pointer (&self->name, g_free);
+  g_clear_pointer (&self->namespace, g_free);
+  g_clear_pointer (&self->parent_name, g_free);
+
+  G_OBJECT_CLASS (gbp_gobject_spec_parent_class)->finalize (object);
+}
+
+static void
+gbp_gobject_spec_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  GbpGobjectSpec *self = GBP_GOBJECT_SPEC (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLASS_NAME:
+      if (self->class_name)
+        g_value_set_string (value, self->class_name);
+      else
+        g_value_set_static_string (value, "");
+      break;
+
+    case PROP_FINAL:
+      g_value_set_boolean (value, self->final);
+      break;
+
+    case PROP_NAME:
+      if (self->name)
+        g_value_set_string (value, self->name);
+      else
+        g_value_set_static_string (value, "");
+      break;
+
+    case PROP_NAMESPACE:
+      if (self->namespace)
+        g_value_set_string (value, self->namespace);
+      else
+        g_value_set_static_string (value, "");
+      break;
+
+    case PROP_PARENT_NAME:
+      if (self->parent_name)
+        g_value_set_string (value, self->parent_name);
+      else
+        g_value_set_static_string (value, "GObject");
+      break;
+
+    case PROP_PROPERTIES:
+      g_value_set_object (value, self->properties);
+      break;
+
+    case PROP_READY:
+      g_value_set_boolean (value, gbp_gobject_spec_get_ready (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_spec_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  GbpGobjectSpec *self = GBP_GOBJECT_SPEC (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLASS_NAME:
+      gbp_gobject_spec_set_class_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_FINAL:
+      self->final = g_value_get_boolean (value);
+      break;
+
+    case PROP_NAME:
+      gbp_gobject_spec_set_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_NAMESPACE:
+      gbp_gobject_spec_set_namespace (self, g_value_get_string (value));
+      break;
+
+    case PROP_PARENT_NAME:
+      gbp_gobject_spec_set_parent_name (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_spec_class_init (GbpGobjectSpecClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_gobject_spec_finalize;
+  object_class->get_property = gbp_gobject_spec_get_property;
+  object_class->set_property = gbp_gobject_spec_set_property;
+
+  properties [PROP_CLASS_NAME] =
+    g_param_spec_string ("class-name",
+                         "Class Name",
+                         "Class Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FINAL] =
+    g_param_spec_boolean ("final",
+                          "Final",
+                          "Final",
+                          TRUE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAMESPACE] =
+    g_param_spec_string ("namespace",
+                         "Namespace",
+                         "Namespace",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PARENT_NAME] =
+    g_param_spec_string ("parent-name",
+                         "Parent Name",
+                         "Parent Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PROPERTIES] =
+    g_param_spec_object ("properties",
+                         "Properties",
+                         "Properties",
+                         G_TYPE_LIST_MODEL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_READY] =
+    g_param_spec_boolean ("ready",
+                          "Ready",
+                          "Ready",
+                          FALSE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_gobject_spec_init (GbpGobjectSpec *self)
+{
+  self->properties = g_list_store_new (GBP_TYPE_GOBJECT_PROPERTY);
+  self->signals = g_list_store_new (GBP_TYPE_GOBJECT_SIGNAL);
+  self->final = TRUE;
+  self->parent_name = g_strdup ("GObject");
+}
+
+GListModel *
+gbp_gobject_spec_get_properties (GbpGobjectSpec *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC (self), NULL);
+
+  return G_LIST_MODEL (self->properties);
+}
+
+void
+gbp_gobject_spec_add_property (GbpGobjectSpec     *self,
+                               GbpGobjectProperty *property)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_SPEC (self));
+  g_return_if_fail (GBP_IS_GOBJECT_PROPERTY (property));
+
+  g_list_store_append (self->properties, property);
+}
+
+void
+gbp_gobject_spec_remove_property (GbpGobjectSpec     *self,
+                                  GbpGobjectProperty *property)
+{
+  GListModel *model;
+  guint n_items;
+  guint i;
+
+  g_return_if_fail (GBP_IS_GOBJECT_SPEC (self));
+  g_return_if_fail (GBP_IS_GOBJECT_PROPERTY (property));
+
+  model = G_LIST_MODEL (self->properties);
+  n_items = g_list_model_get_n_items (model);
+
+  for (i = 0; i < n_items; i++)
+    {
+      g_autoptr(GbpGobjectProperty) item = NULL;
+
+      item = g_list_model_get_item (model, i);
+
+      if (item == property)
+        {
+          g_list_store_remove (self->properties, i);
+          return;
+        }
+    }
+}
+
+GListModel *
+gbp_gobject_spec_get_signals (GbpGobjectSpec *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC (self), NULL);
+
+  return G_LIST_MODEL (self->signals);
+}
+
+void
+gbp_gobject_spec_add_signal (GbpGobjectSpec   *self,
+                             GbpGobjectSignal *signal)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_SPEC (self));
+  g_return_if_fail (GBP_IS_GOBJECT_SIGNAL (signal));
+
+  g_list_store_append (self->signals, signal);
+}
+
+void
+gbp_gobject_spec_remove_signal (GbpGobjectSpec   *self,
+                                GbpGobjectSignal *signal)
+{
+  GListModel *model;
+  guint n_items;
+  guint i;
+
+  g_return_if_fail (GBP_IS_GOBJECT_SPEC (self));
+  g_return_if_fail (GBP_IS_GOBJECT_SIGNAL (signal));
+
+  model = G_LIST_MODEL (self->signals);
+  n_items = g_list_model_get_n_items (model);
+
+  for (i = 0; i < n_items; i++)
+    {
+      g_autoptr(GbpGobjectSignal) item = NULL;
+
+      item = g_list_model_get_item (model, i);
+
+      if (item == signal)
+        {
+          g_list_store_remove (self->signals, i);
+          return;
+        }
+    }
+}
+
+GbpGobjectSpec *
+gbp_gobject_spec_new (void)
+{
+  return g_object_new (GBP_TYPE_GOBJECT_SPEC, NULL);
+}
+
+gboolean
+gbp_gobject_spec_get_ready (GbpGobjectSpec *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC (self), FALSE);
+
+  if (!self->class_name || !*self->class_name ||
+      !self->namespace || !*self->namespace ||
+      !self->parent_name || !*self->parent_name)
+    return FALSE;
+
+  return TRUE;
+}
+
+const gchar *
+gbp_gobject_spec_get_name (GbpGobjectSpec *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC (self), NULL);
+
+  return self->name;
+}
+
+const gchar *
+gbp_gobject_spec_get_namespace (GbpGobjectSpec *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC (self), NULL);
+
+  return self->namespace;
+}
+
+const gchar *
+gbp_gobject_spec_get_class_name (GbpGobjectSpec *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC (self), NULL);
+
+  return self->class_name;
+}
+
+const gchar *
+gbp_gobject_spec_get_parent_name (GbpGobjectSpec *self)
+{
+  g_return_val_if_fail (GBP_IS_GOBJECT_SPEC (self), NULL);
+
+  return self->parent_name;
+}
diff --git a/plugins/gobject-templates/gbp-gobject-spec.h b/plugins/gobject-templates/gbp-gobject-spec.h
new file mode 100644
index 0000000..05d1a38
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-spec.h
@@ -0,0 +1,52 @@
+/* gbp-gobject-spec.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_SPEC_H
+#define GBP_GOBJECT_SPEC_H
+
+#include <gio/gio.h>
+
+#include "gbp-gobject-property.h"
+#include "gbp-gobject-signal.h"
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GOBJECT_SPEC (gbp_gobject_spec_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectSpec, gbp_gobject_spec, GBP, GOBJECT_SPEC, GObject)
+
+GbpGobjectSpec *gbp_gobject_spec_new             (void);
+GListModel     *gbp_gobject_spec_get_properties  (GbpGobjectSpec     *self);
+GListModel     *gbp_gobject_spec_get_signals     (GbpGobjectSpec     *self);
+void            gbp_gobject_spec_add_property    (GbpGobjectSpec     *self,
+                                                  GbpGobjectProperty *property);
+void            gbp_gobject_spec_remove_property (GbpGobjectSpec     *self,
+                                                  GbpGobjectProperty *property);
+void            gbp_gobject_spec_add_signal      (GbpGobjectSpec     *self,
+                                                  GbpGobjectSignal   *signal);
+void            gbp_gobject_spec_remove_signal   (GbpGobjectSpec     *self,
+                                                  GbpGobjectSignal   *signal);
+gboolean        gbp_gobject_spec_get_ready       (GbpGobjectSpec     *self);
+const gchar    *gbp_gobject_spec_get_name        (GbpGobjectSpec     *self);
+const gchar    *gbp_gobject_spec_get_namespace   (GbpGobjectSpec     *self);
+const gchar    *gbp_gobject_spec_get_class_name  (GbpGobjectSpec     *self);
+const gchar    *gbp_gobject_spec_get_parent_name (GbpGobjectSpec     *self);
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_SPEC_H */
diff --git a/plugins/gobject-templates/gbp-gobject-template.c 
b/plugins/gobject-templates/gbp-gobject-template.c
new file mode 100644
index 0000000..b97588f
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-template.c
@@ -0,0 +1,349 @@
+/* gbp-gobject-template.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-template"
+
+#include "gbp-gobject-template.h"
+
+struct _GbpGobjectTemplate
+{
+  IdeTemplateBase parent_instance;
+
+  GbpGobjectSpec     *spec;
+  GFile              *directory;
+  GbpGobjectLanguage  language;
+};
+
+enum {
+  PROP_0,
+  PROP_DIRECTORY,
+  PROP_SPEC,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (GbpGobjectTemplate, gbp_gobject_template, IDE_TYPE_TEMPLATE_BASE)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_gobject_template_finalize (GObject *object)
+{
+  GbpGobjectTemplate *self = (GbpGobjectTemplate *)object;
+
+  g_clear_object (&self->directory);
+  g_clear_object (&self->spec);
+
+  G_OBJECT_CLASS (gbp_gobject_template_parent_class)->finalize (object);
+}
+
+static void
+gbp_gobject_template_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  GbpGobjectTemplate *self = GBP_GOBJECT_TEMPLATE (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIRECTORY:
+      g_value_set_object (value, self->directory);
+      break;
+
+    case PROP_SPEC:
+      g_value_set_object (value, self->spec);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_template_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  GbpGobjectTemplate *self = GBP_GOBJECT_TEMPLATE (object);
+
+  switch (prop_id)
+    {
+    case PROP_DIRECTORY:
+      gbp_gobject_template_set_directory (self, g_value_get_object (value));
+      break;
+
+    case PROP_SPEC:
+      gbp_gobject_template_set_spec (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_gobject_template_class_init (GbpGobjectTemplateClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_gobject_template_finalize;
+  object_class->get_property = gbp_gobject_template_get_property;
+  object_class->set_property = gbp_gobject_template_set_property;
+
+  properties [PROP_DIRECTORY] =
+    g_param_spec_object ("directory",
+                         NULL,
+                         NULL,
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SPEC] =
+    g_param_spec_object ("spec",
+                         NULL,
+                         NULL,
+                         GBP_TYPE_GOBJECT_SPEC,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gbp_gobject_template_init (GbpGobjectTemplate *self)
+{
+}
+
+void
+gbp_gobject_template_set_spec (GbpGobjectTemplate *self,
+                               GbpGobjectSpec     *spec)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_TEMPLATE (self));
+  g_return_if_fail (GBP_IS_GOBJECT_SPEC (spec));
+
+  if (g_set_object (&self->spec, spec))
+    g_object_notify_by_pspec (G_OBJECT (spec), properties [PROP_SPEC]);
+}
+
+void
+gbp_gobject_template_set_directory (GbpGobjectTemplate *self,
+                                    GFile              *directory)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_TEMPLATE (self));
+  g_return_if_fail (G_IS_FILE (directory));
+
+  if (g_set_object (&self->directory, directory))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIRECTORY]);
+}
+
+static gchar *
+mangle_name (const gchar *name)
+{
+  GString *symbol_name = g_string_new ("");
+  gint i;
+
+  /* copied from gtkbuilder.c */
+
+  for (i = 0; name[i] != '\0'; i++)
+    {
+      /* skip if uppercase, first or previous is uppercase */
+      if ((name[i] == g_ascii_toupper (name[i]) &&
+           i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) ||
+          (i > 2 && name[i]   == g_ascii_toupper (name[i]) &&
+           name[i-1] == g_ascii_toupper (name[i-1]) &&
+           name[i-2] == g_ascii_toupper (name[i-2])))
+        g_string_append_c (symbol_name, '_');
+      g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
+    }
+
+  return g_string_free (symbol_name, FALSE);
+}
+
+static gchar *
+mangle_parent_type (const gchar *name)
+{
+  g_autofree gchar *mangled = NULL;
+  g_auto(GStrv) parts = NULL;
+  GString *str;
+
+  if (!name || !*name)
+    return g_strdup ("G_TYPE_OBJECT");
+
+  str = g_string_new (NULL);
+  mangled = mangle_name (name);
+  parts = g_strsplit (mangled, "_", 0);
+
+  if (NULL == strchr (mangled, '_'))
+    {
+      /* Hrmm, looks like we might have a G* parent */
+      if (*mangled == 'g')
+        {
+          g_autofree gchar *upper = g_utf8_strup (name + 1, -1);
+
+          return g_strdup_printf ("G_TYPE_%s", upper);
+        }
+    }
+
+  for (guint i = 0; parts[i]; i++)
+    {
+      g_autofree gchar *upper = g_utf8_strup (parts[i], -1);
+
+      if (i != 0)
+        g_string_append_c (str, '_');
+      g_string_append (str, upper);
+      if (i == 0)
+        g_string_append (str, "_TYPE");
+    }
+
+  return g_string_free (str, FALSE);
+}
+
+static void
+gbp_gobject_template_add_c_resources (GbpGobjectTemplate *self)
+{
+  g_autofree gchar *mangled = NULL;
+  g_autofree gchar *mangled_dash = NULL;
+  g_autofree gchar *mangled_upper = NULL;
+  g_autofree gchar *c_name = NULL;
+  g_autofree gchar *h_name = NULL;
+  g_autofree gchar *namespace_upper = NULL;
+  g_autofree gchar *class_name_upper = NULL;
+  g_autofree gchar *namespace_lower = NULL;
+  g_autofree gchar *class_name_lower = NULL;
+  g_autofree gchar *class_name_mangled = NULL;
+  g_autofree gchar *namespace_mangled = NULL;
+  g_autofree gchar *parent_mangled_upper = NULL;
+  g_autoptr(GFile) c_dest = NULL;
+  g_autoptr(GFile) h_dest = NULL;
+  g_autoptr(TmplScope) scope = NULL;
+  const gchar *class_name;
+  const gchar *namespace;
+  const gchar *name;
+  const gchar *parent_name;
+
+  g_assert (GBP_IS_GOBJECT_TEMPLATE (self));
+  g_assert (GBP_IS_GOBJECT_SPEC (self->spec));
+  g_assert (G_IS_FILE (self->directory));
+  g_assert (self->language == GBP_GOBJECT_LANGUAGE_C);
+
+  name = gbp_gobject_spec_get_name (self->spec);
+  class_name = gbp_gobject_spec_get_class_name (self->spec);
+  namespace = gbp_gobject_spec_get_namespace (self->spec);
+  parent_name = gbp_gobject_spec_get_parent_name (self->spec);
+
+  mangled = mangle_name (name);
+  mangled_dash = g_strdelimit (g_strdup (mangled), "_", '-');
+  mangled_upper = g_utf8_strup (mangled, -1);
+
+  class_name_mangled = mangle_name (class_name);
+  namespace_mangled = mangle_name (namespace);
+
+  namespace_upper = g_utf8_strup (namespace_mangled, -1);
+  class_name_upper = g_utf8_strup (class_name_mangled, -1);
+
+  namespace_lower = g_utf8_strdown (mangled, -1);
+  class_name_lower = g_utf8_strdown (mangled, -1);
+
+  c_name = g_strdup_printf ("%s.c", mangled_dash);
+  h_name = g_strdup_printf ("%s.h", mangled_dash);
+
+  c_dest = g_file_get_child (self->directory, c_name);
+  h_dest = g_file_get_child (self->directory, h_name);
+
+  parent_mangled_upper = mangle_parent_type (parent_name);
+
+  scope = tmpl_scope_new ();
+
+  tmpl_symbol_assign_object (tmpl_scope_get (scope, "spec"), self->spec);
+
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "file_prefix"), mangled_dash);
+
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "Class"), class_name);
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "Name"), name);
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "Namespace"), namespace);
+
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "CLASS"), class_name_upper);
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "NAMESPACE"), namespace_upper);
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "NAME"), mangled_upper);
+
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "PARENT_TYPE"), parent_mangled_upper);
+
+#define ADD_SPACE(name, n) \
+  G_STMT_START { \
+    GString *space = g_string_new (NULL); \
+    guint count = (n); \
+    for (guint i = 0; i < count; i++) \
+      g_string_append_c (space, ' '); \
+    tmpl_symbol_assign_string (tmpl_scope_get (scope, name), space->str); \
+    g_string_free (space, TRUE); \
+  } G_STMT_END
+
+  ADD_SPACE ("space", strlen (mangled));
+  ADD_SPACE ("Space", strlen (name));
+
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "class"), class_name_lower);
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "namespace"), namespace_lower);
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "name"), mangled);
+
+  tmpl_symbol_assign_string (tmpl_scope_get (scope, "Parent"), parent_name);
+
+  ide_template_base_add_resource (IDE_TEMPLATE_BASE (self),
+                                  "/org/gnome/builder/plugins/gobject-templates/gobject.c.tmpl",
+                                  c_dest,
+                                  scope,
+                                  0640);
+
+  ide_template_base_add_resource (IDE_TEMPLATE_BASE (self),
+                                  "/org/gnome/builder/plugins/gobject-templates/gobject.h.tmpl",
+                                  h_dest,
+                                  scope,
+                                  0640);
+}
+
+void
+gbp_gobject_template_set_language (GbpGobjectTemplate *self,
+                                   GbpGobjectLanguage  language)
+{
+  g_return_if_fail (GBP_IS_GOBJECT_TEMPLATE (self));
+
+  self->language = language;
+
+  switch (language)
+    {
+    case GBP_GOBJECT_LANGUAGE_C:
+      gbp_gobject_template_add_c_resources (self);
+      break;
+
+    case GBP_GOBJECT_LANGUAGE_CPLUSPLUS:
+      break;
+
+    case GBP_GOBJECT_LANGUAGE_PYTHON:
+      break;
+
+    case GBP_GOBJECT_LANGUAGE_VALA:
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+}
+
+GbpGobjectTemplate *
+gbp_gobject_template_new (void)
+{
+  return g_object_new (GBP_TYPE_GOBJECT_TEMPLATE, NULL);
+}
diff --git a/plugins/gobject-templates/gbp-gobject-template.h 
b/plugins/gobject-templates/gbp-gobject-template.h
new file mode 100644
index 0000000..ad5197e
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-template.h
@@ -0,0 +1,50 @@
+/* gbp-gobject-template.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_TEMPLATE_H
+#define GBP_GOBJECT_TEMPLATE_H
+
+#include <ide.h>
+
+#include "gbp-gobject-spec.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  GBP_GOBJECT_LANGUAGE_C,
+  GBP_GOBJECT_LANGUAGE_CPLUSPLUS,
+  GBP_GOBJECT_LANGUAGE_VALA,
+  GBP_GOBJECT_LANGUAGE_PYTHON,
+} GbpGobjectLanguage;
+
+#define GBP_TYPE_GOBJECT_TEMPLATE (gbp_gobject_template_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectTemplate, gbp_gobject_template, GBP, GOBJECT_TEMPLATE, IdeTemplateBase)
+
+GbpGobjectTemplate *gbp_gobject_template_new           (void);
+void                gbp_gobject_template_set_spec      (GbpGobjectTemplate *self,
+                                                        GbpGobjectSpec     *spec);
+void                gbp_gobject_template_set_directory (GbpGobjectTemplate *self,
+                                                        GFile              *directory);
+void                gbp_gobject_template_set_language  (GbpGobjectTemplate *self,
+                                                        GbpGobjectLanguage  language);
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_TEMPLATE_H */
diff --git a/plugins/gobject-templates/gbp-gobject-workbench-addin.c 
b/plugins/gobject-templates/gbp-gobject-workbench-addin.c
new file mode 100644
index 0000000..050ac64
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-workbench-addin.c
@@ -0,0 +1,228 @@
+/* gbp-gobject-workbench-addin.c
+ *
+ * Copyright (C) 2016 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 "gbp-gobject-workbench-addin"
+
+#include <glib/gi18n.h>
+#include <tmpl-glib.h>
+
+#include "gbp-gobject-dialog.h"
+#include "gbp-gobject-spec.h"
+#include "gbp-gobject-template.h"
+#include "gbp-gobject-workbench-addin.h"
+
+struct _GbpGobjectWorkbenchAddin
+{
+  GObject parent_instance;
+
+  IdeWorkbench *workbench;
+};
+
+static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GbpGobjectWorkbenchAddin, gbp_gobject_workbench_addin, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN,
+                                               workbench_addin_iface_init))
+
+static void
+gbp_gobject_workbench_addin_class_init (GbpGobjectWorkbenchAddinClass *klass)
+{
+}
+
+static void
+gbp_gobject_workbench_addin_init (GbpGobjectWorkbenchAddin *self)
+{
+}
+
+static void
+dialog_hide_cb (GbpGobjectWorkbenchAddin *self,
+                GbpGobjectDialog         *dialog)
+{
+  g_assert (GBP_IS_GOBJECT_WORKBENCH_ADDIN (self));
+  g_assert (GBP_IS_GOBJECT_DIALOG (dialog));
+
+  gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+expand_all_cb (GObject      *object,
+               GAsyncResult *result,
+               gpointer      user_data)
+{
+  GbpGobjectTemplate *template = (GbpGobjectTemplate *)object;
+  g_autoptr(GbpGobjectWorkbenchAddin) self = user_data;
+  g_autoptr(GError) error = NULL;
+  IdeContext *context = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_GOBJECT_TEMPLATE (template));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (self->workbench != NULL)
+    context = ide_workbench_get_context (self->workbench);
+
+  if (!ide_template_base_expand_all_finish (IDE_TEMPLATE_BASE (template), result, &error))
+    {
+      if (context != NULL)
+        ide_context_warning (context, "%s", error->message);
+      else
+        g_warning ("%s", error->message);
+    }
+
+  IDE_EXIT;
+}
+
+static void
+generate_from_spec (GbpGobjectWorkbenchAddin *self,
+                    GbpGobjectSpec           *spec,
+                    GFile                    *directory)
+{
+  g_autoptr(GbpGobjectTemplate) template = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_GOBJECT_WORKBENCH_ADDIN (self));
+  g_assert (GBP_IS_GOBJECT_SPEC (spec));
+  g_assert (G_IS_FILE (directory));
+
+  template = gbp_gobject_template_new ();
+  gbp_gobject_template_set_spec (template, spec);
+  gbp_gobject_template_set_directory (template, directory);
+  gbp_gobject_template_set_language (template, GBP_GOBJECT_LANGUAGE_C);
+
+  ide_template_base_expand_all_async (IDE_TEMPLATE_BASE (template),
+                                      NULL,
+                                      expand_all_cb,
+                                      g_object_ref (self));
+
+  IDE_EXIT;
+}
+
+static void
+dialog_apply_cb (GbpGobjectWorkbenchAddin *self,
+                 GbpGobjectDialog         *dialog)
+{
+  g_autoptr(GFile) directory = NULL;
+  GbpGobjectSpec *spec;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_GOBJECT_WORKBENCH_ADDIN (self));
+  g_assert (GBP_IS_GOBJECT_DIALOG (dialog));
+
+  spec = gbp_gobject_dialog_get_spec (dialog);
+  directory = gbp_gobject_dialog_get_directory (dialog);
+
+  g_assert (GBP_IS_GOBJECT_SPEC (spec));
+  g_assert (G_IS_FILE (directory));
+
+  if (gbp_gobject_spec_get_ready (spec))
+    generate_from_spec (self, spec, directory);
+
+  gtk_widget_destroy (GTK_WIDGET (dialog));
+
+  IDE_EXIT;
+}
+
+static void
+new_gobject_activate (GSimpleAction *action,
+                      GVariant      *param,
+                      gpointer       user_data)
+{
+  GbpGobjectWorkbenchAddin *self = user_data;
+  GbpGobjectDialog *dialog;
+  IdeContext *context;
+  IdeVcs *vcs;
+  GFile *workdir;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (param == NULL);
+  g_assert (GBP_IS_GOBJECT_WORKBENCH_ADDIN (self));
+
+  context = ide_workbench_get_context (self->workbench);
+  vcs = ide_context_get_vcs (context);
+  workdir = ide_vcs_get_working_directory (vcs);
+
+  dialog = g_object_new (GBP_TYPE_GOBJECT_DIALOG,
+                         "directory", workdir,
+                         "modal", TRUE,
+                         "transient-for", self->workbench,
+                         "title", _("New Class"),
+                         NULL);
+
+  g_signal_connect_object (dialog,
+                           "close",
+                           G_CALLBACK (dialog_apply_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (dialog,
+                           "cancel",
+                           G_CALLBACK (dialog_hide_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static void
+gbp_gobject_workbench_addin_load (IdeWorkbenchAddin *addin,
+                                  IdeWorkbench      *workbench)
+{
+  GbpGobjectWorkbenchAddin *self = (GbpGobjectWorkbenchAddin *)addin;
+  g_autoptr(GSimpleActionGroup) group = NULL;
+  static const GActionEntry entries[] = {
+    { "new-gobject", new_gobject_activate },
+  };
+
+  g_assert (IDE_IS_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  self->workbench = workbench;
+
+  group = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (group),
+                                   entries,
+                                   G_N_ELEMENTS (entries),
+                                   self);
+  gtk_widget_insert_action_group (GTK_WIDGET (workbench),
+                                  "gobject-templates",
+                                  G_ACTION_GROUP (group));
+}
+
+static void
+gbp_gobject_workbench_addin_unload (IdeWorkbenchAddin *addin,
+                                    IdeWorkbench      *workbench)
+{
+  GbpGobjectWorkbenchAddin *self = (GbpGobjectWorkbenchAddin *)addin;
+
+  g_assert (IDE_IS_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  self->workbench = NULL;
+
+  gtk_widget_insert_action_group (GTK_WIDGET (workbench), "gobject-templates", NULL);
+}
+
+static void
+workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
+{
+  iface->load = gbp_gobject_workbench_addin_load;
+  iface->unload = gbp_gobject_workbench_addin_unload;
+}
diff --git a/plugins/gobject-templates/gbp-gobject-workbench-addin.h 
b/plugins/gobject-templates/gbp-gobject-workbench-addin.h
new file mode 100644
index 0000000..9601914
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-workbench-addin.h
@@ -0,0 +1,33 @@
+/* gbp-gobject-workbench-addin.h
+ *
+ * Copyright (C) 2016 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 GBP_GOBJECT_WORKBENCH_ADDIN_H
+#define GBP_GOBJECT_WORKBENCH_ADDIN_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_GOBJECT_WORKBENCH_ADDIN (gbp_gobject_workbench_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpGobjectWorkbenchAddin, gbp_gobject_workbench_addin, GBP, GOBJECT_WORKBENCH_ADDIN, 
GObject)
+
+G_END_DECLS
+
+#endif /* GBP_GOBJECT_WORKBENCH_ADDIN_H */
+
diff --git a/plugins/gobject-templates/gobject-templates-plugin.c 
b/plugins/gobject-templates/gobject-templates-plugin.c
new file mode 100644
index 0000000..7847786
--- /dev/null
+++ b/plugins/gobject-templates/gobject-templates-plugin.c
@@ -0,0 +1,30 @@
+/* gobject-templates-plugin.c
+ *
+ * Copyright (C) 2016 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.h>
+#include <libpeas/peas.h>
+
+#include "gbp-gobject-workbench-addin.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKBENCH_ADDIN,
+                                              GBP_TYPE_GOBJECT_WORKBENCH_ADDIN);
+}
diff --git a/plugins/gobject-templates/gobject-templates.gresource.xml 
b/plugins/gobject-templates/gobject-templates.gresource.xml
new file mode 100644
index 0000000..43c3b78
--- /dev/null
+++ b/plugins/gobject-templates/gobject-templates.gresource.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/builder/plugins/gobject-templates">
+    <!-- Autoload resources -->
+    <file compressed="true">gtk/menus.ui</file>
+
+    <!-- UI Files -->
+    <file compressed="true">gbp-gobject-dialog.ui</file>
+    <file compressed="true">gbp-gobject-property-editor.ui</file>
+    <file compressed="true">gbp-gobject-signal-editor.ui</file>
+    <file compressed="true">gbp-gobject-spec-editor.ui</file>
+
+    <!-- C Templates -->
+    <file compressed="true">gobject.c.tmpl</file>
+    <file compressed="true">gobject.h.tmpl</file>
+  </gresource>
+</gresources>
diff --git a/plugins/gobject-templates/gobject-templates.plugin 
b/plugins/gobject-templates/gobject-templates.plugin
new file mode 100644
index 0000000..a24eb45
--- /dev/null
+++ b/plugins/gobject-templates/gobject-templates.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=gobject-templates
+Name=GObject Templates
+Description=Create new GObjects using templates
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2015 Christian Hergert
+Depends=project-tree-plugin
+Hidden=true
+Builtin=true
diff --git a/plugins/gobject-templates/gobject.c.tmpl b/plugins/gobject-templates/gobject.c.tmpl
new file mode 100644
index 0000000..5638a03
--- /dev/null
+++ b/plugins/gobject-templates/gobject.c.tmpl
@@ -0,0 +1,877 @@
+#define G_LOG_DOMAIN "{{file_prefix}}"
+
+#include "{{file_prefix}}.h"
+
+{{if spec.final}}
+struct _{{Name}}
+{
+  {{Parent}} parent_instance;
+};
+{{end}}
+
+typedef struct
+{
+{{for prop in spec.properties}}
+{{if false}}
+{{else if prop.kind == "string"}}
+  gchar *{{prop.cname}};
+{{else if prop.kind == "int"}}
+  gint {{prop.cname}};
+{{else if prop.kind == "int64"}}
+  gint64 {{prop.cname}};
+{{else if prop.kind == "long"}}
+  glong {{prop.cname}};
+{{else if prop.kind == "uint"}}
+  guint {{prop.cname}};
+{{else if prop.kind == "uint64"}}
+  guint64 {{prop.cname}};
+{{else if prop.kind == "ulong"}}
+  gulong {{prop.cname}};
+{{else if prop.kind == "float"}}
+  gfloat {{prop.cname}};
+{{else if prop.kind == "double"}}
+  gdouble {{prop.cname}};
+{{else if prop.kind == "pointer"}}
+  {{prop.ctype}} {{prop.cname}};
+{{else if prop.kind == "boolean"}}
+  gboolean {{prop.cname}};
+{{else if prop.kind == "char"}}
+  gchar {{prop.cname}};
+{{else if prop.kind == "unichar"}}
+  gunichar {{prop.cname}};
+{{else if prop.kind == "enum"}}
+  {{prop.ctype}} {{prop.cname}};
+{{else if prop.kind == "flags"}}
+  {{prop.ctype}} {{prop.cname}};
+{{else if prop.kind == "boxed"}}
+  {{prop.ctype}} *{{prop.cname}};
+{{else if prop.kind == "variant"}}
+  GVariant *{{prop.cname}};
+{{else if prop.kind == "object"}}
+  {{prop.ctype}} *{{prop.cname}};
+{{end}}
+{{end}}
+} {{Name}}Private;
+
+G_DEFINE_TYPE_WITH_PRIVATE ({{Name}}, {{name}}, {{PARENT_TYPE}})
+
+{{if spec.properties.get_n_items() != 0}}
+enum {
+  PROP_0,
+{{for prop in spec.properties}}
+  PROP_{{prop.cname.upper()}},
+{{end}}
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+{{name}}_get_property (GObject    *object,
+{{name.space()}}               guint       prop_id,
+{{name.space()}}               GValue     *value,
+{{name.space()}}               GParamSpec *pspec)
+{
+  {{Name}} *self = ({{Name}} *)object;
+
+  switch (prop_id)
+    {
+{{for prop in spec.properties}}
+    case PROP_{{prop.cname.upper()}}:
+{{if false}}
+{{else if prop.kind == "string"}}
+      g_value_set_string (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "int"}}
+      g_value_set_int (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "int64"}}
+      g_value_set_int64 (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "long"}}
+      g_value_set_long (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "uint"}}
+      g_value_set_uint (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "uint64"}}
+      g_value_set_uint64 (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "ulong"}}
+      g_value_set_ulong (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "float"}}
+      g_value_set_float (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "double"}}
+      g_value_set_double (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "pointer"}}
+      g_value_set_pointer (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "boolean"}}
+      g_value_set_boolean (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "char"}}
+      g_value_set_char (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "unichar"}}
+      g_value_set_unichar (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "enum"}}
+      g_value_set_enum (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "flags"}}
+      g_value_set_flags (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "boxed"}}
+      g_value_set_boxed (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "variant"}}
+      g_value_set_variant (value, {{name}}_get_{{prop.cname}} (self));
+{{else if prop.kind == "object"}}
+      g_value_set_object (value, {{name}}_get_{{prop.cname}} (self));
+{{end}}
+      break;
+
+{{end}}
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+{{name}}_set_property (GObject      *object,
+{{name.space()}}               guint         prop_id,
+{{name.space()}}               const GValue *value,
+{{name.space()}}               GParamSpec   *pspec)
+{
+  {{Name}} *self = ({{Name}} *)object;
+
+  switch (prop_id)
+    {
+{{for prop in spec.properties}}
+    case PROP_{{prop.cname.upper()}}:
+      {{name}}_set_{{prop.cname}} (self, g_value_get_{{prop.kind.nick()}} (value));
+      break;
+{{end}}
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+{{end}}
+static void
+{{name}}_finalize (GObject *object)
+{
+  {{Name}} *self = ({{Name}} *)object;
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+{{for prop in spec.properties}}
+{{if false}}
+{{else if prop.kind == "string"}}
+  g_clear_pointer (&priv->{{prop.cname}}, g_free);
+{{else if prop.kind == "boxed"}}
+  if (priv->{{prop.cname}} != NULL)
+    {
+      g_boxed_free ({{prop.gtype}}, priv->{{prop.cname}});
+      priv->{{prop.cname}} = NULL;
+    }
+{{else if prop.kind == "object"}}
+  g_clear_object (&priv->{{prop.cname}});
+{{else if prop.kind == "variant"}}
+  g_clear_pointer (&priv->{{prop.cname}}, g_variant_unref);
+{{else if prop.kind == "pointer"}}
+  priv->{{prop.cname}} = NULL;
+{{end}}
+{{end}}
+
+  G_OBJECT_CLASS ({{name}}_parent_class)->finalize (object);
+}
+
+static void
+{{name}}_class_init ({{Name}}Class *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = {{name}}_finalize;
+{{if spec.properties.get_n_items() != 0}}
+  object_class->get_property = {{name}}_get_property;
+  object_class->set_property = {{name}}_set_property;
+{{end}}
+
+{{for prop in spec.properties}}
+  /**
+   * {{Name}}:{{prop.name}}:
+   *
+   * The "{{prop.name}}" property.
+   */
+  properties [PROP_{{prop.cname.upper()}}] =
+    g_param_spec_{{prop.kind.nick()}} ("{{prop.name}}",
+                 {{prop.kind.nick().space()}}  "{{prop.cname.title()}}",
+                 {{prop.kind.nick().space()}}  "{{prop.cname.title()}}",
+{{if false}}
+{{else if prop.kind == "string"}}
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "int"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "int64"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "long"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "uint"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "uint64"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "ulong"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "float"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "double"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "pointer"}}
+{{else if prop.kind == "boolean"}}
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "char"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "unichar"}}
+                 {{prop.kind.nick().space()}}  {{prop.minimum}},
+                 {{prop.kind.nick().space()}}  {{prop.maximum}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "enum"}}
+                 {{prop.kind.nick().space()}}  {{prop.gtype}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "flags"}}
+                 {{prop.kind.nick().space()}}  {{prop.gtype}},
+                 {{prop.kind.nick().space()}}  {{prop.default}},
+{{else if prop.kind == "boxed"}}
+                 {{prop.kind.nick().space()}}  {{prop.gtype}},
+{{else if prop.kind == "variant"}}
+                 {{prop.kind.nick().space()}}  G_VARIANT_TYPE_ANY,
+                 {{prop.kind.nick().space()}}  NULL,
+{{else if prop.kind == "object"}}
+                 {{prop.kind.nick().space()}}  {{prop.gtype}},
+{{end}}
+                 {{prop.kind.nick().space()}}  (G_PARAM_STATIC_STRINGS |
+                 {{prop.kind.nick().space()}}   G_PARAM_EXPLICIT_NOTIFY |
+{{if prop.construct_only}}
+                 {{prop.kind.nick().space()}}   G_PARAM_CONSTRUCT_ONLY |
+{{end}}
+{{if prop.readable && prop.writable}}
+                 {{prop.kind.nick().space()}}   G_PARAM_READWRITE));
+{{else if prop.readable}}
+                 {{prop.kind.nick().space()}}   G_PARAM_READABLE));
+{{else if prop.writable}}
+                 {{prop.kind.nick().space()}}   G_PARAM_WRITABLE));
+{{end}}
+{{end}}
+{{if spec.properties.get_n_items() != 0}}
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+{{end}}
+}
+
+static void
+{{name}}_init ({{Name}} *self)
+{
+}
+
+/**
+ * {{name}}_new:
+ *
+ * Creates a new instance of #{{Name}}.
+ *
+ * Returns: The newly created instance.
+ */
+{{Name}} *
+{{name}}_new (void)
+{
+  return g_object_new ({{NAMESPACE}}_TYPE_{{CLASS}}, NULL);
+}
+{{for prop in spec.properties}}
+{{if false}}
+{{else if prop.kind == "string"}}
+{{if prop.readable}}
+
+/**
+ * {{name}}_get_{{prop.cname}}:
+ * @self: A #{{Name}}
+ *
+ * Gets the "{{prop.name}}" property.
+ *
+ * Returns: A string containing the "{{prop.name}}" property.
+ */
+const gchar *
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), NULL);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+/**
+ * {{name}}_get_{{prop.cname}}:
+ * @self: A #{{Name}}
+ * @{{prop.cname}}: A string or %NULL.
+ *
+ * Sets the "{{prop.name}}" property.
+ */
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, const gchar *{{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (g_strcmp0 ({{prop.cname}}, priv->{{prop.cname}}) != 0)
+    {
+      g_free (priv->{{prop.cname}});
+      priv->{{prop.cname}} = g_strdup ({{prop.cname}});
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "int"}}
+{{if prop.readable}}
+
+gint
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, gint {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "int64"}}
+{{if prop.readable}}
+
+gint64
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, gint64 {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "long"}}
+{{if prop.readable}}
+
+glong
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, glong {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "uint"}}
+{{if prop.readable}}
+
+guint
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, guint {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "uint64"}}
+{{if prop.readable}}
+
+guint64
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, guint64 {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "ulong"}}
+{{if prop.readable}}
+
+gulong
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, gulong {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "float"}}
+{{if prop.readable}}
+
+gfloat
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0.0f);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, gfloat {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "double"}}
+{{if prop.readable}}
+
+gdouble
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0.0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, gdouble {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "pointer"}}
+{{if prop.readable}}
+
+/**
+ * {{name}}_get_{{prop.cname}}:
+ * @self: A #{{Name}}
+ *
+ * Returns: (transfer none) (nullable):
+ */
+{{prop.ctype}}
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "boolean"}}
+{{if prop.readable}}
+
+gboolean
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, gboolean {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  {{prop.cname}} = !!{{prop.cname}};
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "char"}}
+{{if prop.readable}}
+
+gchar
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, gchar {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "unichar"}}
+{{if prop.readable}}
+
+gunichar
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, gunichar {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "enum"}}
+{{if prop.readable}}
+
+{{prop.ctype}}
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "flags"}}
+{{if prop.readable}}
+
+{{prop.ctype}}
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), 0);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} {{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (priv->{{prop.cname}} != {{prop.cname}})
+    {
+      priv->{{prop.cname}} = {{prop.cname}};
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "boxed"}}
+{{if prop.readable}}
+
+/**
+ * {{name}}_get_{{prop.cname}}:
+ * @self: A #{{Name}}
+ *
+ * Returns: (transfer none) (nullable): A #{{prop.ctype}} or %NULL.
+ */
+{{prop.ctype}} *
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), NULL);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} *{{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if ({{prop.cname}} != priv->{{prop.cname}})
+    {
+      if (priv->{{prop.cname}} != NULL)
+        {
+          g_boxed_free ({{prop.gtype}}, priv->{{prop.cname}});
+          priv->{{prop.cname}} = NULL;
+        }
+
+      if ({{prop.cname}} != NULL)
+        priv->{{prop.cname}} = g_boxed_copy ({{prop.gtype}}, {{prop.cname}});
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "variant"}}
+{{if prop.readable}}
+
+/**
+ * {{name}}_get_{{prop.cname}}:
+ * @self: A #{{Name}}
+ *
+ * Returns: (transfer none) (nullable):
+ */
+GVariant *
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), NULL);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, GVariant *{{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if ({{prop.cname}} != priv->{{prop.cname}})
+    {
+      g_clear_pointer (&priv->{{prop.cname}}, g_variant_unref);
+      priv->{{prop.cname}} = {{prop.cname}} ? g_variant_ref ({{prop.cname}}) : NULL;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+    }
+}
+{{end}}
+{{else if prop.kind == "object"}}
+{{if prop.readable}}
+
+/**
+ * {{name}}_get_{{prop.cname}}:
+ * @self: A #{{Name}}
+ *
+ * Returns: (transfer none) (nullable): A #{{prop.ctype}} or %NULL.
+ */
+{{prop.ctype}} *
+{{name}}_get_{{prop.cname}} ({{Name}} *self)
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_val_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self), NULL);
+
+  return priv->{{prop.cname}};
+}
+{{end}}
+{{if prop.writable}}
+
+/**
+ * {{name}}_set_{{prop.cname}}:
+ * @self: A #{{Name}}
+ * @{{prop.cname}}: A #{{prop.ctype}}
+ *
+ * Sets the "{{prop.name}}" property.
+ */
+void
+{{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} *{{prop.cname}})
+{
+  {{Name}}Private *priv = {{name}}_get_instance_private (self);
+
+  g_return_if_fail ({{NAMESPACE}}_IS_{{CLASS}} (self));
+
+  if (g_set_object (&priv->{{prop.cname}}, {{prop.cname}}))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_{{prop.cname.upper()}}]);
+}
+{{end}}
+{{end}}
+{{end}}
+
diff --git a/plugins/gobject-templates/gobject.h.tmpl b/plugins/gobject-templates/gobject.h.tmpl
new file mode 100644
index 0000000..c711b71
--- /dev/null
+++ b/plugins/gobject-templates/gobject.h.tmpl
@@ -0,0 +1,84 @@
+#ifndef {{NAME}}_H
+#define {{NAME}}_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define {{NAMESPACE}}_TYPE_{{CLASS}} ({{name}}_get_type())
+
+{{if spec.final}}
+G_DECLARE_FINAL_TYPE ({{Name}}, {{name}}, {{NAMESPACE}}, {{CLASS}}, {{Parent}})
+{{else}}
+G_DECLARE_DERIVABLE_TYPE ({{Name}}, {{name}}, {{NAMESPACE}}, {{CLASS}}, {{Parent}})
+
+struct _{{Name}}Class
+{
+  {{Parent}}Class parent_class;
+};
+{{end}}
+
+{{Name}} *{{name}}_new (void);
+{{for prop in spec.properties}}
+
+{{if false}}
+{{else if prop.kind == "string"}}
+const gchar *{{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, const gchar *{{prop.cname}});
+{{else if prop.kind == "int"}}
+gint {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gint {{prop.cname}});
+{{else if prop.kind == "int64"}}
+gint64 {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gint64 {{prop.cname}});
+{{else if prop.kind == "long"}}
+glong {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, glong {{prop.cname}});
+{{else if prop.kind == "uint"}}
+guint {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, guint {{prop.cname}});
+{{else if prop.kind == "uint64"}}
+guint64 {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, guint64 {{prop.cname}});
+{{else if prop.kind == "ulong"}}
+gulong {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gulong {{prop.cname}});
+{{else if prop.kind == "float"}}
+gfloat {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gfloat {{prop.cname}});
+{{else if prop.kind == "double"}}
+gdouble {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gdouble {{prop.cname}});
+{{else if prop.kind == "pointer"}}
+gpointer {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gpointer {{prop.cname}});
+{{else if prop.kind == "boolean"}}
+gboolean {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gboolean {{prop.cname}});
+{{else if prop.kind == "char"}}
+gchar {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gchar {{prop.cname}});
+{{else if prop.kind == "unichar"}}
+gunichar {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, gunichar {{prop.cname}});
+{{else if prop.kind == "enum"}}
+{{prop.ctype}} {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} {{prop.cname}});
+{{else if prop.kind == "flags"}}
+{{prop.ctype}} {{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} {{prop.cname}});
+{{else if prop.kind == "boxed"}}
+{{prop.ctype}} *{{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} *{{prop.cname}});
+{{else if prop.kind == "variant"}}
+GVariant *{{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, GVariant *{{prop.cname}});
+{{else if prop.kind == "object"}}
+{{prop.ctype}} *{{name}}_get_{{prop.cname}} ({{Name}} *self);
+void {{name}}_set_{{prop.cname}} ({{Name}} *self, {{prop.ctype}} *{{prop.cname}});
+{{end}}
+{{end}}
+
+G_END_DECLS
+
+#endif /* {{NAME}}_H */
diff --git a/plugins/gobject-templates/gtk/menus.ui b/plugins/gobject-templates/gtk/menus.ui
new file mode 100644
index 0000000..63177cb
--- /dev/null
+++ b/plugins/gobject-templates/gtk/menus.ui
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <menu id="gb-project-tree-popup-menu">
+    <section id="gb-project-tree-new-section">
+      <submenu id="gb-project-tree-new-section-submenu">
+        <section id="gb-project-tree-new-gobject-section">
+          <attribute name="after">gb-project-tree-new-empty-file-section</attribute>
+          <item>
+            <attribute name="label" translatable="yes">Class</attribute>
+            <attribute name="action">gobject-templates.new-gobject</attribute>
+          </item>
+          <item>
+            <attribute name="label" translatable="yes">Interface</attribute>
+            <attribute name="action">gobject-templates.new-ginterface</attribute>
+          </item>
+          <item>
+            <attribute name="label" translatable="yes">Widget</attribute>
+            <attribute name="action">gobject-templates.new-gtkwidget</attribute>
+          </item>
+        </section>
+      </submenu>
+    </section>
+  </menu>
+</interface>
diff --git a/src/Makefile.am b/src/Makefile.am
index 3fdc5bc..24ec362 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -17,7 +17,9 @@ AM_CPPFLAGS = \
        -I$(top_builddir)/libide \
        -I$(top_srcdir)/libide \
        -I$(top_srcdir)/contrib/pnl \
+       -I$(top_srcdir)/contrib/tmpl \
        -I$(top_builddir)/contrib/pnl \
+       -I$(top_builddir)/contrib/tmpl \
        $(NULL)
 
 gnome_builder_libs = \


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