[gnome-builder/wip/chergert/perspective] build-tools: start on a build panel



commit 14b6ada38fdb540261d4f6b91d83fa0d341cfd60
Author: Christian Hergert <chergert redhat com>
Date:   Tue Dec 15 15:07:26 2015 -0800

    build-tools: start on a build panel

 configure.ac                                      |    2 +
 data/theme/shared.css                             |    7 +
 libide/ide-build-system.c                         |    7 +-
 libide/ide-builder.c                              |    5 +
 plugins/Makefile.am                               |    1 +
 plugins/build-tools/Makefile.am                   |   50 +++
 plugins/build-tools/build-tools.gresource.xml     |    5 +
 plugins/build-tools/build-tools.plugin            |    9 +
 plugins/build-tools/configure.ac                  |   12 +
 plugins/build-tools/gbp-build-panel-row.c         |  123 ++++++
 plugins/build-tools/gbp-build-panel-row.h         |   29 ++
 plugins/build-tools/gbp-build-panel-row.ui        |   22 +
 plugins/build-tools/gbp-build-panel.c             |  409 +++++++++++++++++++
 plugins/build-tools/gbp-build-panel.h             |   35 ++
 plugins/build-tools/gbp-build-panel.ui            |  356 +++++++++++++++++
 plugins/build-tools/gbp-build-plugin.c            |   30 ++
 plugins/build-tools/gbp-build-tools.gresource.xml |    9 +
 plugins/build-tools/gbp-build-workbench-addin.c   |  441 +++++++++++++++++++++
 plugins/build-tools/gbp-build-workbench-addin.h   |   29 ++
 plugins/build-tools/gtk/menus.ui                  |    2 +
 plugins/build-tools/theme/shared.css              |   19 +
 21 files changed, 1600 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3554bb8..ea23a33 100644
--- a/configure.ac
+++ b/configure.ac
@@ -228,6 +228,7 @@ dnl ***********************************************************************
 dnl Check for supported plugins
 dnl ***********************************************************************
 m4_include([plugins/autotools/configure.ac])
+m4_include([plugins/build-tools/configure.ac])
 m4_include([plugins/c-pack/configure.ac])
 m4_include([plugins/clang/configure.ac])
 m4_include([plugins/command-bar/configure.ac])
@@ -484,6 +485,7 @@ echo ""
 echo " Plugins"
 echo ""
 echo "  Autotools ............................ : ${enable_autotools_plugin}"
+echo "  Build Tools .......................... : ${enable_build_tools_plugin}"
 echo "  C Language Pack ...................... : ${enable_c_pack_plugin}"
 echo "  Clang ................................ : ${enable_clang_plugin}"
 echo "  Command Bar .......................... : ${enable_command_bar_plugin}"
diff --git a/data/theme/shared.css b/data/theme/shared.css
index 83b6b55..2f01e04 100644
--- a/data/theme/shared.css
+++ b/data/theme/shared.css
@@ -175,3 +175,10 @@ editortweak box.indentation button {
 editortweak box.indentation button label {
   padding: 2px 12px 2px 12px;
 }
+
+
+
+box.build-status {
+  background-image: none;
+  background-color: shade(@theme_bg_color, 1.05);
+}
diff --git a/libide/ide-build-system.c b/libide/ide-build-system.c
index fdd6747..90b7f33 100644
--- a/libide/ide-build-system.c
+++ b/libide/ide-build-system.c
@@ -196,7 +196,7 @@ ide_build_system_new_finish (GAsyncResult  *result,
 /**
  * ide_build_system_get_builder:
  * @system: The #IdeBuildSystem to perform the build.
- * @config: The configuration options for the build.
+ * @config: (nullable): The configuration options for the build.
  * @device: The #IdeDevice the result should be able to run on.
  *
  * This function should return an #IdeBuilder that can be used to perform a
@@ -213,11 +213,14 @@ ide_build_system_get_builder (IdeBuildSystem  *system,
 {
   IdeBuildSystemInterface *iface;
   IdeBuilder *ret = NULL;
+  g_autoptr(GKeyFile) local = NULL;
 
   g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (system), NULL);
-  g_return_val_if_fail (config, NULL);
   g_return_val_if_fail (IDE_IS_DEVICE (device), NULL);
 
+  if (config == NULL)
+    config = local = g_key_file_new ();
+
   iface = IDE_BUILD_SYSTEM_GET_IFACE (system);
 
   if (iface->get_builder)
diff --git a/libide/ide-builder.c b/libide/ide-builder.c
index 74536a1..520e177 100644
--- a/libide/ide-builder.c
+++ b/libide/ide-builder.c
@@ -23,6 +23,11 @@
 
 G_DEFINE_ABSTRACT_TYPE (IdeBuilder, ide_builder, IDE_TYPE_OBJECT)
 
+/**
+ * ide_builder_build_async:
+ * @result: (out) (transfer none): A location for an #IdeBuildResult.
+ *
+ */
 void
 ide_builder_build_async (IdeBuilder           *builder,
                          IdeBuilderBuildFlags  flags,
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index bda42fb..f729ada 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -1,5 +1,6 @@
 SUBDIRS = \
        autotools \
+       build-tools \
        clang \
        command-bar \
        c-pack \
diff --git a/plugins/build-tools/Makefile.am b/plugins/build-tools/Makefile.am
new file mode 100644
index 0000000..5fadfa7
--- /dev/null
+++ b/plugins/build-tools/Makefile.am
@@ -0,0 +1,50 @@
+if ENABLE_BUILD_TOOLS_PLUGIN
+
+DISTCLEANFILES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libbuild-tools-plugin.la
+dist_plugin_DATA = build-tools.plugin
+
+libbuild_tools_plugin_la_SOURCES = \
+       gbp-build-panel.c \
+       gbp-build-panel.h \
+       gbp-build-panel-row.c \
+       gbp-build-panel-row.h \
+       gbp-build-plugin.c \
+       gbp-build-workbench-addin.c \
+       gbp-build-workbench-addin.h \
+       $(NULL)
+
+nodist_libbuild_tools_plugin_la_SOURCES = \
+       gbp-build-resources.c \
+       gbp-build-resources.h
+
+libbuild_tools_plugin_la_CFLAGS = \
+       $(LIBIDE_CFLAGS) \
+       $(OPTIMIZE_CFLAGS) \
+       -I$(top_srcdir)/libide \
+       -I$(top_srcdir)/contrib/egg \
+       $(NULL)
+
+libbuild_tools_plugin_la_LDFLAGS = \
+       $(OPTIMIZE_LDFLAGS) \
+       -avoid-version \
+       -module \
+       -export-regex peas_register_types \
+       $(NULL)
+
+glib_resources_c = gbp-build-resources.c
+glib_resources_h = gbp-build-resources.h
+glib_resources_xml = gbp-build-tools.gresource.xml
+glib_resources_namespace = gbp_build_tools
+include $(top_srcdir)/build/autotools/Makefile.am.gresources
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/build-tools/build-tools.gresource.xml b/plugins/build-tools/build-tools.gresource.xml
new file mode 100644
index 0000000..2db9a35
--- /dev/null
+++ b/plugins/build-tools/build-tools.gresource.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/builder/plugins/build-tools-plugin">
+  </gresource>
+</gresources>
diff --git a/plugins/build-tools/build-tools.plugin b/plugins/build-tools/build-tools.plugin
new file mode 100644
index 0000000..4121e6e
--- /dev/null
+++ b/plugins/build-tools/build-tools.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=build-tools-plugin
+Name=Build Tools
+Description=Build panel and command line tools
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2015 Christian Hergert
+Depends=editor
+Hidden=true
+Builtin=true
diff --git a/plugins/build-tools/configure.ac b/plugins/build-tools/configure.ac
new file mode 100644
index 0000000..13bf717
--- /dev/null
+++ b/plugins/build-tools/configure.ac
@@ -0,0 +1,12 @@
+# --enable-build-tools-plugin=yes/no
+AC_ARG_ENABLE([build-tools-plugin],
+              [AS_HELP_STRING([--enable-build-tools-plugin=@<:@yes/no@:>@],
+                              [Build with support for build tools and panels.])],
+              [enable_build_tools_plugin=$enableval],
+              [enable_build_tools_plugin=yes])
+
+# for if ENABLE_BUILD_TOOLS_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_BUILD_TOOLS_PLUGIN, test x$enable_build_tools_plugin != xno)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/build-tools/Makefile])
diff --git a/plugins/build-tools/gbp-build-panel-row.c b/plugins/build-tools/gbp-build-panel-row.c
new file mode 100644
index 0000000..4fffa0c
--- /dev/null
+++ b/plugins/build-tools/gbp-build-panel-row.c
@@ -0,0 +1,123 @@
+/* gbp-build-panel-row.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ide.h>
+
+#include "gbp-build-panel-row.h"
+
+struct _GbpBuildPanelRow
+{
+  GtkListBoxRow  parent_instance;
+
+  GtkLabel      *file_label;
+  GtkLabel      *message_label;
+
+  IdeDiagnostic *diagnostic;
+};
+
+G_DEFINE_TYPE (GbpBuildPanelRow, gbp_build_panel_row, GTK_TYPE_LIST_BOX_ROW)
+
+enum {
+  PROP_0,
+  PROP_DIAGNOSTIC,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+gbp_build_panel_row_set_diagnostic (GbpBuildPanelRow *self,
+                                    IdeDiagnostic    *diagnostic)
+{
+  gchar *text;
+
+  g_return_if_fail (GBP_IS_BUILD_PANEL_ROW (self));
+  g_return_if_fail (diagnostic != NULL);
+
+  text = ide_diagnostic_get_text_for_display (diagnostic);
+  gtk_label_set_label (self->message_label, text);
+  g_free (text);
+}
+
+static void
+gbp_build_panel_row_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  GbpBuildPanelRow *self = GBP_BUILD_PANEL_ROW(object);
+
+  switch (prop_id)
+    {
+    case PROP_DIAGNOSTIC:
+      g_value_set_boxed (value, self->diagnostic);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_build_panel_row_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  GbpBuildPanelRow *self = GBP_BUILD_PANEL_ROW(object);
+
+  switch (prop_id)
+    {
+    case PROP_DIAGNOSTIC:
+      gbp_build_panel_row_set_diagnostic (self, g_value_get_boxed (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_build_panel_row_class_init (GbpBuildPanelRowClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = gbp_build_panel_row_get_property;
+  object_class->set_property = gbp_build_panel_row_set_property;
+
+  properties [PROP_DIAGNOSTIC] =
+    g_param_spec_boxed ("diagnostic",
+                        "Diagnostic",
+                        "Diagnostic",
+                        IDE_TYPE_DIAGNOSTIC,
+                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/build-tools-plugin/gbp-build-panel-row.ui");
+  gtk_widget_class_set_css_name (widget_class, "buildpanelrow");
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanelRow, file_label);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanelRow, message_label);
+}
+
+static void
+gbp_build_panel_row_init (GbpBuildPanelRow *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/plugins/build-tools/gbp-build-panel-row.h b/plugins/build-tools/gbp-build-panel-row.h
new file mode 100644
index 0000000..afd2d9b
--- /dev/null
+++ b/plugins/build-tools/gbp-build-panel-row.h
@@ -0,0 +1,29 @@
+/* gbp-build-panel-row.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_BUILD_PANEL_ROW (gbp_build_panel_row_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpBuildPanelRow, gbp_build_panel_row, GBP, BUILD_PANEL_ROW, GObject)
+
+G_END_DECLS
diff --git a/plugins/build-tools/gbp-build-panel-row.ui b/plugins/build-tools/gbp-build-panel-row.ui
new file mode 100644
index 0000000..5079030
--- /dev/null
+++ b/plugins/build-tools/gbp-build-panel-row.ui
@@ -0,0 +1,22 @@
+<interface>
+  <template class="GbpBuildPanelRow" parent="GtkListBoxRow">
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">vertical</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkLabel" id="file_label">
+            <property name="visible">true</property>
+            <property name="xalign">0.0</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkLabel" id="message_label">
+            <property name="visible">true</property>
+            <property name="xalign">0.0</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/plugins/build-tools/gbp-build-panel.c b/plugins/build-tools/gbp-build-panel.c
new file mode 100644
index 0000000..b9e689f
--- /dev/null
+++ b/plugins/build-tools/gbp-build-panel.c
@@ -0,0 +1,409 @@
+/* gbp-build-panel.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "egg-binding-group.h"
+#include "egg-signal-group.h"
+
+#include "gbp-build-panel.h"
+#include "gbp-build-panel-row.h"
+
+struct _GbpBuildPanel
+{
+  GtkBin            parent_instance;
+
+  IdeBuildResult   *result;
+  EggSignalGroup   *signals;
+  EggBindingGroup  *bindings;
+
+  IdeDevice        *device;
+
+  GtkListBox       *diagnostics;
+  GtkRevealer      *status_revealer;
+  GtkLabel         *status_label;
+  GtkLabel         *running_time_label;
+  GtkMenuButton    *device_button;
+  GtkLabel         *device_label;
+  GtkListBox       *devices;
+  GtkPopover       *device_popover;
+
+  guint             running_time_source;
+};
+
+G_DEFINE_TYPE (GbpBuildPanel, gbp_build_panel, GTK_TYPE_BIN)
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  PROP_DEVICE_MANAGER,
+  PROP_RESULT,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static GtkWidget *
+create_device_row (gpointer item,
+                   gpointer user_data)
+{
+  GtkListBoxRow *row;
+  IdeDevice *device = item;
+  const gchar *type;
+  const gchar *name;
+  GtkLabel *label;
+  gchar *str;
+
+  g_assert (IDE_IS_DEVICE (device));
+
+  row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+                      "visible", TRUE,
+                      NULL);
+
+  g_object_set_data_full (G_OBJECT (row),
+                          "IDE_DEVICE_ID",
+                          g_strdup (ide_device_get_id (device)),
+                          g_free);
+
+  name = ide_device_get_display_name (device);
+  type = ide_device_get_system_type (device);
+  str = g_strdup_printf ("%s (%s)", name, type);
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "label", str,
+                        "xalign", 0.0f,
+                        "visible", TRUE,
+                        NULL);
+  gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (label));
+
+  g_free (str);
+
+  return GTK_WIDGET (row);
+}
+
+static void
+gbp_build_panel_set_device (GbpBuildPanel *self,
+                            IdeDevice     *device)
+{
+  g_return_if_fail (GBP_IS_BUILD_PANEL (self));
+  g_return_if_fail (!device || IDE_IS_DEVICE (device));
+
+  if (g_set_object (&self->device, device))
+    {
+      const gchar *name = NULL;
+
+      if (device != NULL)
+        name = ide_device_get_display_name (device);
+      gtk_label_set_label (self->device_label, name);
+    }
+}
+
+static void
+gbp_build_panel_set_device_manager (GbpBuildPanel    *self,
+                                    IdeDeviceManager *device_manager)
+{
+  g_return_if_fail (GBP_IS_BUILD_PANEL (self));
+  g_return_if_fail (!device_manager || IDE_IS_DEVICE_MANAGER (device_manager));
+
+  gtk_list_box_bind_model (self->devices,
+                           G_LIST_MODEL (device_manager),
+                           create_device_row, NULL, NULL);
+}
+
+void
+gbp_build_panel_add_error (GbpBuildPanel *self,
+                           const gchar   *message)
+{
+  g_return_if_fail (GBP_IS_BUILD_PANEL (self));
+  g_return_if_fail (message != NULL);
+
+}
+
+static void
+gbp_build_panel_diagnostic (GbpBuildPanel  *self,
+                            IdeDiagnostic  *diagnostic,
+                            IdeBuildResult *result)
+{
+  GtkWidget *row;
+
+  g_assert (GBP_IS_BUILD_PANEL (self));
+  g_assert (diagnostic != NULL);
+  g_assert (IDE_IS_BUILD_RESULT (result));
+
+  row = g_object_new (GBP_TYPE_BUILD_PANEL_ROW,
+                      "diagnostic", diagnostic,
+                      "visible", TRUE,
+                      NULL);
+  gtk_container_add (GTK_CONTAINER (self->diagnostics), row);
+}
+
+static gboolean
+gbp_build_panel_update_running_time (GbpBuildPanel *self)
+{
+  g_assert (GBP_IS_BUILD_PANEL (self));
+
+  if (self->result != NULL)
+    {
+      GTimeSpan span;
+      guint hours;
+      guint minutes;
+      guint seconds;
+      gchar *text;
+
+      span = ide_build_result_get_running_time (self->result);
+
+      hours = span / G_TIME_SPAN_HOUR;
+      minutes = (span % G_TIME_SPAN_HOUR) / G_TIME_SPAN_MINUTE;
+      seconds = (span % G_TIME_SPAN_MINUTE) / G_TIME_SPAN_SECOND;
+
+      text = g_strdup_printf ("%02u:%02u:%02u", hours, minutes, seconds);
+      gtk_label_set_label (self->running_time_label, text);
+      g_free (text);
+    }
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+gbp_build_panel_connect (GbpBuildPanel  *self,
+                         IdeBuildResult *result)
+{
+  g_return_if_fail (GBP_IS_BUILD_PANEL (self));
+  g_return_if_fail (IDE_IS_BUILD_RESULT (result));
+  g_return_if_fail (self->result == NULL);
+
+  self->result = g_object_ref (result);
+
+  egg_signal_group_set_target (self->signals, result);
+  egg_binding_group_set_source (self->bindings, result);
+
+  if (ide_build_result_get_running (result))
+    {
+      gtk_label_set_label (self->running_time_label, NULL);
+      self->running_time_source =
+        g_timeout_add_seconds (1, (GSourceFunc)gbp_build_panel_update_running_time, self);
+    }
+
+  gtk_revealer_set_reveal_child (self->status_revealer, TRUE);
+}
+
+static void
+gbp_build_panel_disconnect (GbpBuildPanel *self)
+{
+  g_return_if_fail (GBP_IS_BUILD_PANEL (self));
+
+  gtk_revealer_set_reveal_child (self->status_revealer, FALSE);
+
+  egg_signal_group_set_target (self->signals, NULL);
+  egg_binding_group_set_source (self->bindings, NULL);
+  ide_clear_source (&self->running_time_source);
+  g_clear_object (&self->result);
+}
+
+void
+gbp_build_panel_set_result (GbpBuildPanel  *self,
+                            IdeBuildResult *result)
+{
+  g_return_if_fail (GBP_IS_BUILD_PANEL (self));
+  g_return_if_fail (!result || IDE_IS_BUILD_RESULT (result));
+
+  if (result != self->result)
+    {
+      if (self->result)
+        gbp_build_panel_disconnect (self);
+
+      if (result)
+        gbp_build_panel_connect (self, result);
+    }
+}
+
+static void
+gbp_build_panel_notify_running (GbpBuildPanel  *self,
+                                GParamSpec     *pspec,
+                                IdeBuildResult *result)
+{
+  g_assert (GBP_IS_BUILD_PANEL (self));
+  g_assert (IDE_IS_BUILD_RESULT (result));
+
+  if (!ide_build_result_get_running (result))
+    ide_clear_source (&self->running_time_source);
+
+  gbp_build_panel_update_running_time (self);
+}
+
+static void
+gbp_build_panel_device_activated (GbpBuildPanel *self,
+                                  GtkListBoxRow *row,
+                                  GtkListBox    *list_box)
+{
+  const gchar *id;
+
+  g_assert (GBP_IS_BUILD_PANEL (self));
+  g_assert (GTK_IS_LIST_BOX_ROW (row));
+  g_assert (GTK_IS_LIST_BOX (list_box));
+
+  if ((id = g_object_get_data (G_OBJECT (row), "IDE_DEVICE_ID")))
+    ide_widget_action (GTK_WIDGET (self),
+                       "build-tools", "device",
+                       g_variant_new_string (id));
+
+  gtk_widget_hide (GTK_WIDGET (self->device_popover));
+}
+
+static void
+gbp_build_panel_destroy (GtkWidget *widget)
+{
+  GbpBuildPanel *self = (GbpBuildPanel *)widget;
+
+  if (self->result)
+    gbp_build_panel_disconnect (self);
+
+  g_clear_object (&self->bindings);
+  g_clear_object (&self->signals);
+  g_clear_object (&self->device);
+
+  GTK_WIDGET_CLASS (gbp_build_panel_parent_class)->destroy (widget);
+}
+
+static void
+gbp_build_panel_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  GbpBuildPanel *self = GBP_BUILD_PANEL(object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      g_value_set_object (value, self->device);
+      break;
+
+    case PROP_RESULT:
+      g_value_set_object (value, self->result);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_build_panel_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GbpBuildPanel *self = GBP_BUILD_PANEL(object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      gbp_build_panel_set_device (self, g_value_get_object (value));
+      break;
+
+    case PROP_DEVICE_MANAGER:
+      gbp_build_panel_set_device_manager (self, g_value_get_object (value));
+      break;
+
+    case PROP_RESULT:
+      gbp_build_panel_set_result (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_build_panel_class_init (GbpBuildPanelClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->get_property = gbp_build_panel_get_property;
+  object_class->set_property = gbp_build_panel_set_property;
+
+  widget_class->destroy = gbp_build_panel_destroy;
+
+  properties [PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "Device",
+                         IDE_TYPE_DEVICE,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DEVICE_MANAGER] =
+    g_param_spec_object ("device-manager",
+                         "Device Manager",
+                         "Device Manager",
+                         IDE_TYPE_DEVICE_MANAGER,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RESULT] =
+    g_param_spec_object ("result",
+                         "Result",
+                         "Result",
+                         IDE_TYPE_BUILD_RESULT,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/build-tools-plugin/gbp-build-panel.ui");
+  gtk_widget_class_set_css_name (widget_class, "buildpanel");
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, device_button);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, device_label);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, device_popover);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, devices);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, diagnostics);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, running_time_label);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, status_label);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, status_revealer);
+}
+
+static void
+gbp_build_panel_init (GbpBuildPanel *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  self->signals = egg_signal_group_new (IDE_TYPE_BUILD_RESULT);
+
+  egg_signal_group_connect_object (self->signals,
+                                   "diagnostic",
+                                   G_CALLBACK (gbp_build_panel_diagnostic),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  egg_signal_group_connect_object (self->signals,
+                                   "notify::running",
+                                   G_CALLBACK (gbp_build_panel_notify_running),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->devices,
+                           "row-activated",
+                           G_CALLBACK (gbp_build_panel_device_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  self->bindings = egg_binding_group_new ();
+
+  egg_binding_group_bind (self->bindings, "mode",
+                          self->status_label, "label",
+                          G_BINDING_SYNC_CREATE);
+}
diff --git a/plugins/build-tools/gbp-build-panel.h b/plugins/build-tools/gbp-build-panel.h
new file mode 100644
index 0000000..e7a9b5f
--- /dev/null
+++ b/plugins/build-tools/gbp-build-panel.h
@@ -0,0 +1,35 @@
+/* gbp-build-panel.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_BUILD_PANEL (gbp_build_panel_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpBuildPanel, gbp_build_panel, GBP, BUILD_PANEL, GtkBin)
+
+void gbp_build_panel_set_result (GbpBuildPanel  *self,
+                                 IdeBuildResult *result);
+void gbp_build_panel_add_error  (GbpBuildPanel  *self,
+                                 const gchar    *message);
+
+G_END_DECLS
diff --git a/plugins/build-tools/gbp-build-panel.ui b/plugins/build-tools/gbp-build-panel.ui
new file mode 100644
index 0000000..e928112
--- /dev/null
+++ b/plugins/build-tools/gbp-build-panel.ui
@@ -0,0 +1,356 @@
+<interface>
+  <template class="GbpBuildPanel" parent="GtkBin">
+    <child>
+      <object class="GtkBox" id="toplevel">
+        <property name="visible">true</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkGrid">
+            <property name="border-width">12</property>
+            <property name="column-spacing">12</property>
+            <property name="column-homogeneous">true</property>
+            <property name="row-spacing">6</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkLabel" id="label1">
+                <property name="label" translatable="yes">Device:</property>
+                <property name="xalign">1.0</property>
+                <property name="visible">true</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label2">
+                <property name="label" translatable="yes">Framework:</property>
+                <property name="xalign">1.0</property>
+                <property name="visible">false</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label3">
+                <property name="label" translatable="yes">Packaging:</property>
+                <property name="xalign">1.0</property>
+                <property name="visible">false</property>
+                <attributes>
+                  <attribute name="weight" value="bold"/>
+                </attributes>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox">
+                <property name="visible">true</property>
+                <property name="margin-top">6</property>
+                <property name="halign">end</property>
+                <property name="hexpand">true</property>
+                <property name="spacing">6</property>
+                <property name="homogeneous">true</property>
+                <child>
+                  <object class="GtkButton">
+                    <property name="action-name">build-tools.build</property>
+                    <property name="label" translatable="yes">_Build</property>
+                    <property name="use-underline">true</property>
+                    <property name="visible">true</property>
+                    <style>
+                      <class name="suggested-action"/>
+                    </style>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="action-name">build-tools.rebuild</property>
+                    <property name="label" translatable="yes">_Rebuild</property>
+                    <property name="use-underline">true</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton">
+                    <property name="action-name">build-tools.clean</property>
+                    <property name="label" translatable="yes">_Clean</property>
+                    <property name="use-underline">true</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left-attach">0</property>
+                <property name="top-attach">3</property>
+                <property name="width">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkMenuButton" id="device_button">
+                <property name="focus-on-click">false</property>
+                <property name="popover">device_popover</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="flat"/>
+                </style>
+                <child>
+                  <object class="GtkBox">
+                    <property name="spacing">6</property>
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkLabel" id="device_label">
+                        <property name="visible">true</property>
+                        <property name="hexpand">false</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="icon-name">pan-down-symbolic</property>
+                        <property name="visible">true</property>
+                        <property name="hexpand">false</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="left-attach">1</property>
+                <property name="top-attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkMenuButton">
+                <property name="label" translatable="yes">framework</property>
+                <property name="visible">false</property>
+              </object>
+              <packing>
+                <property name="left-attach">1</property>
+                <property name="top-attach">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkMenuButton">
+                <property name="label" translatable="yes">packaging</property>
+                <property name="visible">false</property>
+              </object>
+              <packing>
+                <property name="left-attach">1</property>
+                <property name="top-attach">2</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkSeparator">
+            <property name="orientation">horizontal</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkRevealer" id="status_revealer">
+            <property name="transition-type">slide-down</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkBox">
+                <property name="orientation">vertical</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="build-status"/>
+                </style>
+                <child>
+                  <object class="GtkGrid">
+                    <property name="border-width">12</property>
+                    <property name="column-spacing">12</property>
+                    <property name="column-homogeneous">true</property>
+                    <property name="row-spacing">6</property>
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkLabel" id="label4">
+                        <property name="label" translatable="yes">Status:</property>
+                        <property name="xalign">1.0</property>
+                        <property name="visible">true</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="status_label">
+                        <property name="xalign">0.0</property>
+                        <property name="visible">true</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label5">
+                        <property name="label" translatable="yes">Running Time:</property>
+                        <property name="xalign">1.0</property>
+                        <property name="visible">true</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="running_time_label">
+                        <property name="xalign">0.0</property>
+                        <property name="visible">true</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="label6">
+                        <property name="label" translatable="yes">Warnings:</property>
+                        <property name="xalign">1.0</property>
+                        <property name="visible">true</property>
+                        <attributes>
+                          <attribute name="weight" value="bold"/>
+                        </attributes>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="warnings_label">
+                        <property name="xalign">0.0</property>
+                        <property name="visible">true</property>
+                      </object>
+                      <packing>
+                        <property name="left-attach">1</property>
+                        <property name="top-attach">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="visible">true</property>
+                        <property name="margin-top">6</property>
+                        <property name="hexpand">true</property>
+                        <property name="halign">end</property>
+                        <property name="spacing">6</property>
+                        <property name="homogeneous">true</property>
+                        <child>
+                          <object class="GtkButton">
+                            <property name="action-name">build-tools.cancel</property>
+                            <property name="label" translatable="yes">Cancel</property>
+                            <property name="use-underline">true</property>
+                            <property name="visible">true</property>
+                            <style>
+                              <class name="destructive-action"/>
+                            </style>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton">
+                            <property name="action-name">build-tools.deploy</property>
+                            <property name="label" translatable="yes">Deploy</property>
+                            <property name="use-underline">true</property>
+                            <property name="visible">false</property>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton">
+                            <property name="action-name">build-tools.export</property>
+                            <property name="label" translatable="yes">Export</property>
+                            <property name="use-underline">true</property>
+                            <property name="visible">false</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="left-attach">0</property>
+                        <property name="top-attach">3</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkSeparator">
+                    <property name="orientation">horizontal</property>
+                    <property name="visible">true</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow">
+            <property name="vexpand">true</property>
+            <property name="visible">true</property>
+            <child>
+              <object class="GtkListBox" id="diagnostics">
+                <property name="visible">true</property>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkPopover" id="device_popover">
+    <child>
+      <object class="EggScrolledWindow">
+        <property name="min-content-width">100</property>
+        <property name="max-content-width">300</property>
+        <property name="max-content-height">300</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkListBox" id="devices">
+            <property name="selection-mode">none</property>
+            <property name="visible">true</property>
+            <style>
+              <class name="buildpanel"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/plugins/build-tools/gbp-build-plugin.c b/plugins/build-tools/gbp-build-plugin.c
new file mode 100644
index 0000000..e030fc2
--- /dev/null
+++ b/plugins/build-tools/gbp-build-plugin.c
@@ -0,0 +1,30 @@
+/* gbp-build-plugin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libpeas/peas.h>
+#include <ide.h>
+
+#include "gbp-build-workbench-addin.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKBENCH_ADDIN,
+                                              GBP_TYPE_BUILD_WORKBENCH_ADDIN);
+}
diff --git a/plugins/build-tools/gbp-build-tools.gresource.xml 
b/plugins/build-tools/gbp-build-tools.gresource.xml
new file mode 100644
index 0000000..6c6a860
--- /dev/null
+++ b/plugins/build-tools/gbp-build-tools.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/builder/plugins/build-tools-plugin">
+    <file>gtk/menus.ui</file>
+    <file>theme/shared.css</file>
+    <file>gbp-build-panel.ui</file>
+    <file>gbp-build-panel-row.ui</file>
+  </gresource>
+</gresources>
diff --git a/plugins/build-tools/gbp-build-workbench-addin.c b/plugins/build-tools/gbp-build-workbench-addin.c
new file mode 100644
index 0000000..cbca0bb
--- /dev/null
+++ b/plugins/build-tools/gbp-build-workbench-addin.c
@@ -0,0 +1,441 @@
+/* gbp-build-workbench-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "egg-binding-group.h"
+
+#include "gbp-build-panel.h"
+#include "gbp-build-workbench-addin.h"
+
+struct _GbpBuildWorkbenchAddin
+{
+  GObject             parent_instance;
+
+  /* Unowned */
+  GbpBuildPanel      *panel;
+  IdeWorkbench       *workbench;
+
+  /* Owned */
+  EggBindingGroup    *bindings;
+  IdeBuildResult     *result;
+  GSimpleActionGroup *actions;
+  GCancellable       *cancellable;
+  IdeDevice          *device;
+};
+
+static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GbpBuildWorkbenchAddin, gbp_build_workbench_addin, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN,
+                                               workbench_addin_iface_init))
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  PROP_RESULT,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+gbp_build_workbench_addin_set_device (GbpBuildWorkbenchAddin *self,
+                                      IdeDevice              *device)
+{
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_DEVICE (device));
+
+  if (g_set_object (&self->device, device))
+    {
+      const gchar *id = ide_device_get_id (device);
+      GAction *action;
+
+      action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "device");
+      g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (id));
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE]);
+    }
+}
+
+static void
+gbp_build_workbench_addin_set_result (GbpBuildWorkbenchAddin *self,
+                                      IdeBuildResult         *result)
+{
+  g_return_if_fail (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+  g_return_if_fail (!result || IDE_IS_BUILD_RESULT (result));
+
+  if (g_set_object (&self->result, result))
+    {
+      egg_binding_group_set_source (self->bindings, result);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RESULT]);
+    }
+}
+
+static void
+gbp_build_workbench_addin_build_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  IdeBuilder *builder = (IdeBuilder *)object;
+  g_autoptr(GbpBuildWorkbenchAddin) self = user_data;
+  g_autoptr(IdeBuildResult) build_result = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_BUILDER (builder));
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+
+  build_result = ide_builder_build_finish (builder, result, &error);
+
+  if (error != NULL)
+    g_warning ("%s", error->message);
+}
+
+static void
+gbp_build_workbench_addin_do_build (GbpBuildWorkbenchAddin *self,
+                                    IdeBuilderBuildFlags    flags)
+{
+  g_autoptr(IdeBuildResult) build_result = NULL;
+  g_autoptr(IdeBuilder) builder = NULL;
+  g_autoptr(GError) error = NULL;
+  IdeBuildSystem *build_system;
+  IdeWorkbench *workbench;
+  IdeContext *context;
+
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+
+  gbp_build_workbench_addin_set_result (self, NULL);
+
+  workbench = ide_widget_get_workbench (GTK_WIDGET (self->panel));
+  context = ide_workbench_get_context (workbench);
+  build_system = ide_context_get_build_system (context);
+  builder = ide_build_system_get_builder (build_system, NULL, self->device, &error);
+
+  if (error != NULL)
+    {
+      gbp_build_panel_add_error (self->panel, error->message);
+      return;
+    }
+
+  g_clear_object (&self->cancellable);
+  self->cancellable = g_cancellable_new ();
+
+  ide_builder_build_async (builder,
+                           flags,
+                           &build_result,
+                           self->cancellable,
+                           gbp_build_workbench_addin_build_cb,
+                           g_object_ref (self));
+
+  gbp_build_workbench_addin_set_result (self, build_result);
+}
+
+static void
+gbp_build_workbench_addin_build (GSimpleAction *action,
+                                 GVariant      *param,
+                                 gpointer       user_data)
+{
+  GbpBuildWorkbenchAddin *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+
+  gbp_build_workbench_addin_do_build (self, 0);
+}
+
+static void
+gbp_build_workbench_addin_rebuild (GSimpleAction *action,
+                                   GVariant      *param,
+                                   gpointer       user_data)
+{
+  GbpBuildWorkbenchAddin *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+
+  gbp_build_workbench_addin_do_build (self, IDE_BUILDER_BUILD_FLAGS_FORCE_REBUILD);
+}
+
+static void
+gbp_build_workbench_addin_clean (GSimpleAction *action,
+                                 GVariant      *param,
+                                 gpointer       user_data)
+{
+  GbpBuildWorkbenchAddin *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+
+  gbp_build_workbench_addin_do_build (self, IDE_BUILDER_BUILD_FLAGS_CLEAN);
+}
+
+static void
+gbp_build_workbench_addin_cancel (GSimpleAction *action,
+                                  GVariant      *param,
+                                  gpointer       user_data)
+{
+  GbpBuildWorkbenchAddin *self = user_data;
+
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+
+  if (self->cancellable)
+    g_cancellable_cancel (self->cancellable);
+}
+
+static void
+gbp_build_workbench_addin_deploy (GSimpleAction *action,
+                                  GVariant      *param,
+                                  gpointer       user_data)
+{
+}
+
+static void
+gbp_build_workbench_addin_export (GSimpleAction *action,
+                                  GVariant      *param,
+                                  gpointer       user_data)
+{
+}
+
+static void
+gbp_build_workbench_addin_device (GSimpleAction *action,
+                                  GVariant      *param,
+                                  gpointer       user_data)
+{
+  GbpBuildWorkbenchAddin *self = user_data;
+  IdeDeviceManager *device_manager;
+  IdeContext *context;
+  IdeDevice *device;
+  const gchar *id;
+
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (self->workbench));
+
+  id = g_variant_get_string (param, NULL);
+  if (id == NULL)
+    id = "local";
+
+  context = ide_workbench_get_context (self->workbench);
+  device_manager = ide_context_get_device_manager (context);
+  device = ide_device_manager_get_device (device_manager, id);
+
+  if (device == NULL)
+    device = ide_device_manager_get_device (device_manager, "local");
+
+  gbp_build_workbench_addin_set_device (self, device);
+}
+
+static const GActionEntry actions[] = {
+  { "build", gbp_build_workbench_addin_build },
+  { "rebuild", gbp_build_workbench_addin_rebuild },
+  { "clean", gbp_build_workbench_addin_clean },
+  { "cancel", gbp_build_workbench_addin_cancel },
+  { "deploy", gbp_build_workbench_addin_deploy },
+  { "export", gbp_build_workbench_addin_export },
+  { "device", NULL, "s", "'local'", gbp_build_workbench_addin_device },
+};
+
+static void
+gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
+                                IdeWorkbench      *workbench)
+{
+  GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)addin;
+  IdePerspective *editor;
+  GtkWidget *pane;
+  IdeContext *context;
+  IdeDeviceManager *device_manager;
+  IdeDevice *device;
+
+  g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  self->workbench = workbench;
+
+  context = ide_workbench_get_context (workbench);
+  device_manager = ide_context_get_device_manager (context);
+  device = ide_device_manager_get_device (device_manager, "local");
+
+  editor = ide_workbench_get_perspective_by_name (workbench, "editor");
+  pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
+  self->panel = g_object_new (GBP_TYPE_BUILD_PANEL,
+                              "device", device,
+                              "device-manager", device_manager,
+                              "visible", TRUE,
+                              NULL);
+  ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane),
+                            GTK_WIDGET (self->panel),
+                            _("Build"), NULL);
+
+  gtk_widget_insert_action_group (GTK_WIDGET (workbench), "build-tools",
+                                  G_ACTION_GROUP (self->actions));
+
+  g_object_bind_property (self, "result", self->panel, "result", 0);
+  g_object_bind_property (self, "device", self->panel, "device", 0);
+
+  gbp_build_workbench_addin_set_device (self, device);
+}
+
+static void
+gbp_build_workbench_addin_unload (IdeWorkbenchAddin *addin,
+                                  IdeWorkbench      *workbench)
+{
+  GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)addin;
+  IdePerspective *editor;
+  GtkWidget *pane;
+
+  g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  if (self->cancellable)
+    g_cancellable_cancel (self->cancellable);
+
+  g_clear_object (&self->cancellable);
+
+  gtk_widget_insert_action_group (GTK_WIDGET (workbench), "build-tools", NULL);
+
+  editor = ide_workbench_get_perspective_by_name (workbench, "editor");
+  pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
+  ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel));
+}
+
+static void
+gbp_build_workbench_addin_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+  GbpBuildWorkbenchAddin *self = GBP_BUILD_WORKBENCH_ADDIN(object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      g_value_set_object (value, self->device);
+      break;
+
+    case PROP_RESULT:
+      g_value_set_object (value, self->result);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_build_workbench_addin_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+  GbpBuildWorkbenchAddin *self = GBP_BUILD_WORKBENCH_ADDIN(object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      gbp_build_workbench_addin_set_device (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_build_workbench_addin_finalize (GObject *object)
+{
+  GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)object;
+
+  g_clear_object (&self->device);
+  g_clear_object (&self->bindings);
+  g_clear_object (&self->actions);
+  g_clear_object (&self->result);
+  g_clear_object (&self->cancellable);
+
+  G_OBJECT_CLASS (gbp_build_workbench_addin_parent_class)->finalize (object);
+}
+
+static void
+gbp_build_workbench_addin_class_init (GbpBuildWorkbenchAddinClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_build_workbench_addin_finalize;
+  object_class->get_property = gbp_build_workbench_addin_get_property;
+  object_class->set_property = gbp_build_workbench_addin_set_property;
+
+  properties [PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "The device the build is for",
+                         IDE_TYPE_DEVICE,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RESULT] =
+    g_param_spec_object ("result",
+                         "Result",
+                         "The current build result",
+                         IDE_TYPE_BUILD_RESULT,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gbp_build_workbench_addin_init (GbpBuildWorkbenchAddin *self)
+{
+  gint i;
+  static const struct {
+    const gchar   *property;
+    const gchar   *action;
+    GBindingFlags  flags;
+  } bindings[] = {
+    { "running", "build", G_BINDING_INVERT_BOOLEAN },
+    { "running", "rebuild", G_BINDING_INVERT_BOOLEAN },
+    { "running", "clean", G_BINDING_INVERT_BOOLEAN },
+    { "running", "cancel", 0 },
+    { "running", "deploy", G_BINDING_INVERT_BOOLEAN },
+    { "running", "export", G_BINDING_INVERT_BOOLEAN },
+    { NULL }
+  };
+
+  self->actions = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
+                                   actions, G_N_ELEMENTS (actions),
+                                   self);
+
+  self->bindings = egg_binding_group_new ();
+
+  for (i = 0; bindings [i].property; i++)
+    {
+      GActionMap *map = G_ACTION_MAP (self->actions);
+      GAction *action;
+
+      action = g_action_map_lookup_action (map, bindings [i].action);
+      egg_binding_group_bind (self->bindings, bindings [i].property,
+                              action, "enabled",
+                              G_BINDING_SYNC_CREATE | bindings [i].flags);
+    }
+}
+
+static void
+workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
+{
+  iface->load = gbp_build_workbench_addin_load;
+  iface->unload = gbp_build_workbench_addin_unload;
+}
diff --git a/plugins/build-tools/gbp-build-workbench-addin.h b/plugins/build-tools/gbp-build-workbench-addin.h
new file mode 100644
index 0000000..3b3d748
--- /dev/null
+++ b/plugins/build-tools/gbp-build-workbench-addin.h
@@ -0,0 +1,29 @@
+/* gbp-build-workbench-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_BUILD_WORKBENCH_ADDIN (gbp_build_workbench_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpBuildWorkbenchAddin, gbp_build_workbench_addin, GBP, BUILD_WORKBENCH_ADDIN, GObject)
+
+G_END_DECLS
diff --git a/plugins/build-tools/gtk/menus.ui b/plugins/build-tools/gtk/menus.ui
new file mode 100644
index 0000000..cb73794
--- /dev/null
+++ b/plugins/build-tools/gtk/menus.ui
@@ -0,0 +1,2 @@
+<interface>
+</interface>
diff --git a/plugins/build-tools/theme/shared.css b/plugins/build-tools/theme/shared.css
new file mode 100644
index 0000000..272cdb0
--- /dev/null
+++ b/plugins/build-tools/theme/shared.css
@@ -0,0 +1,19 @@
+buildpanel button.popup:active,
+buildpanel button.popup:checked,
+buildpanel button.popup {
+  background-image: none;
+  background-color: transparent;
+  box-shadow: none;
+  border-style: none;
+  margin: 0;
+  padding: 0;
+}
+
+list.buildpanel row {
+  padding: 10px;
+  border-bottom: 1px solid @borders;
+}
+
+list.buildpanel row:last-child {
+  border-bottom: none;
+}


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