[gnome-builder/wip/chergert/perspective] libide: start porting the editor to libide
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/perspective] libide: start porting the editor to libide
- Date: Mon, 16 Nov 2015 10:45:06 +0000 (UTC)
commit fee07257d61cafa0abad0216474745762c975924
Author: Christian Hergert <chergert redhat com>
Date: Mon Nov 16 02:32:13 2015 -0800
libide: start porting the editor to libide
Ideally, this will become an internal plugin so we don't need to
1) move it to plugins/
2) create interfaces for everything
However, maybe we want to do #2 anyway, but that could turn out to be
a lot of work and reduce flexability of plugins.
contrib/egg/Makefile.am | 2 +
contrib/egg/egg-simple-popover.c | 415 +++++++++
contrib/egg/egg-simple-popover.h | 90 ++
.../egg/egg-simple-popover.ui | 2 +-
contrib/egg/egg.gresource.xml | 1 +
data/ui/gb-editor-settings-widget.ui | 292 ------
data/ui/gb-greeter-project-row.ui | 96 --
data/ui/gb-greeter-window.ui | 302 -------
data/ui/gb-preferences-page-editor.ui | 295 -------
data/ui/gb-preferences-page-git.ui | 88 --
data/ui/gb-preferences-page-insight.ui | 99 ---
data/ui/gb-preferences-page-keybindings.ui | 101 ---
data/ui/gb-preferences-page-language.ui | 84 --
data/ui/gb-preferences-page-plugins.ui | 36 -
data/ui/gb-preferences-page-theme.ui | 61 --
data/ui/gb-preferences-switch.ui | 71 --
data/ui/gb-preferences-window.ui | 198 -----
data/ui/gb-recent-project-row.ui | 81 --
data/ui/gb-view-stack.ui | 262 ------
data/ui/gb-view.ui | 71 --
data/ui/gb-workbench.ui | 165 ----
data/ui/gb-workspace-pane.ui | 42 -
data/ui/gb-workspace.ui | 30 -
.../ui/{gb-editor-frame.ui => ide-editor-frame.ui} | 4 +-
...-tweak-widget.ui => ide-editor-tweak-widget.ui} | 2 +-
data/ui/{gb-editor-view.ui => ide-editor-view.ui} | 8 +-
libide/Makefile.am | 25 +
libide/editor/ide-editor-frame-actions.c | 117 +++
libide/editor/ide-editor-frame-actions.h | 30 +
libide/editor/ide-editor-frame-private.h | 58 ++
libide/editor/ide-editor-frame.c | 926 +++++++++++++++++++
libide/editor/ide-editor-frame.h | 38 +
libide/editor/ide-editor-map-bin.c | 245 +++++
libide/editor/ide-editor-map-bin.h | 32 +
libide/editor/ide-editor-print-operation.c | 207 +++++
libide/editor/ide-editor-print-operation.h | 34 +
libide/editor/ide-editor-tweak-widget.c | 184 ++++
libide/editor/ide-editor-tweak-widget.h | 33 +
libide/editor/ide-editor-view-actions.c | 824 +++++++++++++++++
libide/editor/ide-editor-view-actions.h | 31 +
libide/editor/ide-editor-view-addin-private.h | 34 +
libide/editor/ide-editor-view-addin.c | 64 ++
libide/editor/ide-editor-view-addin.h | 44 +
libide/editor/ide-editor-view-private.h | 60 ++
libide/editor/ide-editor-view.c | 932 ++++++++++++++++++++
libide/editor/ide-editor-view.h | 32 +
libide/editor/run.sh | 26 +
libide/resources/libide.gresource.xml | 3 +
libide/util/ide-dnd.c | 42 +
libide/util/ide-dnd.h | 30 +
libide/util/ide-gtk.c | 96 ++
libide/util/ide-gtk.h | 16 +-
plugins/command-bar/gb-command-provider.c | 10 +-
plugins/command-bar/gb-command-vim-provider.c | 2 +-
54 files changed, 4678 insertions(+), 2395 deletions(-)
---
diff --git a/contrib/egg/Makefile.am b/contrib/egg/Makefile.am
index 19fad1a..63761d4 100644
--- a/contrib/egg/Makefile.am
+++ b/contrib/egg/Makefile.am
@@ -34,6 +34,8 @@ libegg_private_la_SOURCES = \
egg-settings-sandwich.h \
egg-signal-group.c \
egg-signal-group.h \
+ egg-simple-popover.c \
+ egg-simple-popover.h \
egg-slider.c \
egg-slider.h \
egg-state-machine.c \
diff --git a/contrib/egg/egg-simple-popover.c b/contrib/egg/egg-simple-popover.c
new file mode 100644
index 0000000..d473a7b
--- /dev/null
+++ b/contrib/egg/egg-simple-popover.c
@@ -0,0 +1,415 @@
+/* egg-simple-popover.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "egg-simple-popover.h"
+
+typedef struct
+{
+ GtkPopover parent_instance;
+
+ GtkLabel *title;
+ GtkLabel *message;
+ GtkEntry *entry;
+ GtkButton *button;
+} EggSimplePopoverPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (EggSimplePopover, egg_simple_popover, GTK_TYPE_POPOVER)
+
+enum {
+ PROP_0,
+ PROP_BUTTON_TEXT,
+ PROP_MESSAGE,
+ PROP_READY,
+ PROP_TEXT,
+ PROP_TITLE,
+ LAST_PROP
+};
+
+enum {
+ ACTIVATE,
+ CHANGED,
+ INSERT_TEXT,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+const gchar *
+egg_simple_popover_get_button_text (EggSimplePopover *self)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), NULL);
+
+ return gtk_button_get_label (priv->button);
+}
+
+void
+egg_simple_popover_set_button_text (EggSimplePopover *self,
+ const gchar *button_text)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+ gtk_button_set_label (priv->button, button_text);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUTTON_TEXT]);
+}
+
+const gchar *
+egg_simple_popover_get_message (EggSimplePopover *self)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), NULL);
+
+ return gtk_label_get_text (priv->message);
+}
+
+void
+egg_simple_popover_set_message (EggSimplePopover *self,
+ const gchar *message)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+ gtk_label_set_label (priv->message, message);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
+}
+
+gboolean
+egg_simple_popover_get_ready (EggSimplePopover *self)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), FALSE);
+
+ return gtk_widget_get_sensitive (GTK_WIDGET (priv->button));
+}
+
+void
+egg_simple_popover_set_ready (EggSimplePopover *self,
+ gboolean ready)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (priv->button), ready);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READY]);
+}
+
+const gchar *
+egg_simple_popover_get_text (EggSimplePopover *self)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), NULL);
+
+ return gtk_entry_get_text (priv->entry);
+}
+
+void
+egg_simple_popover_set_text (EggSimplePopover *self,
+ const gchar *text)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+ gtk_entry_set_text (priv->entry, text);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TEXT]);
+}
+
+const gchar *
+egg_simple_popover_get_title (EggSimplePopover *self)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_val_if_fail (EGG_IS_SIMPLE_POPOVER (self), NULL);
+
+ return gtk_label_get_label (priv->title);
+}
+
+void
+egg_simple_popover_set_title (EggSimplePopover *self,
+ const gchar *title)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_return_if_fail (EGG_IS_SIMPLE_POPOVER (self));
+
+ gtk_label_set_label (priv->title, title);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
+}
+
+static void
+egg_simple_popover_button_clicked (EggSimplePopover *self,
+ GtkButton *button)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+ const gchar *text;
+
+ g_assert (EGG_IS_SIMPLE_POPOVER (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ text = gtk_entry_get_text (GTK_ENTRY (priv->entry));
+ g_signal_emit (self, signals [ACTIVATE], 0, text);
+ gtk_widget_hide (GTK_WIDGET (self));
+}
+
+static void
+egg_simple_popover_entry_activate (EggSimplePopover *self,
+ GtkEntry *entry)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ g_assert (EGG_IS_SIMPLE_POPOVER (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ if (egg_simple_popover_get_ready (self))
+ gtk_widget_activate (GTK_WIDGET (priv->button));
+}
+
+static void
+egg_simple_popover_entry_changed (EggSimplePopover *self,
+ GtkEntry *entry)
+{
+ g_assert (EGG_IS_SIMPLE_POPOVER (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ g_signal_emit (self, signals [CHANGED], 0);
+}
+
+static void
+egg_simple_popover_entry_insert_text (EggSimplePopover *self,
+ gchar *new_text,
+ gint new_text_length,
+ gint *position,
+ GtkEntry *entry)
+{
+ gboolean ret = GDK_EVENT_PROPAGATE;
+ guint pos;
+ guint n_chars;
+
+ g_assert (EGG_IS_SIMPLE_POPOVER (self));
+ g_assert (new_text != NULL);
+ g_assert (position != NULL);
+
+ pos = *position;
+ n_chars = (new_text_length >= 0) ? new_text_length : g_utf8_strlen (new_text, -1);
+
+ g_signal_emit (self, signals [INSERT_TEXT], 0, pos, new_text, n_chars, &ret);
+
+ if (ret == GDK_EVENT_STOP)
+ g_signal_stop_emission_by_name (entry, "insert-text");
+}
+
+static void
+egg_simple_popover_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EggSimplePopover *self = EGG_SIMPLE_POPOVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUTTON_TEXT:
+ g_value_set_string (value, egg_simple_popover_get_button_text (self));
+ break;
+
+ case PROP_MESSAGE:
+ g_value_set_string (value, egg_simple_popover_get_message (self));
+ break;
+
+ case PROP_READY:
+ g_value_set_boolean (value, egg_simple_popover_get_ready (self));
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, egg_simple_popover_get_text (self));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, egg_simple_popover_get_title (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_simple_popover_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EggSimplePopover *self = EGG_SIMPLE_POPOVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_BUTTON_TEXT:
+ egg_simple_popover_set_button_text (self, g_value_get_string (value));
+ break;
+
+ case PROP_MESSAGE:
+ egg_simple_popover_set_message (self, g_value_get_string (value));
+ break;
+
+ case PROP_READY:
+ egg_simple_popover_set_ready (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_TEXT:
+ egg_simple_popover_set_text (self, g_value_get_string (value));
+ break;
+
+ case PROP_TITLE:
+ egg_simple_popover_set_title (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+egg_simple_popover_class_init (EggSimplePopoverClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = egg_simple_popover_get_property;
+ object_class->set_property = egg_simple_popover_set_property;
+
+ properties [PROP_BUTTON_TEXT] =
+ g_param_spec_string ("button-text",
+ "Button Text",
+ "Button Text",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_MESSAGE] =
+ g_param_spec_string ("message",
+ "Message",
+ "Message",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_READY] =
+ g_param_spec_boolean ("ready",
+ "Ready",
+ "Ready",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "Text",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [ACTIVATE] =
+ g_signal_new ("activate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSimplePopoverClass, activate),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+
+ signals [CHANGED] =
+ g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSimplePopoverClass, insert_text),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+ signals [INSERT_TEXT] =
+ g_signal_new ("insert-text",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSimplePopoverClass, insert_text),
+ NULL, NULL, NULL,
+ G_TYPE_BOOLEAN,
+ 3,
+ G_TYPE_UINT,
+ G_TYPE_STRING,
+ G_TYPE_UINT);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/egg-simple-popover.ui");
+ gtk_widget_class_bind_template_child_private (widget_class, EggSimplePopover, title);
+ gtk_widget_class_bind_template_child_private (widget_class, EggSimplePopover, message);
+ gtk_widget_class_bind_template_child_private (widget_class, EggSimplePopover, entry);
+ gtk_widget_class_bind_template_child_private (widget_class, EggSimplePopover, button);
+}
+
+static void
+egg_simple_popover_init (EggSimplePopover *self)
+{
+ EggSimplePopoverPrivate *priv = egg_simple_popover_get_instance_private (self);
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (priv->button,
+ "clicked",
+ G_CALLBACK (egg_simple_popover_button_clicked),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (priv->entry,
+ "changed",
+ G_CALLBACK (egg_simple_popover_entry_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (priv->entry,
+ "activate",
+ G_CALLBACK (egg_simple_popover_entry_activate),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (priv->entry,
+ "insert-text",
+ G_CALLBACK (egg_simple_popover_entry_insert_text),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+GtkWidget *
+egg_simple_popover_new (void)
+{
+ return g_object_new (EGG_TYPE_SIMPLE_POPOVER, NULL);
+}
diff --git a/contrib/egg/egg-simple-popover.h b/contrib/egg/egg-simple-popover.h
new file mode 100644
index 0000000..72be856
--- /dev/null
+++ b/contrib/egg/egg-simple-popover.h
@@ -0,0 +1,90 @@
+/* egg-simple-popover.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EGG_SIMPLE_POPOVER_H
+#define EGG_SIMPLE_POPOVER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_SIMPLE_POPOVER (egg_simple_popover_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (EggSimplePopover, egg_simple_popover, EGG, SIMPLE_POPOVER, GtkPopover)
+
+struct _EggSimplePopoverClass
+{
+ GtkPopoverClass parent;
+
+ /**
+ * EggSimplePopover::activate:
+ * @self: The #EggSimplePopover instance.
+ * @text: The text at the time of activation.
+ *
+ * This signal is emitted when the popover's forward button is activated.
+ * Connect to this signal to perform your forward progress.
+ */
+ void (*activate) (EggSimplePopover *self,
+ const gchar *text);
+
+ /**
+ * EggSimplePopover::insert-text:
+ * @self: A #EggSimplePopover.
+ * @position: the position in UTF-8 characters.
+ * @chars: the NULL terminated UTF-8 text to insert.
+ * @n_chars: the number of UTF-8 characters in chars.
+ *
+ * Use this signal to determine if text should be allowed to be inserted
+ * into the text buffer. Return GDK_EVENT_STOP to prevent the text from
+ * being inserted.
+ */
+ gboolean (*insert_text) (EggSimplePopover *self,
+ guint position,
+ const gchar *chars,
+ guint n_chars);
+
+
+ /**
+ * EggSimplePopover::changed:
+ * @self: A #EggSimplePopover.
+ *
+ * This signal is emitted when the entry text changes.
+ */
+ void (*changed) (EggSimplePopover *self);
+};
+
+GtkWidget *egg_simple_popover_new (void);
+const gchar *egg_simple_popover_get_text (EggSimplePopover *self);
+void egg_simple_popover_set_text (EggSimplePopover *self,
+ const gchar *text);
+const gchar *egg_simple_popover_get_message (EggSimplePopover *self);
+void egg_simple_popover_set_message (EggSimplePopover *self,
+ const gchar *message);
+const gchar *egg_simple_popover_get_title (EggSimplePopover *self);
+void egg_simple_popover_set_title (EggSimplePopover *self,
+ const gchar *title);
+const gchar *egg_simple_popover_get_button_text (EggSimplePopover *self);
+void egg_simple_popover_set_button_text (EggSimplePopover *self,
+ const gchar *button_text);
+gboolean egg_simple_popover_get_ready (EggSimplePopover *self);
+void egg_simple_popover_set_ready (EggSimplePopover *self,
+ gboolean ready);
+
+G_END_DECLS
+
+#endif /* EGG_SIMPLE_POPOVER_H */
diff --git a/data/ui/gb-simple-popover.ui b/contrib/egg/egg-simple-popover.ui
similarity index 96%
rename from data/ui/gb-simple-popover.ui
rename to contrib/egg/egg-simple-popover.ui
index 689efcf..aabadbc 100644
--- a/data/ui/gb-simple-popover.ui
+++ b/contrib/egg/egg-simple-popover.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.16 -->
- <template class="GbSimplePopover" parent="GtkPopover">
+ <template class="EggSimplePopover" parent="GtkPopover">
<child>
<object class="GtkBox">
<property name="border-width">12</property>
diff --git a/contrib/egg/egg.gresource.xml b/contrib/egg/egg.gresource.xml
index 41879df..8d1cf87 100644
--- a/contrib/egg/egg.gresource.xml
+++ b/contrib/egg/egg.gresource.xml
@@ -2,5 +2,6 @@
<gresources>
<gresource prefix="/org/gnome/libegg-private">
<file>egg-pill-box.ui</file>
+ <file>egg-simple-popover.ui</file>
</gresource>
</gresources>
diff --git a/data/ui/gb-editor-frame.ui b/data/ui/ide-editor-frame.ui
similarity index 98%
rename from data/ui/gb-editor-frame.ui
rename to data/ui/ide-editor-frame.ui
index 7eb4a40..c26aee8 100644
--- a/data/ui/gb-editor-frame.ui
+++ b/data/ui/ide-editor-frame.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.15 -->
- <template class="GbEditorFrame" parent="GtkBin">
+ <template class="IdeEditorFrame" parent="GtkBin">
<child>
<object class="GtkOverlay" id="frame_overlay">
<property name="expand">true</property>
@@ -145,7 +145,7 @@
<property name="reveal-child">false</property>
<property name="transition-type">slide-left</property>
<child>
- <object class="GbEditorMapBin" id="source_map_container">
+ <object class="IdeEditorMapBin" id="source_map_container">
<property name="floating-bar">floating_bar</property>
<property name="visible">true</property>
</object>
diff --git a/data/ui/gb-editor-tweak-widget.ui b/data/ui/ide-editor-tweak-widget.ui
similarity index 99%
rename from data/ui/gb-editor-tweak-widget.ui
rename to data/ui/ide-editor-tweak-widget.ui
index 4bfb1af..dc75b5b 100644
--- a/data/ui/gb-editor-tweak-widget.ui
+++ b/data/ui/ide-editor-tweak-widget.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.8 -->
- <template class="GbEditorTweakWidget" parent="GtkBin">
+ <template class="IdeEditorTweakWidget" parent="GtkBin">
<property name="width_request">250</property>
<property name="height_request">450</property>
<child>
diff --git a/data/ui/gb-editor-view.ui b/data/ui/ide-editor-view.ui
similarity index 96%
rename from data/ui/gb-editor-view.ui
rename to data/ui/ide-editor-view.ui
index 04f5e00..0bac144 100644
--- a/data/ui/gb-editor-view.ui
+++ b/data/ui/ide-editor-view.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.15 -->
- <template class="GbEditorView" parent="GbView">
+ <template class="IdeEditorView" parent="IdeLayoutView">
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
@@ -73,7 +73,7 @@
<property name="orientation">vertical</property>
<property name="visible">true</property>
<child>
- <object class="GbEditorFrame" id="frame1">
+ <object class="IdeEditorFrame" id="frame1">
<property name="visible">true</property>
</object>
<packing>
@@ -159,13 +159,13 @@
</template>
<object class="GtkPopover" id="popover">
<child>
- <object class="GbEditorTweakWidget" id="tweak_widget">
+ <object class="IdeEditorTweakWidget" id="tweak_widget">
<property name="border-width">12</property>
<property name="visible">true</property>
</object>
</child>
</object>
- <object class="GbSimplePopover" id="goto_line_popover">
+ <object class="IdeSimplePopover" id="goto_line_popover">
<property name="visible">true</property>
<property name="title" translatable="yes">Go to Line</property>
<property name="button-text" translatable="yes">Go</property>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 3f04bbc..bc73b5d 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -15,6 +15,10 @@ libide_1_0_la_public_sources = \
doap/ide-doap.h \
editor/ide-editor-perspective.c \
editor/ide-editor-perspective.h \
+ editor/ide-editor-view-addin.c \
+ editor/ide-editor-view-addin.h \
+ editor/ide-editor-view.c \
+ editor/ide-editor-view.h \
git/ide-git-remote-callbacks.c \
git/ide-git-remote-callbacks.h \
git/ide-git-vcs.c \
@@ -227,6 +231,21 @@ libide_1_0_la_SOURCES = \
editorconfig/editorconfig-glib.h \
editorconfig/ide-editorconfig-file-settings.c \
editorconfig/ide-editorconfig-file-settings.h \
+ editor/ide-editor-frame-actions.c \
+ editor/ide-editor-frame-actions.h \
+ editor/ide-editor-frame.c \
+ editor/ide-editor-frame.h \
+ editor/ide-editor-frame-private.h \
+ editor/ide-editor-map-bin.c \
+ editor/ide-editor-map-bin.h \
+ editor/ide-editor-print-operation.c \
+ editor/ide-editor-print-operation.h \
+ editor/ide-editor-tweak-widget.c \
+ editor/ide-editor-tweak-widget.h \
+ editor/ide-editor-view-actions.c \
+ editor/ide-editor-view-actions.h \
+ editor/ide-editor-view-addin-private.h \
+ editor/ide-editor-view-private.h \
gconstructor.h \
git/ide-git-buffer-change-monitor.c \
git/ide-git-buffer-change-monitor.h \
@@ -314,6 +333,8 @@ libide_1_0_la_SOURCES = \
theatrics/ide-box-theatric.h \
util/ide-cairo.c \
util/ide-cairo.h \
+ util/ide-dnd.c \
+ util/ide-dnd.h \
util/ide-doc-seq.c \
util/ide-doc-seq.h \
util/ide-gdk.c \
@@ -341,6 +362,8 @@ libide_1_0_la_includes = \
-DBUILDDIR=\""${abs_top_builddir}"\" \
-I$(top_builddir)/libide \
-I$(top_srcdir)/contrib/egg \
+ -I$(top_srcdir)/contrib/gd \
+ -I$(top_srcdir)/contrib/nautilus \
-I$(top_srcdir)/contrib/libeditorconfig \
-I$(top_srcdir)/contrib/search \
-I$(top_srcdir)/contrib/xml \
@@ -392,7 +415,9 @@ libide_1_0_la_LIBADD = \
$(SHM_LIB) \
-lm \
$(top_builddir)/contrib/egg/libegg-private.la \
+ $(top_builddir)/contrib/gd/libgd.la \
$(top_builddir)/contrib/libeditorconfig/libeditorconfig.la \
+ $(top_builddir)/contrib/nautilus/libnautilus.la \
$(top_builddir)/contrib/search/libsearch.la \
$(top_builddir)/contrib/xml/libxml.la \
$(NULL)
diff --git a/libide/editor/ide-editor-frame-actions.c b/libide/editor/ide-editor-frame-actions.c
new file mode 100644
index 0000000..6061274
--- /dev/null
+++ b/libide/editor/ide-editor-frame-actions.c
@@ -0,0 +1,117 @@
+/* ide-editor-frame-actions.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <gtksourceview/gtksource.h>
+
+#include "ide-editor-frame-actions.h"
+#include "ide-editor-frame-private.h"
+
+static void
+ide_editor_frame_actions_find (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ IdeEditorFrame *self = user_data;
+ GtkTextBuffer *buffer;
+ GtkTextIter start_sel;
+ GtkTextIter end_sel;
+ GtkDirectionType search_direction;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ search_direction = (GtkDirectionType) g_variant_get_int32 (variant);
+ ide_source_view_set_search_direction (self->source_view,
+ search_direction);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+ /*
+ * If the buffer currently has a selection, we prime the search entry with the
+ * selected text. If not, we use our previous search text in the case that it was
+ * cleared by the IdeSourceView internal state.
+ */
+
+ if (gtk_text_buffer_get_has_selection (buffer))
+ {
+ gtk_text_buffer_get_selection_bounds (buffer, &start_sel, &end_sel);
+
+ if (gtk_text_iter_get_line (&start_sel) == gtk_text_iter_get_line (&end_sel))
+ {
+ const gchar *selected_text;
+
+ selected_text = gtk_text_buffer_get_text (buffer, &start_sel, &end_sel, FALSE);
+ gtk_entry_set_text (GTK_ENTRY (self->search_entry), selected_text);
+ }
+ }
+ else if (self->previous_search_string != NULL)
+ {
+ gtk_entry_set_text (GTK_ENTRY (self->search_entry), self->previous_search_string);
+ }
+
+ gtk_revealer_set_reveal_child (self->search_revealer, TRUE);
+ gtk_widget_grab_focus (GTK_WIDGET (self->search_entry));
+}
+
+static void
+ide_editor_frame_actions_next_search_result (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ IdeEditorFrame *self = user_data;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ ide_source_view_set_rubberband_search (self->source_view, FALSE);
+
+ IDE_SOURCE_VIEW_GET_CLASS (self->source_view)->move_search
+ (self->source_view, GTK_DIR_DOWN, FALSE, TRUE, TRUE, FALSE, FALSE);
+}
+
+static void
+ide_editor_frame_actions_previous_search_result (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ IdeEditorFrame *self = user_data;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ ide_source_view_set_rubberband_search (self->source_view, FALSE);
+
+ IDE_SOURCE_VIEW_GET_CLASS (self->source_view)->move_search
+ (self->source_view, GTK_DIR_UP, FALSE, TRUE, TRUE, FALSE, FALSE);
+}
+
+static const GActionEntry IdeEditorFrameActions[] = {
+ { "find", ide_editor_frame_actions_find, "i" },
+ { "next-search-result", ide_editor_frame_actions_next_search_result },
+ { "previous-search-result", ide_editor_frame_actions_previous_search_result },
+};
+
+void
+ide_editor_frame_actions_init (IdeEditorFrame *self)
+{
+ g_autoptr(GSimpleActionGroup) group = NULL;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ group = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group), IdeEditorFrameActions,
+ G_N_ELEMENTS (IdeEditorFrameActions), self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self), "frame", G_ACTION_GROUP (group));
+}
diff --git a/libide/editor/ide-editor-frame-actions.h b/libide/editor/ide-editor-frame-actions.h
new file mode 100644
index 0000000..48ebfe7
--- /dev/null
+++ b/libide/editor/ide-editor-frame-actions.h
@@ -0,0 +1,30 @@
+/* ide-editor-frame-actions.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_FRAME_ACTIONS_H
+#define IDE_EDITOR_FRAME_ACTIONS_H
+
+#include "ide-editor-frame.h"
+
+G_BEGIN_DECLS
+
+void ide_editor_frame_actions_init (IdeEditorFrame *self);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_FRAME_ACTIONS_H */
diff --git a/libide/editor/ide-editor-frame-private.h b/libide/editor/ide-editor-frame-private.h
new file mode 100644
index 0000000..8b3f366
--- /dev/null
+++ b/libide/editor/ide-editor-frame-private.h
@@ -0,0 +1,58 @@
+/* ide-editor-frame-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_FRAME_PRIVATE_H
+#define IDE_EDITOR_FRAME_PRIVATE_H
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+#include "ide-editor-map-bin.h"
+#include "gd-tagged-entry.h"
+#include "nautilus-floating-bar.h"
+
+G_BEGIN_DECLS
+
+struct _IdeEditorFrame
+{
+ GtkBin parent_instance;
+
+ gchar *previous_search_string;
+
+ NautilusFloatingBar *floating_bar;
+ GtkRevealer *map_revealer;
+ GtkLabel *mode_name_label;
+ GtkLabel *overwrite_label;
+ GtkScrolledWindow *scrolled_window;
+ GtkRevealer *search_revealer;
+ GdTaggedEntry *search_entry;
+ GdTaggedEntryTag *search_entry_tag;
+ IdeSourceView *source_view;
+ IdeEditorMapBin *source_map_container;
+ IdeSourceMap *source_map;
+ GtkOverlay *source_overlay;
+
+ gulong cursor_moved_handler;
+
+ guint auto_hide_map : 1;
+ guint show_ruler : 1;
+};
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_FRAME_PRIVATE_H */
diff --git a/libide/editor/ide-editor-frame.c b/libide/editor/ide-editor-frame.c
new file mode 100644
index 0000000..3e7013e
--- /dev/null
+++ b/libide/editor/ide-editor-frame.c
@@ -0,0 +1,926 @@
+/* ide-editor-frame.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "ide-dnd.h"
+#include "ide-editor-frame-actions.h"
+#include "ide-editor-frame-private.h"
+#include "ide-editor-frame.h"
+#include "ide-editor-map-bin.h"
+#include "ide-gtk.h"
+#include "ide-layout-stack.h"
+
+#define MINIMAP_HIDE_DURATION 1000
+#define MINIMAP_SHOW_DURATION 250
+
+G_DEFINE_TYPE (IdeEditorFrame, ide_editor_frame, GTK_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_AUTO_HIDE_MAP,
+ PROP_BACK_FORWARD_LIST,
+ PROP_DOCUMENT,
+ PROP_SHOW_MAP,
+ PROP_SHOW_RULER,
+ LAST_PROP
+};
+
+enum {
+ TARGET_URI_LIST = 100
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_editor_frame_update_ruler (IdeEditorFrame *self)
+{
+ const gchar *mode_display_name;
+ const gchar *mode_name;
+ GtkTextBuffer *buffer;
+ gboolean visible = FALSE;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+ if (!IDE_IS_BUFFER (buffer))
+ return;
+
+ /* update line/column text */
+ if (self->show_ruler)
+ {
+ g_autofree gchar *text = NULL;
+ guint ln = 0;
+ guint col = 0;
+
+ ide_source_view_get_visual_position (self->source_view, &ln, &col);
+ text = g_strdup_printf (_("Line %u, Column %u"), ln + 1, col + 1);
+ nautilus_floating_bar_set_primary_label (self->floating_bar, text);
+
+ visible = TRUE;
+ }
+ else
+ {
+ nautilus_floating_bar_set_primary_label (self->floating_bar, NULL);
+ }
+
+ /* update current mode */
+ mode_display_name = ide_source_view_get_mode_display_name (self->source_view);
+ gtk_label_set_label (self->mode_name_label, mode_display_name);
+ gtk_widget_set_visible (GTK_WIDGET (self->mode_name_label), !!mode_display_name);
+ if (mode_display_name != NULL)
+ visible = TRUE;
+
+ /*
+ * Update overwrite label.
+ *
+ * XXX: Hack for 3.18 to ensure we don't have "OVR Replace" in vim mode.
+ */
+ mode_name = ide_source_view_get_mode_name (self->source_view);
+ if (ide_source_view_get_overwrite (self->source_view) &&
+ !ide_str_equal0 (mode_name, "vim-replace"))
+ {
+ gtk_widget_set_visible (GTK_WIDGET (self->overwrite_label), TRUE);
+ visible = TRUE;
+ }
+ else
+ {
+ gtk_widget_set_visible (GTK_WIDGET (self->overwrite_label), FALSE);
+ }
+
+ if (gtk_widget_get_visible (GTK_WIDGET (self->mode_name_label)))
+ visible = TRUE;
+
+ if (ide_buffer_get_busy (IDE_BUFFER (buffer)))
+ {
+ nautilus_floating_bar_set_show_spinner (self->floating_bar, TRUE);
+ visible = TRUE;
+ }
+ else
+ {
+ nautilus_floating_bar_set_show_spinner (self->floating_bar, FALSE);
+ }
+
+ /* we don't fade while hiding because we likely won't have
+ * any text labels set anyway.
+ */
+ if (!visible && gtk_widget_get_visible (GTK_WIDGET (self->floating_bar)))
+ gtk_widget_hide (GTK_WIDGET (self->floating_bar));
+ else if (visible && !gtk_widget_get_visible (GTK_WIDGET (self->floating_bar)))
+ gtk_widget_show (GTK_WIDGET (self->floating_bar));
+}
+
+static void
+ide_editor_frame_set_show_ruler (IdeEditorFrame *self,
+ gboolean show_ruler)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ if (show_ruler != self->show_ruler)
+ {
+ self->show_ruler = show_ruler;
+ ide_editor_frame_update_ruler (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_RULER]);
+ }
+}
+
+static void
+ide_editor_frame_animate_map (IdeEditorFrame *self,
+ gboolean visible)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ gtk_revealer_set_reveal_child (self->map_revealer, visible);
+}
+
+static void
+ide_editor_frame_show_map (IdeEditorFrame *self,
+ IdeSourceMap *source_map)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (IDE_IS_SOURCE_MAP (source_map));
+
+ ide_editor_frame_animate_map (self, TRUE);
+}
+
+static void
+ide_editor_frame_hide_map (IdeEditorFrame *self,
+ IdeSourceMap *source_map)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (IDE_IS_SOURCE_MAP (source_map));
+
+ /* ignore hide request if auto-hide is disabled */
+ if ((self->source_map != NULL) && !self->auto_hide_map)
+ return;
+
+ ide_editor_frame_animate_map (self, FALSE);
+}
+
+static void
+ide_editor_frame_set_position_label (IdeEditorFrame *self,
+ const gchar *text)
+{
+ g_return_if_fail (IDE_IS_EDITOR_FRAME (self));
+
+ if (!text || !*text)
+ {
+ if (self->search_entry_tag)
+ {
+ gd_tagged_entry_remove_tag (self->search_entry, self->search_entry_tag);
+ g_clear_object (&self->search_entry_tag);
+ }
+ return;
+ }
+
+ if (!self->search_entry_tag)
+ {
+ self->search_entry_tag = gd_tagged_entry_tag_new ("");
+ gd_tagged_entry_tag_set_style (self->search_entry_tag, "gb-search-entry-occurrences-tag");
+ gd_tagged_entry_add_tag (self->search_entry, self->search_entry_tag);
+ }
+
+ gd_tagged_entry_tag_set_label (self->search_entry_tag, text);
+}
+
+static void
+ide_editor_frame_update_search_position_label (IdeEditorFrame *self)
+{
+ GtkSourceSearchContext *search_context;
+ GtkStyleContext *context;
+ GtkTextBuffer *buffer;
+ GtkTextIter begin;
+ GtkTextIter end;
+ const gchar *search_text;
+ gchar *text;
+ gint count;
+ gint pos;
+
+ g_return_if_fail (IDE_IS_EDITOR_FRAME (self));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+ search_context = ide_source_view_get_search_context (self->source_view);
+ gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
+ pos = gtk_source_search_context_get_occurrence_position (search_context, &begin, &end);
+ count = gtk_source_search_context_get_occurrences_count (search_context);
+
+ if ((pos == -1) || (count == -1))
+ {
+ /*
+ * We are not yet done scanning the buffer.
+ * We will be updated when we know more, so just hide it for now.
+ */
+ ide_editor_frame_set_position_label (self, NULL);
+ return;
+ }
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (self->search_entry));
+ search_text = gtk_entry_get_text (GTK_ENTRY (self->search_entry));
+
+ if ((count == 0) && !ide_str_empty0 (search_text))
+ gtk_style_context_add_class (context, GTK_STYLE_CLASS_ERROR);
+ else
+ gtk_style_context_remove_class (context, GTK_STYLE_CLASS_ERROR);
+
+ text = g_strdup_printf (_("%u of %u"), pos, count);
+ ide_editor_frame_set_position_label (self, text);
+ g_free (text);
+}
+
+static void
+ide_editor_frame_on_search_occurrences_notify (IdeEditorFrame *self,
+ GParamSpec *pspec,
+ GtkSourceSearchContext *search_context)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (GTK_SOURCE_IS_SEARCH_CONTEXT (search_context));
+
+ ide_editor_frame_update_search_position_label (self);
+}
+
+static void
+on_cursor_moved (IdeBuffer *buffer,
+ const GtkTextIter *location,
+ IdeEditorFrame *self)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (IDE_IS_BUFFER (buffer));
+
+ ide_editor_frame_update_ruler (self);
+ ide_editor_frame_update_search_position_label (self);
+}
+
+/**
+ * ide_editor_frame_get_document:
+ *
+ * Gets the #IdeEditorFrame:document property.
+ *
+ * Returns: (transfer none) (nullable): An #IdeBuffer or %NULL.
+ */
+IdeBuffer *
+ide_editor_frame_get_document (IdeEditorFrame *self)
+{
+ GtkTextBuffer *buffer;
+
+ g_return_val_if_fail (IDE_IS_EDITOR_FRAME (self), NULL);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+ return IDE_IS_BUFFER (buffer) ? IDE_BUFFER (buffer) : NULL;
+}
+
+static gboolean
+search_text_transform_to (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ g_assert (from_value != NULL);
+ g_assert (to_value != NULL);
+
+ if (g_value_get_string (from_value) == NULL)
+ g_value_set_string (to_value, "");
+ else
+ g_value_copy (from_value, to_value);
+
+ return TRUE;
+}
+
+static gboolean
+search_text_transform_from (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ g_assert (from_value != NULL);
+ g_assert (to_value != NULL);
+
+ if (g_value_get_string (from_value) == NULL)
+ g_value_set_string (to_value, "");
+ else
+ g_value_copy (from_value, to_value);
+
+ return TRUE;
+}
+
+void
+ide_editor_frame_set_document (IdeEditorFrame *self,
+ IdeBuffer *buffer)
+{
+ GtkSourceSearchContext *search_context;
+ GtkSourceSearchSettings *search_settings;
+ GtkTextMark *mark;
+ GtkTextIter iter;
+
+ g_return_if_fail (IDE_IS_EDITOR_FRAME (self));
+ g_return_if_fail (IDE_IS_BUFFER (buffer));
+
+ gtk_text_view_set_buffer (GTK_TEXT_VIEW (self->source_view), GTK_TEXT_BUFFER (buffer));
+
+ g_signal_connect_object (buffer,
+ "notify::busy",
+ G_CALLBACK (ide_editor_frame_update_ruler),
+ self,
+ G_CONNECT_SWAPPED);
+
+ self->cursor_moved_handler =
+ g_signal_connect (buffer,
+ "cursor-moved",
+ G_CALLBACK (on_cursor_moved),
+ self);
+ mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, mark);
+ on_cursor_moved (buffer, &iter, self);
+
+ /*
+ * Sync search entry with the search settings.
+ */
+ search_context = ide_source_view_get_search_context (self->source_view);
+ search_settings = gtk_source_search_context_get_settings (search_context);
+ g_object_bind_property_full (self->search_entry, "text", search_settings, "search-text",
+ (G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL),
+ search_text_transform_to, search_text_transform_from,
+ NULL, NULL);
+ g_signal_connect_object (search_context,
+ "notify::occurrences-count",
+ G_CALLBACK (ide_editor_frame_on_search_occurrences_notify),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static gboolean
+get_smart_home_end (GValue *value,
+ GVariant *variant,
+ gpointer user_data)
+{
+ if (g_variant_get_boolean (variant))
+ g_value_set_enum (value, GTK_SOURCE_SMART_HOME_END_BEFORE);
+ else
+ g_value_set_enum (value, GTK_SOURCE_SMART_HOME_END_DISABLED);
+
+ return TRUE;
+}
+
+static void
+keybindings_changed (GSettings *settings,
+ const gchar *key,
+ IdeEditorFrame *self)
+{
+ g_signal_emit_by_name (self->source_view,
+ "set-mode",
+ NULL,
+ IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT);
+}
+
+static void
+ide_editor_frame_grab_focus (GtkWidget *widget)
+{
+ IdeEditorFrame *self = (IdeEditorFrame *)widget;
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+}
+
+static void
+ide_editor_frame__drag_data_received (IdeEditorFrame *self,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint timestamp,
+ GtkWidget *widget)
+{
+ gchar **uri_list;
+
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (widget));
+
+ switch (info)
+ {
+ case TARGET_URI_LIST:
+ uri_list = ide_dnd_get_uri_list (selection_data);
+
+ if (uri_list)
+ {
+ GVariantBuilder *builder;
+ GVariant *variant;
+ guint i;
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE_STRING_ARRAY);
+ for (i = 0; uri_list [i]; i++)
+ g_variant_builder_add (builder, "s", uri_list [i]);
+ variant = g_variant_builder_end (builder);
+ g_variant_builder_unref (builder);
+ g_strfreev (uri_list);
+
+ /*
+ * request that we get focus first so the workbench will deliver the
+ * document to us in the case it is not already open
+ */
+ gtk_widget_grab_focus (GTK_WIDGET (self));
+
+ ide_widget_action (GTK_WIDGET (self), "workbench", "open-uri-list", variant);
+ }
+
+ gtk_drag_finish (context, TRUE, FALSE, timestamp);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static gboolean
+ide_editor_frame__search_key_press_event (IdeEditorFrame *self,
+ GdkEventKey *event,
+ GdTaggedEntry *entry)
+{
+ GtkTextBuffer *buffer;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (GD_IS_TAGGED_ENTRY (entry));
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_Escape:
+ /* stash the search string for later */
+ g_free (self->previous_search_string);
+ g_object_get (self->search_entry, "text", &self->previous_search_string, NULL);
+
+ /* clear the highlights in the source view */
+ ide_source_view_clear_search (self->source_view);
+
+ /* disable rubberbanding and ensure insert mark is on screen */
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+ ide_source_view_set_rubberband_search (self->source_view, FALSE);
+ ide_source_view_scroll_mark_onscreen (self->source_view,
+ gtk_text_buffer_get_insert (buffer),
+ TRUE,
+ 0.5,
+ 0.5);
+
+ /* finally we can focus the source view */
+ gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+
+ return GDK_EVENT_STOP;
+
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_Return:
+ ide_widget_action (GTK_WIDGET (self), "frame", "next-search-result", NULL);
+ gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
+ return GDK_EVENT_STOP;
+
+ case GDK_KEY_Down:
+ ide_widget_action (GTK_WIDGET (self), "frame", "next-search-result", NULL);
+ return GDK_EVENT_STOP;
+
+ case GDK_KEY_Up:
+ ide_widget_action (GTK_WIDGET (self), "frame", "previous-search-result", NULL);
+ return GDK_EVENT_STOP;
+
+ default:
+ {
+ GtkSourceSearchSettings *search_settings;
+ GtkSourceSearchContext *search_context;
+
+ if (!ide_source_view_get_rubberband_search (self->source_view))
+ ide_source_view_set_rubberband_search (self->source_view, TRUE);
+
+ /*
+ * Other modes, such as Vim emulation, want word boundaries, but we do
+ * not when searching from this entry. Sort of hacky, but gets the job
+ * done to just change that setting here.
+ */
+ search_context = ide_source_view_get_search_context (self->source_view);
+ search_settings = gtk_source_search_context_get_settings (search_context);
+ gtk_source_search_settings_set_at_word_boundaries (search_settings, FALSE);
+ }
+ break;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+ide_editor_frame__source_view_focus_in_event (IdeEditorFrame *self,
+ GdkEventKey *event,
+ IdeSourceView *source_view)
+{
+ GtkTextBuffer *buffer;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+ gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view));
+
+ if (IDE_IS_BUFFER (buffer))
+ ide_buffer_check_for_volume_change (IDE_BUFFER (buffer));
+
+ return FALSE;
+}
+
+static void
+ide_editor_frame__source_view_focus_location (IdeEditorFrame *self,
+ IdeSourceLocation *location,
+ IdeSourceView *source_view)
+{
+ GtkWidget *toplevel;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (location != NULL);
+ g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+ toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_WORKBENCH);
+
+ if (IDE_IS_WORKBENCH (toplevel))
+ {
+ /*
+ * Convert location to Uri and open.
+ */
+#if 0
+ ide_layout_stack_focus_location (IDE_LAYOUT_STACK (widget), location);
+#endif
+ }
+}
+
+static void
+ide_editor_frame__source_view_request_documentation (IdeEditorFrame *self,
+ IdeSourceView *source_view)
+{
+ GtkTextBuffer *buffer;
+ GtkTextIter begin;
+ GtkTextIter end;
+ GVariant *param;
+ g_autofree gchar *text = NULL;
+
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view));
+ gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
+ text = gtk_text_iter_get_slice (&begin, &end);
+
+ param = g_variant_new_string (text);
+ ide_widget_action (GTK_WIDGET (self), "workbench", "search-docs", param);
+}
+
+static gboolean
+ide_editor_frame_get_show_map (IdeEditorFrame *self)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ return (self->source_map != NULL);
+}
+
+static void
+ide_editor_frame_set_show_map (IdeEditorFrame *self,
+ gboolean show_map)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ if (show_map != ide_editor_frame_get_show_map (self))
+ {
+ if (self->source_map != NULL)
+ {
+ gtk_container_remove (GTK_CONTAINER (self->source_map_container),
+ GTK_WIDGET (self->source_map));
+ self->source_map = NULL;
+ }
+ else
+ {
+ self->source_map = g_object_new (IDE_TYPE_SOURCE_MAP,
+ "view", self->source_view,
+ "visible", TRUE,
+ NULL);
+ g_signal_connect_object (self->source_map,
+ "show-map",
+ G_CALLBACK (ide_editor_frame_show_map),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->source_map,
+ "hide-map",
+ G_CALLBACK (ide_editor_frame_hide_map),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_container_add (GTK_CONTAINER (self->source_map_container),
+ GTK_WIDGET (self->source_map));
+ g_signal_emit_by_name (self->source_map, "show-map");
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_MAP]);
+ }
+}
+
+static void
+ide_editor_frame_set_auto_hide_map (IdeEditorFrame *self,
+ gboolean auto_hide_map)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+
+ auto_hide_map = !!auto_hide_map;
+
+ if (auto_hide_map != self->auto_hide_map)
+ {
+ self->auto_hide_map = auto_hide_map;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_AUTO_HIDE_MAP]);
+ }
+}
+
+static void
+ide_editor_frame__source_view_populate_popup (IdeEditorFrame *self,
+ GtkWidget *popup,
+ IdeSourceView *source_view)
+{
+ g_assert (IDE_IS_EDITOR_FRAME (self));
+ g_assert (GTK_IS_WIDGET (popup));
+ g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+ if (GTK_IS_MENU_SHELL (popup))
+ {
+ GtkWidget *item;
+ GtkWidget *sep;
+
+ sep = g_object_new (GTK_TYPE_SEPARATOR_MENU_ITEM,
+ "visible", TRUE,
+ NULL);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), sep);
+
+ item = g_object_new (GTK_TYPE_MENU_ITEM,
+ "action-name", "view.reveal",
+ "label", _("Re_veal in Project Tree"),
+ "use-underline", TRUE,
+ "visible", TRUE,
+ NULL);
+ gtk_menu_shell_append (GTK_MENU_SHELL (popup), item);
+ }
+}
+
+static void
+ide_editor_frame_constructed (GObject *object)
+{
+ IdeEditorFrame *self = (IdeEditorFrame *)object;
+
+ G_OBJECT_CLASS (ide_editor_frame_parent_class)->constructed (object);
+
+ g_signal_connect_object (self->source_view,
+ "drag-data-received",
+ G_CALLBACK (ide_editor_frame__drag_data_received),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->source_view,
+ "focus-in-event",
+ G_CALLBACK (ide_editor_frame__source_view_focus_in_event),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->source_view,
+ "focus-location",
+ G_CALLBACK (ide_editor_frame__source_view_focus_location),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->source_view,
+ "populate-popup",
+ G_CALLBACK (ide_editor_frame__source_view_populate_popup),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->source_view,
+ "request-documentation",
+ G_CALLBACK (ide_editor_frame__source_view_request_documentation),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->search_entry,
+ "key-press-event",
+ G_CALLBACK (ide_editor_frame__search_key_press_event),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+ide_editor_frame_dispose (GObject *object)
+{
+ IdeEditorFrame *self = (IdeEditorFrame *)object;
+
+ g_clear_pointer (&self->previous_search_string, g_free);
+
+ if (self->source_view && self->cursor_moved_handler)
+ {
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+ ide_clear_signal_handler (buffer, &self->cursor_moved_handler);
+ }
+
+ g_clear_object (&self->search_entry_tag);
+
+ G_OBJECT_CLASS (ide_editor_frame_parent_class)->dispose (object);
+}
+
+static void
+ide_editor_frame_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorFrame *self = IDE_EDITOR_FRAME (object);
+
+ switch (prop_id)
+ {
+ case PROP_AUTO_HIDE_MAP:
+ g_value_set_boolean (value, self->auto_hide_map);
+ break;
+
+ case PROP_DOCUMENT:
+ g_value_set_object (value, ide_editor_frame_get_document (self));
+ break;
+
+ case PROP_SHOW_MAP:
+ g_value_set_boolean (value, ide_editor_frame_get_show_map (self));
+ break;
+
+ case PROP_SHOW_RULER:
+ g_value_set_boolean (value, self->show_ruler);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_frame_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorFrame *self = IDE_EDITOR_FRAME (object);
+
+ switch (prop_id)
+ {
+ case PROP_AUTO_HIDE_MAP:
+ ide_editor_frame_set_auto_hide_map (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_DOCUMENT:
+ ide_editor_frame_set_document (self, g_value_get_object (value));
+ break;
+
+ case PROP_BACK_FORWARD_LIST:
+ ide_source_view_set_back_forward_list (self->source_view, g_value_get_object (value));
+ break;
+
+ case PROP_SHOW_MAP:
+ ide_editor_frame_set_show_map (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_SHOW_RULER:
+ ide_editor_frame_set_show_ruler (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_frame_class_init (IdeEditorFrameClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = ide_editor_frame_constructed;
+ object_class->dispose = ide_editor_frame_dispose;
+ object_class->get_property = ide_editor_frame_get_property;
+ object_class->set_property = ide_editor_frame_set_property;
+
+ widget_class->grab_focus = ide_editor_frame_grab_focus;
+
+ properties [PROP_AUTO_HIDE_MAP] =
+ g_param_spec_boolean ("auto-hide-map",
+ "Auto Hide Map",
+ "Auto Hide Map",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_BACK_FORWARD_LIST] =
+ g_param_spec_object ("back-forward-list",
+ "Back Forward List",
+ "The back forward list.",
+ IDE_TYPE_BACK_FORWARD_LIST,
+ (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_DOCUMENT] =
+ g_param_spec_object ("document",
+ "Document",
+ "The editor document.",
+ IDE_TYPE_BUFFER,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SHOW_MAP] =
+ g_param_spec_boolean ("show-map",
+ "Show Map",
+ "If the overview map should be shown.",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_SHOW_RULER] =
+ g_param_spec_boolean ("show-ruler",
+ "Show Ruler",
+ "If the ruler should always be shown.",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-editor-frame.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, floating_bar);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, map_revealer);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, mode_name_label);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, overwrite_label);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, scrolled_window);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, search_revealer);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_map_container);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_overlay);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorFrame, source_view);
+
+ g_type_ensure (NAUTILUS_TYPE_FLOATING_BAR);
+ g_type_ensure (GD_TYPE_TAGGED_ENTRY);
+}
+
+static void
+ide_editor_frame_init (IdeEditorFrame *self)
+{
+ g_autoptr(GSettings) settings = NULL;
+ g_autoptr(GSettings) insight_settings = NULL;
+ GtkTargetList *target_list;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ ide_editor_frame_actions_init (self);
+
+ settings = g_settings_new ("org.gnome.builder.editor");
+ g_settings_bind (settings, "draw-spaces", self->source_view, "draw-spaces", G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (settings, "font-name", self->source_view, "font-name", G_SETTINGS_BIND_GET);
+ g_settings_bind (settings, "highlight-current-line", self->source_view, "highlight-current-line",
G_SETTINGS_BIND_GET);
+ g_settings_bind (settings, "scroll-offset", self->source_view, "scroll-offset", G_SETTINGS_BIND_GET);
+ g_settings_bind (settings, "show-grid-lines", self->source_view, "show-grid-lines", G_SETTINGS_BIND_GET);
+ g_settings_bind (settings, "show-line-changes", self->source_view, "show-line-changes",
G_SETTINGS_BIND_GET);
+ g_settings_bind (settings, "show-line-numbers", self->source_view, "show-line-numbers",
G_SETTINGS_BIND_GET);
+ g_settings_bind (settings, "smart-backspace", self->source_view, "smart-backspace", G_SETTINGS_BIND_GET);
+ g_settings_bind_with_mapping (settings, "smart-home-end", self->source_view, "smart-home-end",
G_SETTINGS_BIND_GET, get_smart_home_end, NULL, NULL, NULL);
+ g_settings_bind (settings, "show-map", self, "show-map", G_SETTINGS_BIND_GET);
+ g_settings_bind (settings, "auto-hide-map", self, "auto-hide-map", G_SETTINGS_BIND_GET);
+ g_signal_connect (settings, "changed::keybindings", G_CALLBACK (keybindings_changed), self);
+
+ insight_settings = g_settings_new ("org.gnome.builder.code-insight");
+ g_settings_bind (insight_settings, "word-completion", self->source_view, "enable-word-completion",
G_SETTINGS_BIND_GET);
+
+ g_signal_connect_object (self->source_view,
+ "notify::overwrite",
+ G_CALLBACK (ide_editor_frame_update_ruler),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->source_view,
+ "notify::mode-display-name",
+ G_CALLBACK (ide_editor_frame_update_ruler),
+ self,
+ G_CONNECT_SWAPPED);
+
+ /*
+ * we want to rubberbanding search until enter has been pressed or next/previous actions
+ * have been activated.
+ */
+ g_object_bind_property (self->search_revealer, "visible",
+ self->source_view, "rubberband-search",
+ G_BINDING_SYNC_CREATE);
+
+ /*
+ * Drag and drop support
+ */
+ target_list = gtk_drag_dest_get_target_list (GTK_WIDGET (self->source_view));
+ if (target_list)
+ gtk_target_list_add_uri_targets (target_list, TARGET_URI_LIST);
+}
diff --git a/libide/editor/ide-editor-frame.h b/libide/editor/ide-editor-frame.h
new file mode 100644
index 0000000..5b3c7c4
--- /dev/null
+++ b/libide/editor/ide-editor-frame.h
@@ -0,0 +1,38 @@
+/* ide-editor-frame.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_FRAME_H
+#define IDE_EDITOR_FRAME_H
+
+#include <gtk/gtk.h>
+
+#include "ide-buffer.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_FRAME (ide_editor_frame_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorFrame, ide_editor_frame, IDE, EDITOR_FRAME, GtkBin)
+
+IdeBuffer *ide_editor_frame_get_document (IdeEditorFrame *self);
+void ide_editor_frame_set_document (IdeEditorFrame *self,
+ IdeBuffer *buffer);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_FRAME_H */
diff --git a/libide/editor/ide-editor-map-bin.c b/libide/editor/ide-editor-map-bin.c
new file mode 100644
index 0000000..eaabeb8
--- /dev/null
+++ b/libide/editor/ide-editor-map-bin.c
@@ -0,0 +1,245 @@
+/* ide-editor-map-bin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <ide.h>
+#include <pango/pangofc-fontmap.h>
+
+#include "ide-editor-map-bin.h"
+
+struct _IdeEditorMapBin
+{
+ GtkBox parent_instance;
+ gint cached_height;
+ gulong size_allocate_handler;
+ GtkWidget *floating_bar;
+ GtkWidget *separator;
+};
+
+G_DEFINE_TYPE (IdeEditorMapBin, ide_editor_map_bin, GTK_TYPE_BOX)
+
+enum {
+ PROP_0,
+ PROP_FLOATING_BAR,
+ LAST_PROP
+};
+
+static FcConfig *localFontConfig;
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_editor_map_bin__floating_bar_size_allocate (IdeEditorMapBin *self,
+ GtkAllocation *alloc,
+ GtkWidget *floating_bar)
+{
+ g_assert (IDE_IS_EDITOR_MAP_BIN (self));
+ g_assert (alloc != NULL);
+ g_assert (GTK_IS_WIDGET (floating_bar));
+
+ if (self->cached_height != alloc->height)
+ {
+ self->cached_height = alloc->height;
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ }
+}
+
+static void
+ide_editor_map_bin_set_floating_bar (IdeEditorMapBin *self,
+ GtkWidget *floating_bar)
+{
+ g_return_if_fail (IDE_IS_EDITOR_MAP_BIN (self));
+
+ if (floating_bar != self->floating_bar)
+ {
+ self->cached_height = 0;
+
+ if (self->floating_bar)
+ {
+ ide_clear_signal_handler (self->floating_bar, &self->size_allocate_handler);
+ ide_clear_weak_pointer (&self->floating_bar);
+ }
+
+ if (floating_bar)
+ {
+ ide_set_weak_pointer (&self->floating_bar, floating_bar);
+ g_signal_connect_object (self->floating_bar,
+ "size-allocate",
+ G_CALLBACK (ide_editor_map_bin__floating_bar_size_allocate),
+ self,
+ G_CONNECT_SWAPPED);
+ gtk_widget_queue_resize (GTK_WIDGET (floating_bar));
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ }
+}
+
+static void
+ide_editor_map_bin_size_allocate (GtkWidget *widget,
+ GtkAllocation *alloc)
+{
+ IdeEditorMapBin *self = (IdeEditorMapBin *)widget;
+
+ if (self->floating_bar != NULL)
+ alloc->height -= self->cached_height;
+
+ GTK_WIDGET_CLASS (ide_editor_map_bin_parent_class)->size_allocate (widget, alloc);
+}
+
+static void
+ide_editor_map_bin_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ IdeEditorMapBin *self = (IdeEditorMapBin *)container;
+
+ if (IDE_IS_SOURCE_MAP (child) && (self->separator != NULL))
+ {
+ PangoFontMap *font_map;
+ PangoFontDescription *font_desc;
+
+ font_map = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);
+ pango_fc_font_map_set_config (PANGO_FC_FONT_MAP (font_map), localFontConfig);
+ gtk_widget_set_font_map (child, font_map);
+
+ font_desc = pango_font_description_from_string ("Builder Blocks 1");
+ g_object_set (child, "font-desc", font_desc, NULL);
+
+ g_object_unref (font_map);
+ pango_font_description_free (font_desc);
+
+ gtk_widget_show (GTK_WIDGET (self->separator));
+ }
+
+ GTK_CONTAINER_CLASS (ide_editor_map_bin_parent_class)->add (container, child);
+}
+
+static void
+ide_editor_map_bin_remove (GtkContainer *container,
+ GtkWidget *child)
+{
+ IdeEditorMapBin *self = (IdeEditorMapBin *)container;
+
+ if (IDE_IS_SOURCE_MAP (child) && (self->separator != NULL))
+ gtk_widget_hide (GTK_WIDGET (self->separator));
+
+ GTK_CONTAINER_CLASS (ide_editor_map_bin_parent_class)->remove (container, child);
+}
+
+static void
+ide_editor_map_bin_finalize (GObject *object)
+{
+ IdeEditorMapBin *self = (IdeEditorMapBin *)object;
+
+ if (self->separator != NULL)
+ g_object_remove_weak_pointer (G_OBJECT (self->separator), (gpointer *)&self->separator);
+ ide_clear_signal_handler (self->floating_bar, &self->size_allocate_handler);
+ ide_clear_weak_pointer (&self->floating_bar);
+
+ G_OBJECT_CLASS (ide_editor_map_bin_parent_class)->finalize (object);
+}
+
+static void
+ide_editor_map_bin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorMapBin *self = IDE_EDITOR_MAP_BIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_FLOATING_BAR:
+ g_value_set_object (value, self->floating_bar);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_map_bin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorMapBin *self = IDE_EDITOR_MAP_BIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_FLOATING_BAR:
+ ide_editor_map_bin_set_floating_bar (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_map_bin_load_font (void)
+{
+ const gchar *font_path = PACKAGE_DATADIR"/gnome-builder/fonts/BuilderBlocks.ttf";
+
+ localFontConfig = FcInitLoadConfigAndFonts ();
+
+ if (g_getenv ("GB_IN_TREE_FONTS") != NULL)
+ font_path = "data/fonts/BuilderBlocks.ttf";
+
+ FcConfigAppFontAddFile (localFontConfig, (const FcChar8 *)font_path);
+}
+
+static void
+ide_editor_map_bin_class_init (IdeEditorMapBinClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->finalize = ide_editor_map_bin_finalize;
+ object_class->get_property = ide_editor_map_bin_get_property;
+ object_class->set_property = ide_editor_map_bin_set_property;
+
+ widget_class->size_allocate = ide_editor_map_bin_size_allocate;
+
+ container_class->add = ide_editor_map_bin_add;
+ container_class->remove = ide_editor_map_bin_remove;
+
+ properties [PROP_FLOATING_BAR] =
+ g_param_spec_object ("floating-bar",
+ "Floating Bar",
+ "The floating bar to use for relative allocation size.",
+ GTK_TYPE_WIDGET,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ ide_editor_map_bin_load_font ();
+}
+
+static void
+ide_editor_map_bin_init (IdeEditorMapBin *self)
+{
+ self->separator = g_object_new (GTK_TYPE_SEPARATOR,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "hexpand", FALSE,
+ "visible", FALSE,
+ NULL);
+ g_object_add_weak_pointer (G_OBJECT (self->separator), (gpointer *)&self->separator);
+ gtk_container_add (GTK_CONTAINER (self), self->separator);
+}
diff --git a/libide/editor/ide-editor-map-bin.h b/libide/editor/ide-editor-map-bin.h
new file mode 100644
index 0000000..84a537a
--- /dev/null
+++ b/libide/editor/ide-editor-map-bin.h
@@ -0,0 +1,32 @@
+/* ide-editor-map-bin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_MAP_BIN_H
+#define IDE_EDITOR_MAP_BIN_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_MAP_BIN (ide_editor_map_bin_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorMapBin, ide_editor_map_bin, IDE, EDITOR_MAP_BIN, GtkBox)
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_MAP_BIN_H */
diff --git a/libide/editor/ide-editor-print-operation.c b/libide/editor/ide-editor-print-operation.c
new file mode 100644
index 0000000..2adce17
--- /dev/null
+++ b/libide/editor/ide-editor-print-operation.c
@@ -0,0 +1,207 @@
+/* ide-editor-print-operation.c
+ *
+ * Copyright (C) 2015 Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-editor-print-operation"
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+#include <ide.h>
+
+#include "ide-editor-print-operation.h"
+#include "ide-editor-view.h"
+
+struct _IdeEditorPrintOperation
+{
+ GtkPrintOperation parent_instance;
+
+ IdeSourceView *view;
+ GtkSourcePrintCompositor *compositor;
+};
+
+G_DEFINE_TYPE (IdeEditorPrintOperation, ide_editor_print_operation, GTK_TYPE_PRINT_OPERATION)
+
+enum {
+ PROP_0,
+ PROP_VIEW,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static void
+ide_editor_print_operation_dispose (GObject *object)
+{
+ IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (object);
+
+ g_clear_object (&self->compositor);
+
+ G_OBJECT_CLASS (ide_editor_print_operation_parent_class)->dispose (object);
+}
+
+static void
+ide_editor_view_print_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ g_value_set_object (value, self->view);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_view_print_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_VIEW:
+ self->view = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_print_operation_begin_print (GtkPrintOperation *operation,
+ GtkPrintContext *context)
+{
+ IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (operation);
+ GtkSourceBuffer *buffer;
+ guint tab_width;
+ gboolean syntax_hl;
+
+ buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->view)));
+
+ tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (self->view));
+ syntax_hl = gtk_source_buffer_get_highlight_syntax (buffer);
+
+ self->compositor = GTK_SOURCE_PRINT_COMPOSITOR (
+ g_object_new (GTK_SOURCE_TYPE_PRINT_COMPOSITOR,
+ "buffer", buffer,
+ "tab-width", tab_width,
+ "highlight-syntax", syntax_hl,
+ NULL));
+}
+
+static gboolean
+ide_editor_print_operation_paginate (GtkPrintOperation *operation,
+ GtkPrintContext *context)
+{
+ IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (operation);
+ gboolean finished;
+
+ finished = gtk_source_print_compositor_paginate (self->compositor, context);
+
+ if (finished)
+ {
+ gint n_pages;
+
+ n_pages = gtk_source_print_compositor_get_n_pages (self->compositor);
+ gtk_print_operation_set_n_pages (operation, n_pages);
+ }
+
+ return finished;
+}
+
+static void
+ide_editor_print_operation_draw_page (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ gint page_nr)
+{
+ IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (operation);
+
+ gtk_source_print_compositor_draw_page (self->compositor, context, page_nr);
+}
+
+static void
+ide_editor_print_operation_end_print (GtkPrintOperation *operation,
+ GtkPrintContext *context)
+{
+ IdeEditorPrintOperation *self = IDE_EDITOR_PRINT_OPERATION (operation);
+
+ g_clear_object (&self->compositor);
+}
+
+static void
+ide_editor_print_operation_class_init (IdeEditorPrintOperationClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkPrintOperationClass *operation_class = GTK_PRINT_OPERATION_CLASS (klass);
+
+ object_class->dispose = ide_editor_print_operation_dispose;
+ object_class->get_property = ide_editor_view_print_get_property;
+ object_class->set_property = ide_editor_view_print_set_property;
+
+ operation_class->begin_print = ide_editor_print_operation_begin_print;
+ operation_class->draw_page = ide_editor_print_operation_draw_page;
+ operation_class->end_print = ide_editor_print_operation_end_print;
+
+ properties [PROP_VIEW] =
+ g_param_spec_object ("view",
+ "View",
+ "The source view.",
+ IDE_TYPE_SOURCE_VIEW,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static gboolean
+paginate_cb (GtkPrintOperation *operation,
+ GtkPrintContext *context,
+ gpointer user_data)
+{
+ return ide_editor_print_operation_paginate (operation, context);
+}
+
+static void
+ide_editor_print_operation_init (IdeEditorPrintOperation *self)
+{
+ /* FIXME: gtk decides to call paginate only if it sees a pending signal
+ * handler, even if we override the default handler.
+ * So for now we connect to the signal instead of overriding the vfunc
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=345345
+ */
+ g_signal_connect (self, "paginate", G_CALLBACK (paginate_cb), NULL);
+}
+
+IdeEditorPrintOperation *
+ide_editor_print_operation_new (IdeSourceView *view)
+{
+ g_assert (IDE_IS_SOURCE_VIEW (view));
+
+ return g_object_new (IDE_TYPE_EDITOR_PRINT_OPERATION,
+ "view", view,
+ "allow-async", TRUE,
+ NULL);
+}
diff --git a/libide/editor/ide-editor-print-operation.h b/libide/editor/ide-editor-print-operation.h
new file mode 100644
index 0000000..19a569c
--- /dev/null
+++ b/libide/editor/ide-editor-print-operation.h
@@ -0,0 +1,34 @@
+/* ide-editor-print-operation.h
+ *
+ * Copyright (C) 2015 Paolo Borelli <pborelli gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_PRINT_OPERATION_H
+#define IDE_EDITOR_PRINT_OPERATION_H
+
+#include "ide-editor-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_PRINT_OPERATION (ide_editor_print_operation_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorPrintOperation, ide_editor_print_operation, IDE, EDITOR_PRINT_OPERATION,
GtkPrintOperation)
+
+IdeEditorPrintOperation *ide_editor_print_operation_new (IdeSourceView *view);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_PRINT_OPERATION_H */
diff --git a/libide/editor/ide-editor-tweak-widget.c b/libide/editor/ide-editor-tweak-widget.c
new file mode 100644
index 0000000..7304c5f
--- /dev/null
+++ b/libide/editor/ide-editor-tweak-widget.c
@@ -0,0 +1,184 @@
+/* ide-editor-tweak-widget.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "editor-tweak"
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+
+#include "ide-editor-tweak-widget.h"
+#include "ide-gtk.h"
+
+struct _IdeEditorTweakWidget
+{
+ GtkBin parent_instance;
+
+ GtkSearchEntry *entry;
+ GtkListBox *list_box;
+};
+
+G_DEFINE_TYPE (IdeEditorTweakWidget, ide_editor_tweak_widget, GTK_TYPE_BIN)
+
+static GQuark langQuark;
+
+GtkWidget *
+ide_editor_tweak_widget_new (void)
+{
+ return g_object_new (IDE_TYPE_EDITOR_TWEAK_WIDGET, NULL);
+}
+
+static gboolean
+ide_editor_tweak_widget_filter_func (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ GtkSourceLanguage *language;
+ GtkWidget *child;
+ const gchar *needle = user_data;
+ const gchar *lang_id;
+ const gchar *lang_name;
+ g_autofree gchar *lang_name_cf = NULL;
+
+ g_return_val_if_fail (GTK_IS_LIST_BOX_ROW (row), FALSE);
+ g_return_val_if_fail (needle, FALSE);
+
+ child = gtk_bin_get_child (GTK_BIN (row));
+ language = g_object_get_qdata (G_OBJECT (child), langQuark);
+ lang_id = gtk_source_language_get_id (language);
+ lang_name = gtk_source_language_get_name (language);
+ lang_name_cf = g_utf8_casefold (lang_name, -1);
+
+ if (strstr (lang_id, needle) || strstr (lang_name, needle) || strstr (lang_name_cf, needle))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+ide_editor_tweak_widget_entry_changed (IdeEditorTweakWidget *self,
+ GtkEntry *entry)
+{
+ const gchar *text;
+ gchar *text_cf;
+
+ g_return_if_fail (IDE_IS_EDITOR_TWEAK_WIDGET (self));
+ g_return_if_fail (GTK_IS_ENTRY (entry));
+
+ text = gtk_entry_get_text (entry);
+
+ if (ide_str_empty0 (text))
+ gtk_list_box_set_filter_func (self->list_box, NULL, NULL, NULL);
+ else
+ {
+ text_cf = g_utf8_casefold (text, -1);
+ gtk_list_box_set_filter_func (self->list_box, ide_editor_tweak_widget_filter_func,
+ text_cf, g_free);
+ }
+}
+
+static void
+ide_editor_tweak_widget_row_activated (IdeEditorTweakWidget *self,
+ GtkListBoxRow *row,
+ GtkListBox *list_box)
+{
+ GtkSourceLanguage *lang;
+ const gchar *lang_id;
+ GtkWidget *child;
+ GVariant *param;
+
+ g_return_if_fail (IDE_IS_EDITOR_TWEAK_WIDGET (self));
+ g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
+ g_return_if_fail (GTK_IS_LIST_BOX (list_box));
+
+ child = gtk_bin_get_child (GTK_BIN (row));
+ lang = g_object_get_qdata (G_OBJECT (child), langQuark);
+
+ if (lang)
+ {
+ lang_id = gtk_source_language_get_id (lang);
+ param = g_variant_new_string (lang_id);
+ ide_widget_action (GTK_WIDGET (self), "view", "language", param);
+ }
+}
+
+static void
+ide_editor_tweak_widget_constructed (GObject *object)
+{
+ IdeEditorTweakWidget *self = (IdeEditorTweakWidget *)object;
+ GtkSourceLanguageManager *manager;
+ GtkSourceLanguage *lang;
+ const gchar * const *lang_ids;
+ guint i;
+
+ g_return_if_fail (IDE_IS_EDITOR_TWEAK_WIDGET (self));
+
+ G_OBJECT_CLASS (ide_editor_tweak_widget_parent_class)->constructed (object);
+
+ manager = gtk_source_language_manager_get_default ();
+ lang_ids = gtk_source_language_manager_get_language_ids (manager);
+
+ for (i = 0; lang_ids [i]; i++)
+ {
+ GtkWidget *row;
+
+ lang = gtk_source_language_manager_get_language (manager, lang_ids [i]);
+ row = g_object_new (GTK_TYPE_LABEL,
+ "label", gtk_source_language_get_name (lang),
+ "visible", TRUE,
+ "xalign", 0.0f,
+ "margin-end", 6,
+ "margin-start", 6,
+ "margin-top", 3,
+ "margin-bottom", 3,
+ NULL);
+ g_object_set_qdata (G_OBJECT (row), langQuark, lang);
+ gtk_list_box_insert (self->list_box, row, -1);
+ }
+
+ g_signal_connect_object (self->entry,
+ "changed",
+ G_CALLBACK (ide_editor_tweak_widget_entry_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->list_box,
+ "row-activated",
+ G_CALLBACK (ide_editor_tweak_widget_row_activated),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+ide_editor_tweak_widget_class_init (IdeEditorTweakWidgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->constructed = ide_editor_tweak_widget_constructed;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/ui/ide-editor-tweak-widget.ui");
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorTweakWidget, entry);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorTweakWidget, list_box);
+
+ langQuark = g_quark_from_static_string ("GtkSourceLanguage");
+}
+
+static void
+ide_editor_tweak_widget_init (IdeEditorTweakWidget *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/libide/editor/ide-editor-tweak-widget.h b/libide/editor/ide-editor-tweak-widget.h
new file mode 100644
index 0000000..4cbc5b1
--- /dev/null
+++ b/libide/editor/ide-editor-tweak-widget.h
@@ -0,0 +1,33 @@
+/* ide-editor-tweak-widget.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_TWEAK_WIDGET_H
+#define IDE_EDITOR_TWEAK_WIDGET_H
+
+#include <gtk/gtk.h>
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_TWEAK_WIDGET (ide_editor_tweak_widget_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorTweakWidget, ide_editor_tweak_widget, IDE, EDITOR_TWEAK_WIDGET, GtkBin)
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_TWEAK_WIDGET_H */
diff --git a/libide/editor/ide-editor-view-actions.c b/libide/editor/ide-editor-view-actions.c
new file mode 100644
index 0000000..8604215
--- /dev/null
+++ b/libide/editor/ide-editor-view-actions.c
@@ -0,0 +1,824 @@
+/* ide-editor-view-actions.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-editor-view"
+
+#include <glib/gi18n.h>
+#include <string.h>
+
+#include "ide-editor-frame-private.h"
+#include "ide-editor-print-operation.h"
+#include "ide-editor-view-actions.h"
+#include "ide-editor-view-private.h"
+#include "ide-gtk.h"
+
+static void
+ide_editor_view_actions_source_view_notify (IdeSourceView *source_view,
+ GParamSpec *pspec,
+ GActionMap *actions)
+{
+ g_autoptr(GVariant) param = NULL;
+ GtkSourceView *gsv;
+ GAction *action = NULL;
+
+ g_assert (IDE_IS_SOURCE_VIEW (source_view));
+ g_assert (pspec != NULL);
+ g_assert (G_IS_ACTION_MAP (actions));
+
+ gsv = GTK_SOURCE_VIEW (source_view);
+
+ if (g_str_equal (pspec->name, "show-line-numbers"))
+ {
+ gboolean show_line_numbers;
+
+ action = g_action_map_lookup_action (actions, "show-line-numbers");
+ show_line_numbers = gtk_source_view_get_show_line_numbers (gsv);
+ param = g_variant_new_boolean (show_line_numbers);
+ }
+ else if (g_str_equal (pspec->name, "show-right-margin"))
+ {
+ gboolean show_right_margin;
+
+ action = g_action_map_lookup_action (actions, "show-right-margin");
+ show_right_margin = gtk_source_view_get_show_right_margin (gsv);
+ param = g_variant_new_boolean (show_right_margin);
+ }
+ else if (g_str_equal (pspec->name, "highlight-current-line"))
+ {
+ gboolean highlight_current_line;
+
+ action = g_action_map_lookup_action (actions, "highlight-current-line");
+ g_object_get (gsv, "highlight-current-line", &highlight_current_line, NULL);
+ param = g_variant_new_boolean (highlight_current_line);
+ }
+ else if (g_str_equal (pspec->name, "auto-indent"))
+ {
+ gboolean auto_indent;
+
+ action = g_action_map_lookup_action (actions, "auto-indent");
+ g_object_get (source_view, "auto-indent", &auto_indent, NULL);
+ param = g_variant_new_boolean (auto_indent);
+ }
+ else if (g_str_equal (pspec->name, "tab-width"))
+ {
+ guint tab_width;
+
+ action = g_action_map_lookup_action (actions, "tab-width");
+ g_object_get (source_view, "tab-width", &tab_width, NULL);
+ param = g_variant_new_int32 (tab_width);
+ }
+ else if (g_str_equal (pspec->name, "insert-spaces-instead-of-tabs"))
+ {
+ gboolean use_spaces;
+
+ action = g_action_map_lookup_action (actions, "use-spaces");
+ g_object_get (source_view, "insert-spaces-instead-of-tabs", &use_spaces, NULL);
+ param = g_variant_new_boolean (use_spaces);
+ }
+ else if (g_str_equal (pspec->name, "smart-backspace"))
+ {
+ gboolean smart_backspace;
+
+ action = g_action_map_lookup_action (actions, "smart-backspace");
+ g_object_get (source_view, "smart-backspace", &smart_backspace, NULL);
+ param = g_variant_new_boolean (smart_backspace);
+ }
+
+ if (action && param)
+ {
+ g_simple_action_set_state (G_SIMPLE_ACTION (action), param);
+ param = NULL;
+ }
+}
+
+static void
+ide_editor_view_actions_language (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ GtkSourceLanguageManager *manager;
+ GtkSourceLanguage *language;
+ GtkSourceBuffer *buffer;
+ const gchar *name;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ manager = gtk_source_language_manager_get_default ();
+ name = g_variant_get_string (variant, NULL);
+ buffer = GTK_SOURCE_BUFFER (self->document);
+
+ if (name != NULL)
+ {
+ language = gtk_source_language_manager_get_language (manager, name);
+ gtk_source_buffer_set_language (buffer, language);
+ ide_editor_view_actions_update (self);
+ }
+}
+
+#define STATE_HANDLER_BOOLEAN(name,propname) \
+static void \
+ide_editor_view_actions_##name (GSimpleAction *action, \
+ GVariant *variant, \
+ gpointer user_data) \
+{ \
+ IdeEditorView *self = user_data; \
+ gboolean val; \
+ \
+ g_assert (IDE_IS_EDITOR_VIEW (self)); \
+ \
+ val = g_variant_get_boolean (variant); \
+ g_object_set (self->frame1->source_view, propname, val, NULL); \
+ if (self->frame2) \
+ g_object_set (self->frame2->source_view, propname, val, NULL); \
+}
+
+#define STATE_HANDLER_INT(name,propname) \
+static void \
+ide_editor_view_actions_##name (GSimpleAction *action, \
+ GVariant *variant, \
+ gpointer user_data) \
+{ \
+ IdeEditorView *self = user_data; \
+ gint val; \
+ \
+ g_assert (IDE_IS_EDITOR_VIEW (self)); \
+ \
+ val = g_variant_get_int32 (variant); \
+ g_object_set (self->frame1->source_view, propname, val, NULL); \
+ if (self->frame2) \
+ g_object_set (self->frame2->source_view, propname, val, NULL); \
+}
+
+STATE_HANDLER_BOOLEAN (auto_indent, "auto-indent")
+STATE_HANDLER_BOOLEAN (show_line_numbers, "show-line-numbers")
+STATE_HANDLER_BOOLEAN (show_right_margin, "show-right-margin")
+STATE_HANDLER_BOOLEAN (highlight_current_line, "highlight-current-line")
+STATE_HANDLER_BOOLEAN (use_spaces, "insert-spaces-instead-of-tabs")
+STATE_HANDLER_BOOLEAN (smart_backspace, "smart-backspace")
+STATE_HANDLER_INT (tab_width, "tab-width")
+
+static void
+save_file_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+ g_autoptr(IdeEditorView) self = user_data;
+ GError *error = NULL;
+
+ if (!ide_buffer_manager_save_file_finish (buffer_manager, result, &error))
+ {
+ /* info bar */
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+
+ ide_widget_hide_with_fade (GTK_WIDGET (self->progress_bar));
+}
+
+static void
+ide_editor_view_actions__save_temp_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+ GError *error = NULL;
+
+ if (!ide_buffer_manager_save_file_finish (buffer_manager, result, &error))
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_object_unref (self);
+}
+
+static void
+save_temp_response (GtkWidget *widget,
+ gint response,
+ gpointer user_data)
+{
+ g_autoptr(IdeEditorView) self = user_data;
+ g_autoptr(GFile) target = NULL;
+ g_autoptr(IdeProgress) progress = NULL;
+ GtkFileChooser *chooser = (GtkFileChooser *)widget;
+
+ g_assert (GTK_IS_FILE_CHOOSER (chooser));
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ switch (response)
+ {
+ case GTK_RESPONSE_OK:
+ target = gtk_file_chooser_get_file (chooser);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ default:
+ break;
+ }
+
+ if (target != NULL)
+ {
+ IdeBufferManager *buffer_manager;
+ IdeContext *context;
+ IdeProject *project;
+ IdeBuffer *buffer = IDE_BUFFER (self->document);
+ g_autoptr(IdeFile) file = NULL;
+
+ context = ide_buffer_get_context (buffer);
+ project = ide_context_get_project (context);
+ buffer_manager = ide_context_get_buffer_manager (context);
+ file = ide_project_get_project_file (project, target);
+
+ ide_buffer_set_file (buffer, file);
+
+ ide_buffer_manager_save_file_async (buffer_manager,
+ buffer,
+ file,
+ &progress,
+ NULL,
+ ide_editor_view_actions__save_temp_cb,
+ g_object_ref (self));
+ }
+
+ gtk_widget_destroy (widget);
+}
+
+static void
+ide_editor_view_actions_save (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ IdeContext *context;
+ IdeBufferManager *buffer_manager;
+ IdeFile *file;
+ IdeProgress *progress = NULL;
+ IdeVcs *vcs;
+ GFile *workdir;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ file = ide_buffer_get_file (IDE_BUFFER (self->document));
+ context = ide_buffer_get_context (IDE_BUFFER (self->document));
+ buffer_manager = ide_context_get_buffer_manager (context);
+ vcs = ide_context_get_vcs (context);
+ workdir = ide_vcs_get_working_directory (vcs);
+
+ if (ide_file_get_is_temporary (file))
+ {
+ GtkDialog *dialog;
+ GtkWidget *toplevel;
+ GtkWidget *suggested;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+ dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
+ "action", GTK_FILE_CHOOSER_ACTION_SAVE,
+ "do-overwrite-confirmation", TRUE,
+ "local-only", FALSE,
+ "modal", TRUE,
+ "select-multiple", FALSE,
+ "show-hidden", FALSE,
+ "transient-for", toplevel,
+ "title", _("Save Document"),
+ NULL);
+
+ gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (dialog), workdir, NULL);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("Cancel"), GTK_RESPONSE_CANCEL,
+ _("Save"), GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ suggested = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+ gtk_style_context_add_class (gtk_widget_get_style_context (suggested),
+ GTK_STYLE_CLASS_SUGGESTED_ACTION);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (save_temp_response), g_object_ref (self));
+
+ gtk_window_present (GTK_WINDOW (dialog));
+
+ return;
+ }
+
+ ide_buffer_manager_save_file_async (buffer_manager,
+ IDE_BUFFER (self->document),
+ file,
+ &progress,
+ NULL,
+ save_file_cb,
+ g_object_ref (self));
+ g_object_bind_property (progress, "fraction", self->progress_bar, "fraction",
+ G_BINDING_SYNC_CREATE);
+ gtk_widget_show (GTK_WIDGET (self->progress_bar));
+ g_clear_object (&progress);
+}
+
+static void
+ide_editor_view_actions__save_as_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+ GError *error = NULL;
+
+ if (!ide_buffer_manager_save_file_finish (buffer_manager, result, &error))
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_object_unref (self);
+}
+
+static void
+save_as_response (GtkWidget *widget,
+ gint response,
+ gpointer user_data)
+{
+ g_autoptr(IdeEditorView) self = user_data;
+ g_autoptr(GFile) target = NULL;
+ g_autoptr(IdeProgress) progress = NULL;
+ GtkFileChooser *chooser = (GtkFileChooser *)widget;
+
+ g_assert (GTK_IS_FILE_CHOOSER (chooser));
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ switch (response)
+ {
+ case GTK_RESPONSE_OK:
+ target = gtk_file_chooser_get_file (chooser);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ default:
+ break;
+ }
+
+ if (target != NULL)
+ {
+ IdeBufferManager *buffer_manager;
+ IdeContext *context;
+ IdeProject *project;
+ IdeBuffer *buffer = IDE_BUFFER (self->document);
+ g_autoptr(IdeFile) file = NULL;
+
+ context = ide_buffer_get_context (buffer);
+ project = ide_context_get_project (context);
+ buffer_manager = ide_context_get_buffer_manager (context);
+ file = ide_project_get_project_file (project, target);
+
+ ide_buffer_manager_save_file_async (buffer_manager,
+ buffer,
+ file,
+ &progress,
+ NULL,
+ ide_editor_view_actions__save_as_cb,
+ g_object_ref (self));
+ }
+
+ gtk_widget_destroy (widget);
+}
+
+static void
+ide_editor_view_actions_save_as (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ IdeBuffer *buffer;
+ GtkWidget *suggested;
+ GtkWidget *toplevel;
+ GtkWidget *dialog;
+ IdeFile *file;
+ GFile *gfile;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+ dialog = g_object_new (GTK_TYPE_FILE_CHOOSER_DIALOG,
+ "action", GTK_FILE_CHOOSER_ACTION_SAVE,
+ "do-overwrite-confirmation", TRUE,
+ "local-only", FALSE,
+ "modal", TRUE,
+ "select-multiple", FALSE,
+ "show-hidden", FALSE,
+ "transient-for", toplevel,
+ "title", _("Save Document As"),
+ NULL);
+
+ buffer = IDE_BUFFER (self->document);
+ file = ide_buffer_get_file (buffer);
+ gfile = ide_file_get_file (file);
+
+ if (gfile != NULL)
+ gtk_file_chooser_set_file (GTK_FILE_CHOOSER (dialog), gfile, NULL);
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("Cancel"), GTK_RESPONSE_CANCEL,
+ _("Save"), GTK_RESPONSE_OK,
+ NULL);
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+
+ suggested = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+ gtk_style_context_add_class (gtk_widget_get_style_context (suggested),
+ GTK_STYLE_CLASS_SUGGESTED_ACTION);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (save_as_response), g_object_ref (self));
+
+ gtk_window_present (GTK_WINDOW (dialog));
+}
+
+static gboolean
+set_split_view (gpointer data)
+{
+ g_autoptr(IdeEditorView) self = data;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ ide_layout_view_set_split_view (IDE_LAYOUT_VIEW (self), (self->frame2 == NULL));
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+ide_editor_view_actions_toggle_split (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ g_timeout_add (0, set_split_view, g_object_ref (self));
+}
+
+static void
+ide_editor_view_actions_close (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ /* just close our current frame if we have split view */
+ if (self->frame2 != NULL)
+ {
+ /* todo: swap frame1/frame2 if frame2 was last focused. */
+ g_timeout_add (0, set_split_view, g_object_ref (self));
+ }
+ else
+ {
+ ide_widget_action (GTK_WIDGET (self), "view-stack", "close", NULL);
+ }
+}
+
+static void
+find_other_file_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(IdeEditorView) self = user_data;
+ g_autoptr(IdeFile) ret = NULL;
+ IdeFile *file = (IdeFile *)object;
+
+ ret = ide_file_find_other_finish (file, result, NULL);
+
+ if (ret != NULL)
+ {
+ IdeWorkbench *workbench;
+ GFile *gfile;
+
+ gfile = ide_file_get_file (ret);
+ workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+ ide_workbench_open_files_async (workbench, &gfile, 1, NULL, NULL, NULL);
+ }
+}
+
+static void
+ide_editor_view_actions_find_other_file (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ IdeFile *file;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ file = ide_buffer_get_file (IDE_BUFFER (self->document));
+ ide_file_find_other_async (file, NULL, find_other_file_cb, g_object_ref (self));
+}
+
+static void
+ide_editor_view_actions_reload_buffer_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+ g_autoptr(IdeEditorView) self = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(IdeBuffer) buffer = NULL;
+
+ g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ gtk_revealer_set_reveal_child (self->modified_revealer, FALSE);
+
+ if (!(buffer = ide_buffer_manager_load_file_finish (buffer_manager, result, &error)))
+ {
+ g_warning ("%s", error->message);
+ }
+ else
+ {
+ g_signal_emit_by_name (self->frame1->source_view, "movement",
+ IDE_SOURCE_VIEW_MOVEMENT_FIRST_LINE, FALSE, TRUE,
+ FALSE);
+ if (self->frame2 != NULL)
+ g_signal_emit_by_name (self->frame2->source_view, "movement",
+ IDE_SOURCE_VIEW_MOVEMENT_FIRST_LINE, FALSE, TRUE,
+ FALSE);
+ }
+
+ ide_widget_hide_with_fade (GTK_WIDGET (self->progress_bar));
+}
+
+static void
+ide_editor_view_actions_reload_buffer (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ IdeContext *context;
+ IdeBufferManager *buffer_manager;
+ IdeFile *file;
+ g_autoptr(IdeProgress) progress = NULL;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ context = ide_buffer_get_context (IDE_BUFFER (self->document));
+ file = ide_buffer_get_file (IDE_BUFFER (self->document));
+
+ buffer_manager = ide_context_get_buffer_manager (context);
+
+ gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->progress_bar), 0.0);
+ gtk_widget_show (GTK_WIDGET (self->progress_bar));
+
+ ide_buffer_manager_load_file_async (buffer_manager,
+ file,
+ TRUE,
+ &progress,
+ NULL,
+ ide_editor_view_actions_reload_buffer_cb,
+ g_object_ref (self));
+
+ g_object_bind_property (progress, "fraction", self->progress_bar, "fraction",
+ G_BINDING_SYNC_CREATE);
+}
+
+static void
+ide_editor_view_actions_preview (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+#if 0
+ IdeEditorView *self = user_data;
+ GtkSourceLanguage *language;
+ const gchar *lang_id = NULL;
+ g_autoptr(GbDocument) document = NULL;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->document));
+ if (!language)
+ return;
+
+ lang_id = gtk_source_language_get_id (language);
+ if (!lang_id)
+ return;
+
+ if (g_str_equal (lang_id, "html"))
+ {
+ document = g_object_new (GB_TYPE_HTML_DOCUMENT,
+ "buffer", self->document,
+ NULL);
+ }
+ else if (g_str_equal (lang_id, "markdown"))
+ {
+ document = g_object_new (GB_TYPE_HTML_DOCUMENT,
+ "buffer", self->document,
+ NULL);
+ gb_html_document_set_transform_func (GB_HTML_DOCUMENT (document),
+ gb_html_markdown_transform);
+ }
+
+ if (document)
+ {
+ GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (self));
+
+ while (parent && !IDE_IS_LAYOUT_GRID (parent))
+ parent = gtk_widget_get_parent (parent);
+
+ if (parent == NULL)
+ {
+ while (parent && !IDE_IS_LAYOUT_STACK (parent))
+ parent = gtk_widget_get_parent (parent);
+ g_assert (IDE_IS_LAYOUT_STACK (parent));
+ ide_layout_stack_focus_document (IDE_LAYOUT_STACK (parent), document);
+ return;
+ }
+
+ g_assert (IDE_IS_LAYOUT_GRID (parent));
+ gb_view_grid_focus_document (IDE_LAYOUT_GRID (parent), document);
+ }
+#endif
+}
+
+static void
+ide_editor_view_actions_reveal (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ IdeWorkbench *workbench;
+ IdeFile *file;
+ GFile *gfile;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ file = ide_buffer_get_file (IDE_BUFFER (self->document));
+ gfile = ide_file_get_file (file);
+ workbench = ide_widget_get_workbench (GTK_WIDGET (self));
+
+#if 0
+ gb_workbench_reveal_file (workbench, gfile);
+#endif
+}
+
+static void
+handle_print_result (IdeEditorView *self,
+ GtkPrintOperation *operation,
+ GtkPrintOperationResult result)
+{
+ if (result == GTK_PRINT_OPERATION_RESULT_ERROR)
+ {
+ GError *error = NULL;
+
+ gtk_print_operation_get_error (operation, &error);
+
+ /* info bar */
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ }
+}
+
+static void
+print_done (GtkPrintOperation *operation,
+ GtkPrintOperationResult result,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+
+ handle_print_result (self, operation, result);
+
+ g_object_unref (operation);
+ g_object_unref (self);
+}
+
+static void
+ide_editor_view_actions_print (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+ GtkWidget *toplevel;
+ g_autoptr(IdeEditorPrintOperation) operation;
+ GtkPrintOperationResult result;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+
+ operation = ide_editor_print_operation_new (self->frame1->source_view);
+
+ /* keep a ref until "done" is emitted */
+ g_object_ref (operation);
+
+ g_signal_connect_after (operation, "done", G_CALLBACK (print_done), g_object_ref (self));
+
+ result = gtk_print_operation_run (GTK_PRINT_OPERATION (operation),
+ GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
+ GTK_WINDOW (toplevel),
+ NULL);
+
+ handle_print_result (self, GTK_PRINT_OPERATION (operation), result);
+}
+
+static void
+ide_editor_view_actions_goto_line (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ IdeEditorView *self = user_data;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ gtk_widget_activate (GTK_WIDGET (self->goto_line_button));
+}
+
+static GActionEntry IdeEditorViewActions[] = {
+ { "auto-indent", NULL, NULL, "false", ide_editor_view_actions_auto_indent },
+ { "close", ide_editor_view_actions_close },
+ { "find-other-file", ide_editor_view_actions_find_other_file },
+ { "goto-line", ide_editor_view_actions_goto_line },
+ { "highlight-current-line", NULL, NULL, "false", ide_editor_view_actions_highlight_current_line },
+ { "language", NULL, "s", "''", ide_editor_view_actions_language },
+ { "preview", ide_editor_view_actions_preview },
+ { "reload-buffer", ide_editor_view_actions_reload_buffer },
+ { "reveal", ide_editor_view_actions_reveal },
+ { "save", ide_editor_view_actions_save },
+ { "save-as", ide_editor_view_actions_save_as },
+ { "print", ide_editor_view_actions_print },
+ { "show-line-numbers", NULL, NULL, "false", ide_editor_view_actions_show_line_numbers },
+ { "show-right-margin", NULL, NULL, "false", ide_editor_view_actions_show_right_margin },
+ { "smart-backspace", NULL, NULL, "false", ide_editor_view_actions_smart_backspace },
+ { "tab-width", NULL, "i", "8", ide_editor_view_actions_tab_width },
+ { "toggle-split", ide_editor_view_actions_toggle_split },
+ { "use-spaces", NULL, "b", "false", ide_editor_view_actions_use_spaces },
+};
+
+void
+ide_editor_view_actions_init (IdeEditorView *self)
+{
+ g_autoptr(GSimpleActionGroup) group = NULL;
+
+ group = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group), IdeEditorViewActions,
+ G_N_ELEMENTS (IdeEditorViewActions), self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self), "view", G_ACTION_GROUP (group));
+ gtk_widget_insert_action_group (GTK_WIDGET (self->tweak_widget), "view", G_ACTION_GROUP (group));
+
+#define WATCH_PROPERTY(name) \
+ G_STMT_START { \
+ g_signal_connect (self->frame1->source_view, \
+ "notify::"name, \
+ G_CALLBACK (ide_editor_view_actions_source_view_notify), \
+ group); \
+ g_object_notify (G_OBJECT (self->frame1->source_view), name); \
+ } G_STMT_END
+
+ WATCH_PROPERTY ("auto-indent");
+ WATCH_PROPERTY ("highlight-current-line");
+ WATCH_PROPERTY ("insert-spaces-instead-of-tabs");
+ WATCH_PROPERTY ("show-line-numbers");
+ WATCH_PROPERTY ("show-right-margin");
+ WATCH_PROPERTY ("smart-backspace");
+ WATCH_PROPERTY ("tab-width");
+
+#undef WATCH_PROPERTY
+}
+
+void
+ide_editor_view_actions_update (IdeEditorView *self)
+{
+ GtkSourceLanguage *language;
+ const gchar *lang_id = NULL;
+ GActionGroup *group;
+ GAction *action;
+ gboolean enabled;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ group = gtk_widget_get_action_group (GTK_WIDGET (self), "view");
+ if (!G_IS_SIMPLE_ACTION_GROUP (group))
+ return;
+
+ /* update preview sensitivity */
+ language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->document));
+ if (language)
+ lang_id = gtk_source_language_get_id (language);
+ enabled = ((g_strcmp0 (lang_id, "html") == 0) ||
+ (g_strcmp0 (lang_id, "markdown") == 0));
+ action = g_action_map_lookup_action (G_ACTION_MAP (group), "preview");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+}
diff --git a/libide/editor/ide-editor-view-actions.h b/libide/editor/ide-editor-view-actions.h
new file mode 100644
index 0000000..daab752
--- /dev/null
+++ b/libide/editor/ide-editor-view-actions.h
@@ -0,0 +1,31 @@
+/* ide-editor-view-actions.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_ACTIONS_H
+#define IDE_EDITOR_VIEW_ACTIONS_H
+
+#include "ide-editor-view.h"
+
+G_BEGIN_DECLS
+
+void ide_editor_view_actions_init (IdeEditorView *self);
+void ide_editor_view_actions_update (IdeEditorView *self);
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_ACTIONS_H */
diff --git a/libide/editor/ide-editor-view-addin-private.h b/libide/editor/ide-editor-view-addin-private.h
new file mode 100644
index 0000000..967e985
--- /dev/null
+++ b/libide/editor/ide-editor-view-addin-private.h
@@ -0,0 +1,34 @@
+/* ide-editor-view-addin-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_ADDIN_PRIVATE_H
+#define IDE_EDITOR_VIEW_ADDIN_PRIVATE_H
+
+#include "ide-editor-view-addin.h"
+
+G_BEGIN_DECLS
+
+void ide_editor_view_addin_language_changed (IdeEditorViewAddin *self,
+ const gchar *language_id);
+void ide_editor_view_addin_load (IdeEditorViewAddin *self,
+ IdeEditorView *view);
+void ide_editor_view_addin_unload (IdeEditorViewAddin *self,
+ IdeEditorView *view);
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_ADDIN_PRIVATE_H */
diff --git a/libide/editor/ide-editor-view-addin.c b/libide/editor/ide-editor-view-addin.c
new file mode 100644
index 0000000..98eb544
--- /dev/null
+++ b/libide/editor/ide-editor-view-addin.c
@@ -0,0 +1,64 @@
+/* ide-editor-view-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-editor-view-addin.h"
+
+G_DEFINE_INTERFACE (IdeEditorViewAddin, ide_editor_view_addin, G_TYPE_OBJECT)
+
+static void
+dummy_vfunc (IdeEditorViewAddin *self,
+ IdeEditorView *view)
+{
+}
+
+static void
+ide_editor_view_addin_default_init (IdeEditorViewAddinInterface *iface)
+{
+ iface->load = dummy_vfunc;
+ iface->unload = dummy_vfunc;
+}
+
+void
+ide_editor_view_addin_load (IdeEditorViewAddin *self,
+ IdeEditorView *view)
+{
+ g_return_if_fail (IDE_IS_EDITOR_VIEW_ADDIN (self));
+ g_return_if_fail (IDE_IS_EDITOR_VIEW (view));
+
+ IDE_EDITOR_VIEW_ADDIN_GET_IFACE (self)->load (self, view);
+}
+
+void
+ide_editor_view_addin_unload (IdeEditorViewAddin *self,
+ IdeEditorView *view)
+{
+ g_return_if_fail (IDE_IS_EDITOR_VIEW_ADDIN (self));
+ g_return_if_fail (IDE_IS_EDITOR_VIEW (view));
+
+ IDE_EDITOR_VIEW_ADDIN_GET_IFACE (self)->unload (self, view);
+}
+
+void
+ide_editor_view_addin_language_changed (IdeEditorViewAddin *self,
+ const gchar *language_id)
+{
+ g_return_if_fail (IDE_IS_EDITOR_VIEW_ADDIN (self));
+
+ if (IDE_EDITOR_VIEW_ADDIN_GET_IFACE (self)->language_changed)
+ IDE_EDITOR_VIEW_ADDIN_GET_IFACE (self)->language_changed (self, language_id);
+}
diff --git a/libide/editor/ide-editor-view-addin.h b/libide/editor/ide-editor-view-addin.h
new file mode 100644
index 0000000..8622762
--- /dev/null
+++ b/libide/editor/ide-editor-view-addin.h
@@ -0,0 +1,44 @@
+/* ide-editor-view-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_ADDIN_H
+#define IDE_EDITOR_VIEW_ADDIN_H
+
+#include "ide-editor-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_VIEW_ADDIN (ide_editor_view_addin_get_type ())
+
+G_DECLARE_INTERFACE (IdeEditorViewAddin, ide_editor_view_addin, IDE, EDITOR_VIEW_ADDIN, GObject)
+
+struct _IdeEditorViewAddinInterface
+{
+ GTypeInterface parent;
+
+ void (*load) (IdeEditorViewAddin *self,
+ IdeEditorView *view);
+ void (*unload) (IdeEditorViewAddin *self,
+ IdeEditorView *view);
+ void (*language_changed) (IdeEditorViewAddin *self,
+ const gchar *language_id);
+};
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_ADDIN_H */
diff --git a/libide/editor/ide-editor-view-private.h b/libide/editor/ide-editor-view-private.h
new file mode 100644
index 0000000..d5968fd
--- /dev/null
+++ b/libide/editor/ide-editor-view-private.h
@@ -0,0 +1,60 @@
+/* ide-editor-view-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_PRIVATE_H
+#define IDE_EDITOR_VIEW_PRIVATE_H
+
+#include <libpeas/peas.h>
+#include <gtk/gtk.h>
+
+#include "egg-simple-popover.h"
+
+#include "ide-buffer.h"
+#include "ide-editor-frame.h"
+#include "ide-editor-tweak-widget.h"
+#include "ide-layout-view.h"
+
+G_BEGIN_DECLS
+
+struct _IdeEditorView
+{
+ IdeLayoutView parent_instance;
+
+ IdeBuffer *document;
+ PeasExtensionSet *extensions;
+ GSettings *settings;
+ gchar *title;
+
+ GtkLabel *cursor_label;
+ IdeEditorFrame *frame1;
+ IdeEditorFrame *frame2;
+ IdeEditorFrame *last_focused_frame;
+ GtkButton *modified_cancel_button;
+ GtkRevealer *modified_revealer;
+ GtkPaned *paned;
+ GtkProgressBar *progress_bar;
+ GtkMenuButton *tweak_button;
+ IdeEditorTweakWidget *tweak_widget;
+ GtkMenuButton *goto_line_button;
+ EggSimplePopover *goto_line_popover;
+ GtkButton *warning_button;
+};
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_PRIVATE_H */
diff --git a/libide/editor/ide-editor-view.c b/libide/editor/ide-editor-view.c
new file mode 100644
index 0000000..64ac974
--- /dev/null
+++ b/libide/editor/ide-editor-view.c
@@ -0,0 +1,932 @@
+/* ide-editor-view.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-editor-view"
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+
+#include "egg-simple-popover.h"
+
+#include "ide-editor-frame-private.h"
+#include "ide-editor-view-actions.h"
+#include "ide-editor-view-addin.h"
+#include "ide-editor-view.h"
+#include "ide-editor-view-addin-private.h"
+#include "ide-editor-view-private.h"
+#include "ide-macros.h"
+
+G_DEFINE_TYPE (IdeEditorView, ide_editor_view, IDE_TYPE_LAYOUT_VIEW)
+
+enum {
+ PROP_0,
+ PROP_DOCUMENT,
+ LAST_PROP
+};
+
+enum {
+ REQUEST_DOCUMENTATION,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static IdeEditorFrame *
+ide_editor_view_get_last_focused (IdeEditorView *self)
+{
+ g_assert (self->last_focused_frame != NULL);
+
+ return self->last_focused_frame;
+}
+
+static void
+ide_editor_view_navigate_to (IdeLayoutView *view,
+ IdeSourceLocation *location)
+{
+ IdeEditorView *self = (IdeEditorView *)view;
+ IdeEditorFrame *frame;
+ GtkTextMark *insert;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ guint line;
+ guint line_offset;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (location != NULL);
+
+ frame = ide_editor_view_get_last_focused (self);
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (frame->source_view));
+
+ line = ide_source_location_get_line (location);
+ line_offset = ide_source_location_get_line_offset (location);
+
+ gtk_text_buffer_get_iter_at_line (buffer, &iter, line);
+ for (; line_offset; line_offset--)
+ if (gtk_text_iter_ends_line (&iter) || !gtk_text_iter_forward_char (&iter))
+ break;
+
+ gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+ insert = gtk_text_buffer_get_insert (buffer);
+ gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (frame->source_view), insert, 0.0, TRUE, 1.0, 0.5);
+
+ g_signal_emit_by_name (frame->source_view, "save-insert-mark");
+
+ IDE_EXIT;
+}
+
+static gboolean
+language_to_string (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ GtkSourceLanguage *language;
+
+ language = g_value_get_object (from_value);
+
+ if (language != NULL)
+ g_value_set_string (to_value, gtk_source_language_get_name (language));
+ else
+ g_value_set_string (to_value, _("Plain Text"));
+
+ return TRUE;
+}
+
+static gboolean
+ide_editor_view_get_modified (IdeLayoutView *view)
+{
+ IdeEditorView *self = (IdeEditorView *)view;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ return gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (self->document));
+}
+
+static void
+ide_editor_view__buffer_modified_changed (IdeEditorView *self,
+ GParamSpec *pspec,
+ GtkTextBuffer *buffer)
+{
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ g_object_notify (G_OBJECT (self), "modified");
+}
+
+static void
+force_scroll_to_top (IdeSourceView *source_view)
+{
+ GtkAdjustment *vadj;
+ GtkAdjustment *hadj;
+ gdouble lower;
+
+ /*
+ * FIXME:
+ *
+ * See the comment in ide_editor_view__buffer_changed_on_volume()
+ */
+
+ vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (source_view));
+ hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (source_view));
+
+ lower = gtk_adjustment_get_lower (vadj);
+ gtk_adjustment_set_value (vadj, lower);
+
+ lower = gtk_adjustment_get_lower (hadj);
+ gtk_adjustment_set_value (hadj, lower);
+}
+
+static gboolean
+no_really_scroll_to_the_top (gpointer data)
+{
+ g_autoptr(IdeEditorView) self = data;
+
+ force_scroll_to_top (self->frame1->source_view);
+ if (self->frame2 != NULL)
+ force_scroll_to_top (self->frame2->source_view);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+ide_editor_view__buffer_changed_on_volume (IdeEditorView *self,
+ GParamSpec *pspec,
+ IdeBuffer *buffer)
+{
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (IDE_IS_BUFFER (buffer));
+
+ if (ide_buffer_get_changed_on_volume (buffer))
+ gtk_revealer_set_reveal_child (self->modified_revealer, TRUE);
+ else if (gtk_revealer_get_reveal_child (self->modified_revealer))
+ {
+ GtkTextIter iter;
+
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &iter, &iter);
+
+ /*
+ * FIXME:
+ *
+ * Without this delay, I see a condition with split view where the
+ * non-focused split will just render blank. Well that isn't totally
+ * correct, it renders empty gutters and proper line grid background. But
+ * no textual content. And the adjustment is way out of sync. Even
+ * changing the adjustment manually doesn't help. So whatever, I'll
+ * insert a short delay and we'll pick up after the textview has
+ * stablized.
+ */
+ g_timeout_add (10, no_really_scroll_to_the_top, g_object_ref (self));
+
+ gtk_revealer_set_reveal_child (self->modified_revealer, FALSE);
+ }
+}
+
+static const gchar *
+ide_editor_view_get_special_title (IdeLayoutView *view)
+{
+ return ((IdeEditorView *)view)->title;
+}
+
+static void
+ide_editor_view__buffer_notify_title (IdeEditorView *self,
+ GParamSpec *pspec,
+ IdeBuffer *buffer)
+{
+ const gchar *title;
+ gchar **parts;
+ gboolean needs_prefix;
+ gchar *str;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (IDE_IS_BUFFER (buffer));
+
+ g_free (self->title);
+
+ title = ide_buffer_get_title (buffer);
+
+ if (title == NULL)
+ {
+ /* translators: this shouldn't ever happen */
+ self->title = g_strdup ("untitled");
+ return;
+ }
+
+ if ((needs_prefix = (title [0] == G_DIR_SEPARATOR)))
+ title++;
+
+ parts = g_strsplit (title, G_DIR_SEPARATOR_S, 0);
+ str = g_strjoinv (" "G_DIR_SEPARATOR_S" ", parts);
+
+ if (needs_prefix)
+ {
+ self->title = g_strdup_printf (G_DIR_SEPARATOR_S" %s", str);
+ g_free (str);
+ }
+ else
+ {
+ self->title = str;
+ }
+
+ g_strfreev (parts);
+
+ g_object_notify (G_OBJECT (self), "title");
+}
+
+static void
+notify_language_foreach (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ const gchar *language_id = user_data;
+
+ ide_editor_view_addin_language_changed (IDE_EDITOR_VIEW_ADDIN (exten), language_id);
+}
+
+static void
+ide_editor_view__buffer_notify_language (IdeEditorView *self,
+ GParamSpec *pspec,
+ IdeBuffer *document)
+{
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (IDE_IS_BUFFER (document));
+
+ if (self->extensions != NULL)
+ {
+ GtkSourceLanguage *language;
+ const gchar *language_id;
+
+ language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (document));
+ language_id = language ? gtk_source_language_get_id (language) : NULL;
+
+ peas_extension_set_foreach (self->extensions,
+ notify_language_foreach,
+ (gchar *)language_id);
+ }
+}
+
+static void
+ide_editor_view__buffer_cursor_moved (IdeEditorView *self,
+ const GtkTextIter *iter,
+ GtkTextBuffer *buffer)
+{
+ GtkTextIter bounds;
+ GtkTextMark *mark;
+ gchar *str;
+ guint line;
+ gint column;
+ gint column2;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (iter != NULL);
+ g_assert (IDE_IS_BUFFER (buffer));
+
+ ide_source_view_get_visual_position (self->frame1->source_view, &line, (guint *)&column);
+
+ mark = gtk_text_buffer_get_selection_bound (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &bounds, mark);
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (self->frame1->source_view)) ||
+ gtk_text_iter_equal (&bounds, iter) ||
+ (gtk_text_iter_get_line (iter) != gtk_text_iter_get_line (&bounds)))
+ {
+ str = g_strdup_printf ("%d:%d", line + 1, column + 1);
+ gtk_label_set_text (self->cursor_label, str);
+ g_free (str);
+ return;
+ }
+
+ /* We have a selection that is on the same line.
+ * Lets give some detail as to how long the selection is.
+ */
+ column2 = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self->frame1->source_view),
+ &bounds);
+ str = g_strdup_printf ("%d:%d (%d)", line + 1, column + 1, ABS (column2 - column));
+ gtk_label_set_text (self->cursor_label, str);
+ g_free (str);
+}
+
+static void
+ide_editor_view_set_document (IdeEditorView *self,
+ IdeBuffer *document)
+{
+ g_return_if_fail (IDE_IS_EDITOR_VIEW (self));
+ g_return_if_fail (IDE_IS_BUFFER (document));
+
+ if (g_set_object (&self->document, document))
+ {
+ if (self->frame1)
+ ide_editor_frame_set_document (self->frame1, document);
+
+ if (self->frame2)
+ ide_editor_frame_set_document (self->frame2, document);
+
+ g_settings_bind (self->settings, "style-scheme-name",
+ document, "style-scheme-name",
+ G_SETTINGS_BIND_GET);
+ g_settings_bind (self->settings, "highlight-matching-brackets",
+ document, "highlight-matching-brackets",
+ G_SETTINGS_BIND_GET);
+
+ g_signal_connect_object (document,
+ "cursor-moved",
+ G_CALLBACK (ide_editor_view__buffer_cursor_moved),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_object_bind_property_full (document, "language", self->tweak_button,
+ "label", G_BINDING_SYNC_CREATE,
+ language_to_string, NULL, NULL, NULL);
+
+ g_signal_connect_object (document,
+ "modified-changed",
+ G_CALLBACK (ide_editor_view__buffer_modified_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (document,
+ "notify::title",
+ G_CALLBACK (ide_editor_view__buffer_notify_title),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (document,
+ "notify::language",
+ G_CALLBACK (ide_editor_view__buffer_notify_language),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (document,
+ "notify::changed-on-volume",
+ G_CALLBACK (ide_editor_view__buffer_changed_on_volume),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DOCUMENT]);
+
+ g_object_bind_property (document, "has-diagnostics",
+ self->warning_button, "visible",
+ G_BINDING_SYNC_CREATE);
+
+ ide_editor_view__buffer_notify_language (self, NULL, document);
+ ide_editor_view__buffer_notify_title (self, NULL, IDE_BUFFER (document));
+
+ ide_editor_view_actions_update (self);
+ }
+}
+
+static IdeLayoutView *
+ide_editor_view_create_split (IdeLayoutView *view)
+{
+ IdeEditorView *self = (IdeEditorView *)view;
+ IdeLayoutView *ret;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ ret = g_object_new (IDE_TYPE_EDITOR_VIEW,
+ "document", self->document,
+ "visible", TRUE,
+ NULL);
+
+ return ret;
+}
+
+static void
+ide_editor_view_grab_focus (GtkWidget *widget)
+{
+ IdeEditorView *self = (IdeEditorView *)widget;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (IDE_IS_EDITOR_FRAME (self->last_focused_frame));
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->last_focused_frame->source_view));
+}
+
+static void
+ide_editor_view_request_documentation (IdeEditorView *self,
+ IdeSourceView *source_view)
+{
+ g_autofree gchar *word = NULL;
+ IdeBuffer *buffer;
+ GtkTextMark *mark;
+ GtkTextIter iter;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+ buffer = IDE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view)));
+ mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, mark);
+
+ word = ide_buffer_get_word_at_iter (buffer, &iter);
+
+ g_signal_emit (self, signals [REQUEST_DOCUMENTATION], 0, word);
+}
+
+static void
+ide_editor_view__focused_frame_weak_notify (gpointer data,
+ GObject *object)
+{
+ IdeEditorView *self = data;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ self->last_focused_frame = self->frame1;
+}
+
+static gboolean
+ide_editor_view__focus_in_event (IdeEditorView *self,
+ GdkEvent *event,
+ IdeSourceView *source_view)
+{
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (IDE_IS_SOURCE_VIEW (source_view));
+
+ if (self->last_focused_frame && self->last_focused_frame->source_view == source_view)
+ return FALSE;
+
+ if (self->frame2 && self->frame2->source_view == source_view)
+ {
+ self->last_focused_frame = self->frame2;
+ g_object_weak_ref (G_OBJECT (self->frame2), ide_editor_view__focused_frame_weak_notify, self);
+ }
+ else
+ {
+ g_object_weak_unref (G_OBJECT (self->frame2), ide_editor_view__focused_frame_weak_notify, self);
+ self->last_focused_frame = self->frame1;
+ }
+
+ return FALSE;
+}
+
+static void
+ide_editor_view_set_split_view (IdeLayoutView *view,
+ gboolean split_view)
+{
+ IdeEditorView *self = (IdeEditorView *)view;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ if (split_view && (self->frame2 != NULL))
+ return;
+
+ if (!split_view && (self->frame2 == NULL))
+ return;
+
+ if (split_view)
+ {
+ self->frame2 = g_object_new (IDE_TYPE_EDITOR_FRAME,
+ "show-ruler", TRUE,
+ "document", self->document,
+ "visible", TRUE,
+ NULL);
+ g_signal_connect_object (self->frame2->source_view,
+ "request-documentation",
+ G_CALLBACK (ide_editor_view_request_documentation),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->frame2->source_view,
+ "focus-in-event",
+ G_CALLBACK (ide_editor_view__focus_in_event),
+ self,
+ G_CONNECT_SWAPPED);
+
+ gtk_container_add_with_properties (GTK_CONTAINER (self->paned), GTK_WIDGET (self->frame2),
+ "shrink", FALSE,
+ "resize", TRUE,
+ NULL);
+ gtk_widget_grab_focus (GTK_WIDGET (self->frame2));
+ }
+ else
+ {
+ GtkWidget *copy = GTK_WIDGET (self->frame2);
+
+ self->frame2 = NULL;
+ gtk_container_remove (GTK_CONTAINER (self->paned), copy);
+ gtk_widget_grab_focus (GTK_WIDGET (self->frame1));
+ }
+}
+
+static void
+ide_editor_view_set_back_forward_list (IdeLayoutView *view,
+ IdeBackForwardList *back_forward_list)
+{
+ IdeEditorView *self = (IdeEditorView *)view;
+
+ g_assert (IDE_IS_LAYOUT_VIEW (view));
+ g_assert (IDE_IS_BACK_FORWARD_LIST (back_forward_list));
+
+ g_object_set (self->frame1, "back-forward-list", back_forward_list, NULL);
+ if (self->frame2)
+ g_object_set (self->frame2, "back-forward-list", back_forward_list, NULL);
+}
+
+static void
+ide_editor_view_hide_reload_bar (IdeEditorView *self,
+ GtkWidget *button)
+{
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ gtk_revealer_set_reveal_child (self->modified_revealer, FALSE);
+}
+
+static GtkSizeRequestMode
+ide_editor_view_get_request_mode (GtkWidget *widget)
+{
+ return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+static void
+ide_editor_view_get_preferred_height (GtkWidget *widget,
+ gint *min_height,
+ gint *nat_height)
+{
+ /*
+ * FIXME: Workaround GtkStack changes.
+ *
+ * This can probably be removed once upstream changes land.
+ *
+ * This ignores our potential giant size requests since we don't actually
+ * care about keeping our size requests between animated transitions in
+ * the stack.
+ */
+ GTK_WIDGET_CLASS (ide_editor_view_parent_class)->get_preferred_height (widget, min_height, nat_height);
+ *nat_height = *min_height;
+}
+
+static void
+ide_editor_view_goto_line_activate (IdeEditorView *self,
+ const gchar *text,
+ EggSimplePopover *popover)
+{
+ gint64 value;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (EGG_IS_SIMPLE_POPOVER (popover));
+
+ if (!ide_str_empty0 (text))
+ {
+ value = g_ascii_strtoll (text, NULL, 10);
+
+ if ((value > 0) && (value < G_MAXINT))
+ {
+ GtkTextIter iter;
+ GtkTextBuffer *buffer = GTK_TEXT_BUFFER (self->document);
+
+ gtk_widget_grab_focus (GTK_WIDGET (self->frame1->source_view));
+ gtk_text_buffer_get_iter_at_line (buffer, &iter, value - 1);
+ gtk_text_buffer_select_range (buffer, &iter, &iter);
+ ide_source_view_scroll_to_iter (self->frame1->source_view,
+ &iter, 0.25, TRUE, 1.0, 0.5, TRUE);
+ }
+ }
+}
+
+static gboolean
+ide_editor_view_goto_line_insert_text (IdeEditorView *self,
+ guint position,
+ const gchar *chars,
+ guint n_chars,
+ EggSimplePopover *popover)
+{
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (EGG_IS_SIMPLE_POPOVER (popover));
+ g_assert (chars != NULL);
+
+ for (; *chars; chars = g_utf8_next_char (chars))
+ {
+ if (!g_unichar_isdigit (g_utf8_get_char (chars)))
+ return GDK_EVENT_STOP;
+ }
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+static void
+ide_editor_view_goto_line_changed (IdeEditorView *self,
+ EggSimplePopover *popover)
+{
+ gchar *message;
+ const gchar *text;
+ GtkTextIter begin;
+ GtkTextIter end;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (EGG_IS_SIMPLE_POPOVER (popover));
+
+ text = egg_simple_popover_get_text (popover);
+
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (self->document), &begin, &end);
+
+ if (!ide_str_empty0 (text))
+ {
+ gint64 value;
+
+ value = g_ascii_strtoll (text, NULL, 10);
+
+ if (value > 0)
+ {
+ if (value <= gtk_text_iter_get_line (&end) + 1)
+ {
+ egg_simple_popover_set_message (popover, NULL);
+ egg_simple_popover_set_ready (popover, TRUE);
+ return;
+ }
+
+ }
+ }
+
+ /* translators: the user selected a number outside the value range for the document. */
+ message = g_strdup_printf (_("Provide a number between 1 and %u"),
+ gtk_text_iter_get_line (&end) + 1);
+ egg_simple_popover_set_message (popover, message);
+ egg_simple_popover_set_ready (popover, FALSE);
+
+ g_free (message);
+}
+
+static void
+ide_editor_view__extension_added (PeasExtensionSet *set,
+ PeasPluginInfo *info,
+ IdeEditorViewAddin *addin,
+ IdeEditorView *self)
+{
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (info != NULL);
+ g_assert (IDE_IS_EDITOR_VIEW_ADDIN (addin));
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ ide_editor_view_addin_load (addin, self);
+
+ if (self->document != NULL)
+ {
+ GtkSourceLanguage *language;
+
+ language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->document));
+
+ if (language != NULL)
+ {
+ const gchar *language_id;
+
+ language_id = gtk_source_language_get_id (language);
+ ide_editor_view_addin_language_changed (addin, language_id);
+ }
+ }
+}
+
+static void
+ide_editor_view__extension_removed (PeasExtensionSet *set,
+ PeasPluginInfo *info,
+ IdeEditorViewAddin *addin,
+ IdeEditorView *self)
+{
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (info != NULL);
+ g_assert (IDE_IS_EDITOR_VIEW_ADDIN (addin));
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+
+ ide_editor_view_addin_unload (addin, self);
+}
+
+static void
+ide_editor_view_warning_button_clicked (IdeEditorView *self,
+ GtkButton *button)
+{
+ IdeEditorFrame *frame;
+
+ g_assert (IDE_IS_EDITOR_VIEW (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ frame = ide_editor_view_get_last_focused (self);
+ gtk_widget_grab_focus (GTK_WIDGET (frame));
+ g_signal_emit_by_name (frame->source_view, "move-error", GTK_DIR_DOWN);
+}
+
+static void
+ide_editor_view_constructed (GObject *object)
+{
+ IdeEditorView *self = (IdeEditorView *)object;
+ PeasEngine *engine;
+
+ G_OBJECT_CLASS (ide_editor_view_parent_class)->constructed (object);
+
+ engine = peas_engine_get_default ();
+ self->extensions = peas_extension_set_new (engine, IDE_TYPE_EDITOR_VIEW_ADDIN, NULL);
+ g_signal_connect_object (self->extensions,
+ "extension-added",
+ G_CALLBACK (ide_editor_view__extension_added),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->extensions,
+ "extension-added",
+ G_CALLBACK (ide_editor_view__extension_removed),
+ self,
+ G_CONNECT_SWAPPED);
+ peas_extension_set_foreach (self->extensions,
+ (PeasExtensionSetForeachFunc)ide_editor_view__extension_added,
+ self);
+}
+
+static void
+ide_editor_view_destroy (GtkWidget *widget)
+{
+ IdeEditorView *self = (IdeEditorView *)widget;
+
+ GTK_WIDGET_CLASS (ide_editor_view_parent_class)->destroy (widget);
+
+ g_clear_object (&self->document);
+}
+
+static void
+ide_editor_view_finalize (GObject *object)
+{
+ IdeEditorView *self = (IdeEditorView *)object;
+
+ g_clear_pointer (&self->title, g_free);
+ g_clear_object (&self->extensions);
+ g_clear_object (&self->document);
+ g_clear_object (&self->settings);
+
+ G_OBJECT_CLASS (ide_editor_view_parent_class)->finalize (object);
+}
+
+static void
+ide_editor_view_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorView *self = IDE_EDITOR_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT:
+ g_value_set_object (value, self->document);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_view_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeEditorView *self = IDE_EDITOR_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_DOCUMENT:
+ ide_editor_view_set_document (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_editor_view_class_init (IdeEditorViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ IdeLayoutViewClass *view_class = IDE_LAYOUT_VIEW_CLASS (klass);
+
+ object_class->constructed = ide_editor_view_constructed;
+ object_class->finalize = ide_editor_view_finalize;
+ object_class->get_property = ide_editor_view_get_property;
+ object_class->set_property = ide_editor_view_set_property;
+
+ widget_class->destroy = ide_editor_view_destroy;
+ widget_class->grab_focus = ide_editor_view_grab_focus;
+ widget_class->get_request_mode = ide_editor_view_get_request_mode;
+ widget_class->get_preferred_height = ide_editor_view_get_preferred_height;
+
+ view_class->create_split = ide_editor_view_create_split;
+ view_class->get_special_title = ide_editor_view_get_special_title;
+ view_class->get_modified = ide_editor_view_get_modified;
+ view_class->set_split_view = ide_editor_view_set_split_view;
+ view_class->set_back_forward_list = ide_editor_view_set_back_forward_list;
+ view_class->navigate_to = ide_editor_view_navigate_to;
+
+ properties [PROP_DOCUMENT] =
+ g_param_spec_object ("document",
+ "Document",
+ "The editor document.",
+ IDE_TYPE_BUFFER,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [REQUEST_DOCUMENTATION] =
+ g_signal_new ("request-documentation",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-editor-view.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, cursor_label);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, frame1);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, modified_cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, modified_revealer);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, paned);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, progress_bar);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, tweak_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, tweak_widget);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, goto_line_button);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, goto_line_popover);
+ gtk_widget_class_bind_template_child (widget_class, IdeEditorView, warning_button);
+
+ g_type_ensure (IDE_TYPE_EDITOR_FRAME);
+ g_type_ensure (IDE_TYPE_EDITOR_TWEAK_WIDGET);
+}
+
+static void
+ide_editor_view_init (IdeEditorView *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->settings = g_settings_new ("org.gnome.builder.editor");
+ self->last_focused_frame = self->frame1;
+
+ ide_editor_view_actions_init (self);
+
+ /*
+ * XXX: Refactor all of this.
+ *
+ * In frame1, we don't show the floating bar, so no need to alter the
+ * editor map allocation.
+ */
+ g_object_set (self->frame1->source_map_container,
+ "floating-bar", NULL,
+ NULL);
+
+ g_signal_connect_object (self->modified_cancel_button,
+ "clicked",
+ G_CALLBACK (ide_editor_view_hide_reload_bar),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->frame1->source_view,
+ "request-documentation",
+ G_CALLBACK (ide_editor_view_request_documentation),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->frame1->source_view,
+ "focus-in-event",
+ G_CALLBACK (ide_editor_view__focus_in_event),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->goto_line_popover,
+ "activate",
+ G_CALLBACK (ide_editor_view_goto_line_activate),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->goto_line_popover,
+ "insert-text",
+ G_CALLBACK (ide_editor_view_goto_line_insert_text),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->goto_line_popover,
+ "changed",
+ G_CALLBACK (ide_editor_view_goto_line_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->warning_button,
+ "clicked",
+ G_CALLBACK (ide_editor_view_warning_button_clicked),
+ self,
+ G_CONNECT_SWAPPED);
+}
diff --git a/libide/editor/ide-editor-view.h b/libide/editor/ide-editor-view.h
new file mode 100644
index 0000000..e7dba5b
--- /dev/null
+++ b/libide/editor/ide-editor-view.h
@@ -0,0 +1,32 @@
+/* ide-editor-view.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_EDITOR_VIEW_H
+#define IDE_EDITOR_VIEW_H
+
+#include "ide-layout-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_EDITOR_VIEW (ide_editor_view_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeEditorView, ide_editor_view, IDE, EDITOR_VIEW, IdeLayoutView)
+
+G_END_DECLS
+
+#endif /* IDE_EDITOR_VIEW_H */
diff --git a/libide/editor/run.sh b/libide/editor/run.sh
new file mode 100755
index 0000000..d4f809e
--- /dev/null
+++ b/libide/editor/run.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+base="../../src/editor"
+
+refactor.py \
+ "${base}/gb-editor-frame-actions.c=ide-editor-frame-actions.c" \
+ "${base}/gb-editor-frame-actions.h=ide-editor-frame-actions.h" \
+ "${base}/gb-editor-frame.c=ide-editor-frame.c" \
+ "${base}/gb-editor-frame.h=ide-editor-frame.h" \
+ "${base}/gb-editor-frame-private.h=ide-editor-frame-private.h" \
+ "${base}/gb-editor-map-bin.c=ide-editor-map-bin.c" \
+ "${base}/gb-editor-map-bin.h=ide-editor-map-bin.h" \
+ "${base}/gb-editor-print-operation.c=ide-editor-print-operation.c" \
+ "${base}/gb-editor-print-operation.h=ide-editor-print-operation.h" \
+ "${base}/gb-editor-settings-widget.c=ide-editor-settings-widget.c" \
+ "${base}/gb-editor-settings-widget.h=ide-editor-settings-widget.h" \
+ "${base}/gb-editor-tweak-widget.c=ide-editor-tweak-widget.c" \
+ "${base}/gb-editor-tweak-widget.h=ide-editor-tweak-widget.h" \
+ "${base}/gb-editor-view-actions.c=ide-editor-view-actions.c" \
+ "${base}/gb-editor-view-actions.h=ide-editor-view-actions.h" \
+ "${base}/gb-editor-view-addin.c=ide-editor-view-addin.c" \
+ "${base}/gb-editor-view-addin.h=ide-editor-view-addin.h" \
+ "${base}/gb-editor-view-addin-private.h=ide-editor-view-addin-private.h" \
+ "${base}/gb-editor-view.c=ide-editor-view.c" \
+ "${base}/gb-editor-view.h=ide-editor-view.h" \
+ "${base}/gb-editor-view-private.h=ide-editor-view-private.h"
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index fbefeb5..7b1bb65 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -41,6 +41,9 @@
<gresource prefix="/org/gnome/builder/ui">
<file alias="ide-editor-perspective.ui">../../data/ui/ide-editor-perspective.ui</file>
+ <file alias="ide-editor-frame.ui">../../data/ui/ide-editor-frame.ui</file>
+ <file alias="ide-editor-tweak-widget.ui">../../data/ui/ide-editor-tweak-widget.ui</file>
+ <file alias="ide-editor-view.ui">../../data/ui/ide-editor-view.ui</file>
<file alias="ide-greeter-perspective.ui">../../data/ui/ide-greeter-perspective.ui</file>
<file alias="ide-greeter-project-row.ui">../../data/ui/ide-greeter-project-row.ui</file>
<file alias="ide-layout.ui">../../data/ui/ide-layout.ui</file>
diff --git a/libide/util/ide-dnd.c b/libide/util/ide-dnd.c
new file mode 100644
index 0000000..4165ada
--- /dev/null
+++ b/libide/util/ide-dnd.c
@@ -0,0 +1,42 @@
+/* ide-dnd.c
+ *
+ * Copyright (C) 2015 Dimitris Zenios <dimitris zenios gmail 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-dnd.h"
+
+/**
+ * ide_dnd_get_uri_list:
+ * @selection_data: the #GtkSelectionData from drag_data_received
+ *
+ * Create a list of valid uri's from a uri-list drop.
+ *
+ * Returns: (transfer full): a string array which will hold the uris or
+ * %NULL if there were no valid uris. g_strfreev should be used when
+ * the string array is no longer used
+ */
+gchar **
+ide_dnd_get_uri_list (GtkSelectionData *selection_data)
+{
+ const gchar *data;
+
+ g_return_val_if_fail (selection_data, NULL);
+ g_return_val_if_fail (gtk_selection_data_get_length (selection_data) > 0, NULL);
+
+ data = (const gchar *)gtk_selection_data_get_data (selection_data);
+
+ return g_uri_list_extract_uris (data);
+}
diff --git a/libide/util/ide-dnd.h b/libide/util/ide-dnd.h
new file mode 100644
index 0000000..affac1c
--- /dev/null
+++ b/libide/util/ide-dnd.h
@@ -0,0 +1,30 @@
+/* ide-dnd.h
+ *
+ * Copyright (C) 2015 Dimitris Zenios <dimitris zenios gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_DND_H
+#define IDE_DND_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+gchar **ide_dnd_get_uri_list (GtkSelectionData *selection_data);
+
+G_END_DECLS
+
+#endif /* GB_RGBA_H */
diff --git a/libide/util/ide-gtk.c b/libide/util/ide-gtk.c
index 26071be..3618458 100644
--- a/libide/util/ide-gtk.c
+++ b/libide/util/ide-gtk.c
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "egg-animation.h"
+
#include "ide-gtk.h"
gboolean
@@ -124,3 +126,97 @@ ide_widget_set_context_handler (gpointer widget,
if ((toplevel = gtk_widget_get_toplevel (widget)))
ide_widget_hierarchy_changed (widget, NULL, NULL);
}
+
+static void
+show_callback (gpointer data)
+{
+ g_object_set_data (data, "FADE_ANIMATION", NULL);
+ g_object_unref (data);
+}
+
+static void
+hide_callback (gpointer data)
+{
+ GtkWidget *widget = data;
+
+ g_object_set_data (data, "FADE_ANIMATION", NULL);
+ gtk_widget_hide (widget);
+ gtk_widget_set_opacity (widget, 1.0);
+ g_object_unref (widget);
+}
+
+void
+ide_widget_hide_with_fade (GtkWidget *widget)
+{
+ GdkFrameClock *frame_clock;
+ EggAnimation *anim;
+
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ if (gtk_widget_get_visible (widget))
+ {
+ anim = g_object_get_data (G_OBJECT (widget), "FADE_ANIMATION");
+ if (anim != NULL)
+ egg_animation_stop (anim);
+
+ frame_clock = gtk_widget_get_frame_clock (widget);
+ anim = egg_object_animate_full (widget,
+ EGG_ANIMATION_LINEAR,
+ 1000,
+ frame_clock,
+ hide_callback,
+ g_object_ref (widget),
+ "opacity", 0.0,
+ NULL);
+ g_object_set_data_full (G_OBJECT (widget), "FADE_ANIMATION",
+ g_object_ref (anim), g_object_unref);
+ }
+}
+
+void
+ide_widget_show_with_fade (GtkWidget *widget)
+{
+ GdkFrameClock *frame_clock;
+ EggAnimation *anim;
+
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ if (!gtk_widget_get_visible (widget))
+ {
+ anim = g_object_get_data (G_OBJECT (widget), "FADE_ANIMATION");
+ if (anim != NULL)
+ egg_animation_stop (anim);
+
+ frame_clock = gtk_widget_get_frame_clock (widget);
+ gtk_widget_set_opacity (widget, 0.0);
+ gtk_widget_show (widget);
+ anim = egg_object_animate_full (widget,
+ EGG_ANIMATION_LINEAR,
+ 500,
+ frame_clock,
+ show_callback,
+ g_object_ref (widget),
+ "opacity", 1.0,
+ NULL);
+ g_object_set_data_full (G_OBJECT (widget), "FADE_ANIMATION",
+ g_object_ref (anim), g_object_unref);
+ }
+}
+
+IdeWorkbench *
+ide_widget_get_workbench (GtkWidget *widget)
+{
+ GtkWidget *ancestor;
+
+ g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+
+ ancestor = gtk_widget_get_ancestor (widget, IDE_TYPE_WORKBENCH);
+ if (IDE_IS_WORKBENCH (ancestor))
+ return IDE_WORKBENCH (ancestor);
+
+ /*
+ * TODO: Add "IDE_WORKBENCH" gdata for popout windows.
+ */
+
+ return NULL;
+}
diff --git a/libide/util/ide-gtk.h b/libide/util/ide-gtk.h
index d3f687a..4b7b00d 100644
--- a/libide/util/ide-gtk.h
+++ b/libide/util/ide-gtk.h
@@ -22,19 +22,21 @@
#include <gtk/gtk.h>
#include "ide-context.h"
+#include "ide-workbench.h"
G_BEGIN_DECLS
typedef void (*IdeWidgetContextHandler) (GtkWidget *widget,
IdeContext *context);
-gboolean ide_widget_action (GtkWidget *widget,
- const gchar *group,
- const gchar *name,
- GVariant *param);
-void ide_widget_set_context_handler (gpointer widget,
- IdeWidgetContextHandler handler);
-
+gboolean ide_widget_action (GtkWidget *widget,
+ const gchar *group,
+ const gchar *name,
+ GVariant *param);
+void ide_widget_set_context_handler (gpointer widget,
+ IdeWidgetContextHandler handler);
+void ide_widget_hide_with_fade (GtkWidget *widget);
+IdeWorkbench *ide_widget_get_workbench (GtkWidget *widget);
G_END_DECLS
diff --git a/plugins/command-bar/gb-command-provider.c b/plugins/command-bar/gb-command-provider.c
index 7f8b2f6..480a7d9 100644
--- a/plugins/command-bar/gb-command-provider.c
+++ b/plugins/command-bar/gb-command-provider.c
@@ -105,7 +105,7 @@ on_workbench_set_focus (GbCommandProvider *provider,
IdeWorkbench *workbench)
{
g_return_if_fail (GB_IS_COMMAND_PROVIDER (provider));
- g_return_if_fail (GB_IS_WORKBENCH (workbench));
+ g_return_if_fail (IDE_IS_WORKBENCH (workbench));
g_return_if_fail (!widget || GTK_IS_WIDGET (widget));
/* walk the hierarchy to find a tab */
@@ -124,7 +124,7 @@ gb_command_provider_connect (GbCommandProvider *provider,
IdeWorkbench *workbench)
{
g_return_if_fail (GB_IS_COMMAND_PROVIDER (provider));
- g_return_if_fail (GB_IS_WORKBENCH (workbench));
+ g_return_if_fail (IDE_IS_WORKBENCH (workbench));
g_signal_connect_object (workbench,
"set-focus",
@@ -138,7 +138,7 @@ gb_command_provider_disconnect (GbCommandProvider *provider,
IdeWorkbench *workbench)
{
g_return_if_fail (GB_IS_COMMAND_PROVIDER (provider));
- g_return_if_fail (GB_IS_WORKBENCH (workbench));
+ g_return_if_fail (IDE_IS_WORKBENCH (workbench));
g_signal_handlers_disconnect_by_func (workbench,
G_CALLBACK (on_workbench_set_focus),
@@ -162,7 +162,7 @@ gb_command_provider_set_workbench (GbCommandProvider *provider,
GbCommandProviderPrivate *priv = gb_command_provider_get_instance_private (provider);
g_return_if_fail (GB_IS_COMMAND_PROVIDER (provider));
- g_return_if_fail (!workbench || GB_IS_WORKBENCH (workbench));
+ g_return_if_fail (!workbench || IDE_IS_WORKBENCH (workbench));
if (priv->workbench != workbench)
{
@@ -317,7 +317,7 @@ gb_command_provider_class_init (GbCommandProviderClass *klass)
g_param_spec_object ("active-tab",
"Active View",
"The last focused IdeLayoutView widget.",
- GB_TYPE_VIEW,
+ IDE_TYPE_LAYOUT_VIEW,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
diff --git a/plugins/command-bar/gb-command-vim-provider.c b/plugins/command-bar/gb-command-vim-provider.c
index a59b7aa..4238a7f 100644
--- a/plugins/command-bar/gb-command-vim-provider.c
+++ b/plugins/command-bar/gb-command-vim-provider.c
@@ -46,7 +46,7 @@ get_source_view (GbCommandProvider *provider)
/* Make sure we have a workbench */
workbench = gb_command_provider_get_workbench (provider);
- if (!GB_IS_WORKBENCH (workbench))
+ if (!IDE_IS_WORKBENCH (workbench))
return NULL;
/* Make sure we have an editor tab last focused */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]