[gnome-builder/wip/chergert/gobjgen] wip
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/gobjgen] wip
- Date: Fri, 5 Aug 2016 21:41:28 +0000 (UTC)
commit 97b9c6022ab7b007e814377f52b973d3c62c432a
Author: Christian Hergert <chergert redhat com>
Date: Fri Aug 5 13:47:35 2016 -0700
wip
configure.ac | 3 +
libide/ide.h | 1 +
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 | 196 ++++++
.../gbp-gobject-property-editor.c | 273 ++++++++
.../gbp-gobject-property-editor.h | 36 +
.../gbp-gobject-property-editor.ui | 720 ++++++++++++++++++++
plugins/gobject-templates/gbp-gobject-property.c | 406 +++++++++++
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 | 500 ++++++++++++++
plugins/gobject-templates/gbp-gobject-spec.h | 52 ++
plugins/gobject-templates/gbp-gobject-template.c | 277 ++++++++
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 | 7 +
plugins/gobject-templates/gobject.h.tmpl | 18 +
plugins/gobject-templates/gtk/menus.ui | 24 +
33 files changed, 4570 insertions(+), 0 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/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/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..4f6af26
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-dialog.ui
@@ -0,0 +1,196 @@
+<?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="visible">true</property>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="shadow-type">none</property>
+ <property name="visible">true</property>
+ <property name="width-request">250</property>
+ <child>
+ <object class="GtkListBox" id="signals_list_box">
+ <property name="visible">true</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="hexpand">true</property>
+ <property name="orientation">vertical</property>
+ <property name="visible">true</property>
+ <property name="border-width">36</property>
+ <child>
+ <object class="GbpGobjectSignalEditor" id="signal_editor">
+ <property name="visible">true</property>
+ </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..cbee9dc
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-property-editor.c
@@ -0,0 +1,273 @@
+/* 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;
+ 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, name_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, default_entry);
+ gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, readable_switch);
+ gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, writable_switch);
+ gtk_widget_class_bind_template_child (widget_class, GbpGobjectPropertyEditor, construct_only_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, "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..db08155
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-property-editor.ui
@@ -0,0 +1,720 @@
+<?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>
+
+ <!-- GType -->
+ <child>
+ <object class="GtkBox" id="gtype_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">GType</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="gtype_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="gtype_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="gtype_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="gtype_box">
+ <property name="visible">true</property>
+ </object>
+ <object id="gtype_entry">
+ <property name="placeholder-text">MY_TYPE_BOXED</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="gtype_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="gtype_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="gtype_box">
+ <property name="visible">true</property>
+ </object>
+ <object id="gtype_entry">
+ <property name="placeholder-text">MY_TYPE_ENUM</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="gtype_box">
+ <property name="visible">true</property>
+ </object>
+ <object id="gtype_entry">
+ <property name="placeholder-text">MY_TYPE_FLAGS</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="gtype_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="gtype_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="gtype_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="gtype_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="gtype_box">
+ <property name="visible">true</property>
+ </object>
+ <object id="gtype_entry">
+ <property name="placeholder-text">MY_TYPE_OBJECT</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="gtype_box">
+ <property name="visible">false</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="gtype_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="gtype_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="gtype_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="gtype_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="gtype_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="gtype_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..b3ba254
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-property.c
@@ -0,0 +1,406 @@
+/* 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 *name;
+ gchar *default_;
+ gchar *minimum;
+ gchar *maximum;
+
+ guint readable : 1;
+ guint writable : 1;
+ guint construct_only : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_CONSTRUCT_ONLY,
+ PROP_DEFAULT,
+ 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_finalize (GObject *object)
+{
+ GbpGobjectProperty *self = (GbpGobjectProperty *)object;
+
+ 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_CONSTRUCT_ONLY:
+ g_value_set_boolean (value, self->construct_only);
+ 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_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_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;
+}
+
+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..8f92f4c
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-spec.c
@@ -0,0 +1,500 @@
+/* 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_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_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_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..39fee60
--- /dev/null
+++ b/plugins/gobject-templates/gbp-gobject-template.c
@@ -0,0 +1,277 @@
+/* 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 void
+gbp_gobject_template_add_c_resources (GbpGobjectTemplate *self)
+{
+ g_autofree gchar *mangled = 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_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 = g_strdelimit (mangle_name (name), "_", '-');
+
+ namespace_upper = g_utf8_strup (mangled, -1);
+ class_name_upper = g_utf8_strup (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);
+ h_name = g_strdup_printf ("%s.h", mangled);
+
+ c_dest = g_file_get_child (self->directory, c_name);
+ h_dest = g_file_get_child (self->directory, h_name);
+
+ scope = tmpl_scope_new ();
+
+ 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, "class"), class_name_lower);
+ tmpl_symbol_assign_string (tmpl_scope_get (scope, "namespace"), namespace_lower);
+
+ 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..082e7aa
--- /dev/null
+++ b/plugins/gobject-templates/gobject.c.tmpl
@@ -0,0 +1,7 @@
+{{include "license.c"}}
+
+#define G_LOG_DOMAIN "{{file_prefix}}"
+
+#include "{{file_prefix}}.h"
+
+
diff --git a/plugins/gobject-templates/gobject.h.tmpl b/plugins/gobject-templates/gobject.h.tmpl
new file mode 100644
index 0000000..cdd3c1b
--- /dev/null
+++ b/plugins/gobject-templates/gobject.h.tmpl
@@ -0,0 +1,18 @@
+{{include "license.h"}}
+
+#ifndef {{FILE_PREFIX}}_H
+#define {{FILE_PREFIX}}_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define {{NAMESPACE}}_TYPE_{{CLASS}} ({{namespace}}_{{class}}_get_type())
+
+G_DECLARE_FINAL_TYPE ({{Name}}, {{namespace}}_{{class}}, {{NAMESPACE}}, {{CLASS}}, {{Parent}})
+
+{{Name}}* {{namespace}}_{{class}}_new (void);
+
+G_END_DECLS
+
+#endif /* {{FILE_PREFIX}}_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>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]