[gnome-builder/wip/chergert/perspective] project-tree: start extracting project-tree into plugin
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/perspective] project-tree: start extracting project-tree into plugin
- Date: Wed, 11 Nov 2015 09:58:42 +0000 (UTC)
commit 0e62fba554d90c067cc76299724c7c59ad89d8ce
Author: Christian Hergert <chergert redhat com>
Date: Wed Nov 11 01:58:17 2015 -0800
project-tree: start extracting project-tree into plugin
configure.ac | 2 +
plugins/project-tree/Makefile.am | 57 ++
plugins/project-tree/configure.ac | 12 +
plugins/project-tree/gb-new-file-popover.c | 384 ++++++++++
plugins/project-tree/gb-new-file-popover.h | 39 +
plugins/project-tree/gb-project-file.c | 273 +++++++
plugins/project-tree/gb-project-file.h | 48 ++
plugins/project-tree/gb-project-tree-actions.c | 944 ++++++++++++++++++++++++
plugins/project-tree/gb-project-tree-actions.h | 31 +
plugins/project-tree/gb-project-tree-addin.c | 69 ++
plugins/project-tree/gb-project-tree-addin.h | 32 +
plugins/project-tree/gb-project-tree-builder.c | 422 +++++++++++
plugins/project-tree/gb-project-tree-builder.h | 37 +
plugins/project-tree/gb-project-tree-private.h | 38 +
plugins/project-tree/gb-project-tree.c | 212 ++++++
plugins/project-tree/gb-project-tree.h | 42 ++
plugins/project-tree/gb-rename-file-popover.c | 362 +++++++++
plugins/project-tree/gb-rename-file-popover.h | 35 +
plugins/project-tree/project-tree-plugin.c | 30 +
plugins/project-tree/project-tree.plugin | 8 +
20 files changed, 3077 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 8b9dc5a..e488738 100644
--- a/configure.ac
+++ b/configure.ac
@@ -244,6 +244,7 @@ m4_include([plugins/html-completion/configure.ac])
m4_include([plugins/html-preview/configure.ac])
m4_include([plugins/jedi/configure.ac])
m4_include([plugins/mingw/configure.ac])
+m4_include([plugins/project-tree/configure.ac])
m4_include([plugins/python-gi-imports-completion/configure.ac])
m4_include([plugins/python-pack/configure.ac])
m4_include([plugins/symbol-tree/configure.ac])
@@ -494,6 +495,7 @@ echo " GNOME Code Assistance ................ : ${enable_gnome_code_assistance_
echo " Global File Search ................... : ${enable_file_search_plugin}"
echo " HTML Autocompletion .................. : ${enable_html_completion_plugin}"
echo " HTML and Markdown Preview ............ : ${enable_html_preview_plugin}"
+echo " Project Tree ......................... : ${enable_project_tree_plugin}"
echo " Python GObject Introspection ......... : ${enable_python_gi_imports_completion_plugin}"
echo " Python Jedi Autocompletion ........... : ${enable_jedi_plugin}"
echo " Python Language Pack ................. : ${enable_python_pack_plugin}"
diff --git a/plugins/project-tree/Makefile.am b/plugins/project-tree/Makefile.am
new file mode 100644
index 0000000..cf09162
--- /dev/null
+++ b/plugins/project-tree/Makefile.am
@@ -0,0 +1,57 @@
+if ENABLE_DEVHELP_PLUGIN
+
+DISTCLEANFILES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libproject-tree-plugin.la
+dist_plugin_DATA = project-tree.plugin
+
+libproject_tree_plugin_la_SOURCES = \
+ gb-new-file-popover.c \
+ gb-new-file-popover.h \
+ gb-project-file.c \
+ gb-project-file.h \
+ gb-project-tree-actions.c \
+ gb-project-tree-actions.h \
+ gb-project-tree-builder.c \
+ gb-project-tree-builder.h \
+ gb-project-tree.c \
+ gb-project-tree.h \
+ gb-project-tree-private.h \
+ gb-rename-file-popover.c \
+ gb-rename-file-popover.h \
+ gb-project-tree-addin.c \
+ gb-project-tree-addin.h \
+ project-tree-plugin.c \
+ $(NULL)
+
+libproject_tree_plugin_la_CFLAGS = \
+ $(LIBIDE_CFLAGS) \
+ $(BUILDER_CFLAGS) \
+ $(OPTIMIZE_CFLAGS) \
+ $(DEBUG_CFLAGS) \
+ -I$(top_srcdir)/libide \
+ -I$(top_srcdir)/src/tree \
+ -I$(top_srcdir)/src/util \
+ $(NULL)
+
+libproject_tree_plugin_la_LIBADD = \
+ $(LIBIDE_LIBS) \
+ $(BUILDER_LIBS) \
+ $(top_builddir)/libide/libide-1.0.la \
+ $(NULL)
+
+libproject_tree_plugin_la_LDFLAGS = \
+ $(OPTIMIZE_LDFLAGS) \
+ -avoid-version \
+ -module \
+ $(NULL)
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/project-tree/configure.ac b/plugins/project-tree/configure.ac
new file mode 100644
index 0000000..ec37e7c
--- /dev/null
+++ b/plugins/project-tree/configure.ac
@@ -0,0 +1,12 @@
+# --enable-project-tree-plugin=yes/no
+AC_ARG_ENABLE([project-tree-plugin],
+ [AS_HELP_STRING([--enable-project-tree-plugin=@<:@yes/no@:>@],
+ [Build with support for the project tree.])],
+ [enable_project_tree_plugin=$enableval],
+ [enable_project_tree_plugin=yes])
+
+# for if ENABLE_PROJECT_TREE_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_PROJECT_TREE_PLUGIN, test x$enable_project_tree_plugin = xyes)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/project-tree/Makefile])
diff --git a/plugins/project-tree/gb-new-file-popover.c b/plugins/project-tree/gb-new-file-popover.c
new file mode 100644
index 0000000..25ece48
--- /dev/null
+++ b/plugins/project-tree/gb-new-file-popover.c
@@ -0,0 +1,384 @@
+/* gb-new-file-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 <glib/gi18n.h>
+#include <ide.h>
+
+#include "gb-new-file-popover.h"
+
+struct _GbNewFilePopover
+{
+ GtkPopover parent_instance;
+
+ GFileType file_type;
+ GFile *directory;
+ GCancellable *cancellable;
+
+ GtkButton *button;
+ GtkEntry *entry;
+ GtkLabel *message;
+ GtkLabel *title;
+};
+
+G_DEFINE_TYPE (GbNewFilePopover, gb_new_file_popover, GTK_TYPE_POPOVER)
+
+enum {
+ PROP_0,
+ PROP_DIRECTORY,
+ PROP_FILE_TYPE,
+ LAST_PROP
+};
+
+enum {
+ CREATE_FILE,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+static void
+gb_new_file_popover__button_clicked (GbNewFilePopover *self,
+ GtkButton *button)
+{
+ g_autoptr(GFile) file = NULL;
+ const gchar *path;
+
+ g_assert (GB_IS_NEW_FILE_POPOVER (self));
+ g_assert (GTK_IS_BUTTON (button));
+
+ if (self->directory == NULL)
+ return;
+
+ path = gtk_entry_get_text (self->entry);
+ if (ide_str_empty0 (path))
+ return;
+
+ file = g_file_get_child (self->directory, path);
+
+ g_signal_emit (self, signals [CREATE_FILE], 0, file, self->file_type);
+}
+
+static void
+gb_new_file_popover__entry_activate (GbNewFilePopover *self,
+ GtkEntry *entry)
+{
+ g_assert (GB_IS_NEW_FILE_POPOVER (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ if (gtk_widget_get_sensitive (GTK_WIDGET (self->button)))
+ gtk_widget_activate (GTK_WIDGET (self->button));
+}
+
+static void
+gb_new_file_popover__query_info_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = (GFile *)object;
+ g_autoptr(GFileInfo) file_info = NULL;
+ g_autoptr(GbNewFilePopover) self = user_data;
+ g_autoptr(GError) error = NULL;
+ GFileType file_type;
+
+ file_info = g_file_query_info_finish (file, result, &error);
+
+ if (file_info == NULL &&
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+
+ if ((file_info == NULL) &&
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ gtk_label_set_label (self->message, NULL);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button), TRUE);
+ return;
+ }
+
+ if (file_info == NULL)
+ {
+ gtk_label_set_label (self->message, error->message);
+ return;
+ }
+
+ file_type = g_file_info_get_file_type (file_info);
+
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ gtk_label_set_label (self->message,
+ _("A folder with that name already exists."));
+ else
+ gtk_label_set_label (self->message,
+ _("A file with that name already exists."));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
+}
+
+static void
+gb_new_file_popover_check_exists (GbNewFilePopover *self,
+ GFile *directory,
+ const gchar *path)
+{
+ g_autoptr(GFile) child = NULL;
+
+ g_assert (GB_IS_NEW_FILE_POPOVER (self));
+ g_assert (!directory || G_IS_FILE (directory));
+
+ if (self->cancellable != NULL)
+ {
+ if (!g_cancellable_is_cancelled (self->cancellable))
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ gtk_label_set_label (self->message, NULL);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
+
+ if (directory == NULL)
+ return;
+
+ if (ide_str_empty0 (path))
+ return;
+
+ child = g_file_get_child (directory, path);
+
+ self->cancellable = g_cancellable_new ();
+
+ g_file_query_info_async (child,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ gb_new_file_popover__query_info_cb,
+ g_object_ref (self));
+
+}
+
+static void
+gb_new_file_popover__entry_changed (GbNewFilePopover *self,
+ GtkEntry *entry)
+{
+ const gchar *text;
+
+ g_assert (GB_IS_NEW_FILE_POPOVER (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ text = gtk_entry_get_text (entry);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button), !ide_str_empty0 (text));
+
+ gb_new_file_popover_check_exists (self, self->directory, text);
+}
+
+static void
+gb_new_file_popover_finalize (GObject *object)
+{
+ GbNewFilePopover *self = (GbNewFilePopover *)object;
+
+ if (self->cancellable && !g_cancellable_is_cancelled (self->cancellable))
+ g_cancellable_cancel (self->cancellable);
+
+ g_clear_object (&self->cancellable);
+ g_clear_object (&self->directory);
+
+ G_OBJECT_CLASS (gb_new_file_popover_parent_class)->finalize (object);
+}
+
+static void
+gb_new_file_popover_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbNewFilePopover *self = GB_NEW_FILE_POPOVER(object);
+
+ switch (prop_id)
+ {
+ case PROP_DIRECTORY:
+ g_value_set_object (value, gb_new_file_popover_get_directory (self));
+ break;
+
+ case PROP_FILE_TYPE:
+ g_value_set_enum (value, gb_new_file_popover_get_file_type (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+/**
+ * gb_new_file_popover_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+gb_new_file_popover_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbNewFilePopover *self = GB_NEW_FILE_POPOVER(object);
+
+ switch (prop_id)
+ {
+ case PROP_DIRECTORY:
+ gb_new_file_popover_set_directory (self, g_value_get_object (value));
+ break;
+
+ case PROP_FILE_TYPE:
+ gb_new_file_popover_set_file_type (self, g_value_get_enum (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+gb_new_file_popover_class_init (GbNewFilePopoverClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gb_new_file_popover_finalize;
+ object_class->get_property = gb_new_file_popover_get_property;
+ object_class->set_property = gb_new_file_popover_set_property;
+
+ properties [PROP_DIRECTORY] =
+ g_param_spec_object ("directory",
+ "Directory",
+ "Directory",
+ G_TYPE_FILE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_FILE_TYPE] =
+ g_param_spec_enum ("file-type",
+ "File Type",
+ "The file type to create.",
+ G_TYPE_FILE_TYPE,
+ G_FILE_TYPE_REGULAR,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [CREATE_FILE] =
+ g_signal_new ("create-file",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_FILE,
+ G_TYPE_FILE_TYPE);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/gb-new-file-popover.ui");
+ gtk_widget_class_bind_template_child (widget_class, GbNewFilePopover, button);
+ gtk_widget_class_bind_template_child (widget_class, GbNewFilePopover, entry);
+ gtk_widget_class_bind_template_child (widget_class, GbNewFilePopover, message);
+ gtk_widget_class_bind_template_child (widget_class, GbNewFilePopover, title);
+}
+
+static void
+gb_new_file_popover_init (GbNewFilePopover *self)
+{
+ self->file_type = G_FILE_TYPE_REGULAR;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (self->entry,
+ "activate",
+ G_CALLBACK (gb_new_file_popover__entry_activate),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->entry,
+ "changed",
+ G_CALLBACK (gb_new_file_popover__entry_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->button,
+ "clicked",
+ G_CALLBACK (gb_new_file_popover__button_clicked),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+GFileType
+gb_new_file_popover_get_file_type (GbNewFilePopover *self)
+{
+ g_return_val_if_fail (GB_IS_NEW_FILE_POPOVER (self), 0);
+
+ return self->file_type;
+}
+
+void
+gb_new_file_popover_set_file_type (GbNewFilePopover *self,
+ GFileType file_type)
+{
+ g_return_if_fail (GB_IS_NEW_FILE_POPOVER (self));
+ g_return_if_fail ((file_type == G_FILE_TYPE_REGULAR) ||
+ (file_type == G_FILE_TYPE_DIRECTORY));
+
+ if (file_type != self->file_type)
+ {
+ self->file_type = file_type;
+
+ if (file_type == G_FILE_TYPE_REGULAR)
+ gtk_label_set_label (self->title, _("File Name"));
+ else
+ gtk_label_set_label (self->title, _("Folder Name"));
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE_TYPE]);
+ }
+}
+
+void
+gb_new_file_popover_set_directory (GbNewFilePopover *self,
+ GFile *directory)
+{
+ g_return_if_fail (GB_IS_NEW_FILE_POPOVER (self));
+ g_return_if_fail (G_IS_FILE (directory));
+
+ if (g_set_object (&self->directory, directory))
+ {
+ const gchar *path;
+
+ path = gtk_entry_get_text (self->entry);
+ gb_new_file_popover_check_exists (self, directory, path);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIRECTORY]);
+ }
+}
+
+/**
+ * gb_new_file_popover_get_directory:
+ *
+ * Returns: (transfer none) (nullable): A #GFile or %NULL.
+ */
+GFile *
+gb_new_file_popover_get_directory (GbNewFilePopover *self)
+{
+ g_return_val_if_fail (GB_IS_NEW_FILE_POPOVER (self), NULL);
+
+ return self->directory;
+}
diff --git a/plugins/project-tree/gb-new-file-popover.h b/plugins/project-tree/gb-new-file-popover.h
new file mode 100644
index 0000000..106cdea
--- /dev/null
+++ b/plugins/project-tree/gb-new-file-popover.h
@@ -0,0 +1,39 @@
+/* gb-new-file-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 GB_NEW_FILE_POPOVER_H
+#define GB_NEW_FILE_POPOVER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_NEW_FILE_POPOVER (gb_new_file_popover_get_type())
+
+G_DECLARE_FINAL_TYPE (GbNewFilePopover, gb_new_file_popover, GB, NEW_FILE_POPOVER, GtkPopover)
+
+GFileType gb_new_file_popover_get_file_type (GbNewFilePopover *self);
+void gb_new_file_popover_set_file_type (GbNewFilePopover *self,
+ GFileType file_type);
+void gb_new_file_popover_set_directory (GbNewFilePopover *self,
+ GFile *directory);
+GFile *gb_new_file_popover_get_directory (GbNewFilePopover *self);
+
+G_END_DECLS
+
+#endif /* GB_NEW_FILE_POPOVER_H */
diff --git a/plugins/project-tree/gb-project-file.c b/plugins/project-tree/gb-project-file.c
new file mode 100644
index 0000000..787adde
--- /dev/null
+++ b/plugins/project-tree/gb-project-file.c
@@ -0,0 +1,273 @@
+/* gb-project-file.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 "gb-project-file.h"
+
+struct _GbProjectFile
+{
+ GObject parent_instance;
+
+ GFile *file;
+ GFileInfo *file_info;
+};
+
+G_DEFINE_TYPE (GbProjectFile, gb_project_file, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_DISPLAY_NAME,
+ PROP_FILE,
+ PROP_FILE_INFO,
+ PROP_ICON_NAME,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+gint
+gb_project_file_compare (GbProjectFile *a,
+ GbProjectFile *b)
+{
+ const gchar *display_name_a = g_file_info_get_display_name (a->file_info);
+ const gchar *display_name_b = g_file_info_get_display_name (b->file_info);
+ gchar *casefold_a = NULL;
+ gchar *casefold_b = NULL;
+ gboolean ret;
+
+ casefold_a = g_utf8_collate_key_for_filename (display_name_a, -1);
+ casefold_b = g_utf8_collate_key_for_filename (display_name_b, -1);
+
+ ret = strcmp (casefold_a, casefold_b);
+
+ g_free (casefold_a);
+ g_free (casefold_b);
+
+ return ret;
+}
+
+gint
+gb_project_file_compare_directories_first (GbProjectFile *a,
+ GbProjectFile *b)
+{
+ GFileType file_type_a = g_file_info_get_file_type (a->file_info);
+ GFileType file_type_b = g_file_info_get_file_type (b->file_info);
+ gint dir_a = (file_type_a == G_FILE_TYPE_DIRECTORY);
+ gint dir_b = (file_type_b == G_FILE_TYPE_DIRECTORY);
+ gint ret;
+
+ ret = dir_b - dir_a;
+ if (ret == 0)
+ ret = gb_project_file_compare (a, b);
+
+ return ret;
+}
+
+
+static void
+gb_project_file_finalize (GObject *object)
+{
+ GbProjectFile *self = (GbProjectFile *)object;
+
+ g_clear_object (&self->file);
+ g_clear_object (&self->file_info);
+
+ G_OBJECT_CLASS (gb_project_file_parent_class)->finalize (object);
+}
+
+static void
+gb_project_file_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbProjectFile *self = GB_PROJECT_FILE (object);
+
+ switch (prop_id)
+ {
+ case PROP_DISPLAY_NAME:
+ g_value_set_string (value, gb_project_file_get_display_name (self));
+ break;
+
+ case PROP_ICON_NAME:
+ g_value_set_static_string (value, gb_project_file_get_icon_name (self));
+ break;
+
+ case PROP_FILE:
+ g_value_set_object (value, gb_project_file_get_file (self));
+ break;
+
+ case PROP_FILE_INFO:
+ g_value_set_object (value, gb_project_file_get_file_info (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_project_file_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbProjectFile *self = GB_PROJECT_FILE (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILE:
+ gb_project_file_set_file (self, g_value_get_object (value));
+ break;
+
+ case PROP_FILE_INFO:
+ gb_project_file_set_file_info (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_project_file_class_init (GbProjectFileClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gb_project_file_finalize;
+ object_class->get_property = gb_project_file_get_property;
+ object_class->set_property = gb_project_file_set_property;
+
+ properties [PROP_DISPLAY_NAME] =
+ g_param_spec_string ("display-name",
+ "Display Name",
+ "Display Name",
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon Name",
+ "Icon Name",
+ NULL,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_FILE] =
+ g_param_spec_object ("file",
+ "File",
+ "File",
+ G_TYPE_FILE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_FILE_INFO] =
+ g_param_spec_object ("file-info",
+ "File Info",
+ "File Info",
+ G_TYPE_FILE_INFO,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gb_project_file_init (GbProjectFile *self)
+{
+}
+
+GbProjectFile *
+gb_project_file_new (GFile *file,
+ GFileInfo *file_info)
+{
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+ g_return_val_if_fail (G_IS_FILE_INFO (file_info), NULL);
+
+ return g_object_new (GB_TYPE_PROJECT_FILE,
+ "file", file,
+ "file-info", file_info,
+ NULL);
+}
+
+GFile *
+gb_project_file_get_file (GbProjectFile *self)
+{
+ g_return_val_if_fail (GB_IS_PROJECT_FILE (self), NULL);
+
+ return self->file;
+}
+
+void
+gb_project_file_set_file (GbProjectFile *self,
+ GFile *file)
+{
+ g_return_if_fail (GB_IS_PROJECT_FILE (self));
+ g_return_if_fail (!file || G_IS_FILE (file));
+
+ if (g_set_object (&self->file, file))
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
+}
+
+GFileInfo *
+gb_project_file_get_file_info (GbProjectFile *self)
+{
+ g_return_val_if_fail (GB_IS_PROJECT_FILE (self), NULL);
+
+ return self->file_info;
+}
+
+void
+gb_project_file_set_file_info (GbProjectFile *self,
+ GFileInfo *file_info)
+{
+ g_return_if_fail (GB_IS_PROJECT_FILE (self));
+ g_return_if_fail (!file_info || G_IS_FILE_INFO (file_info));
+
+ if (g_set_object (&self->file_info, file_info))
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE_INFO]);
+}
+
+gboolean
+gb_project_file_get_is_directory (GbProjectFile *self)
+{
+ g_return_val_if_fail (GB_IS_PROJECT_FILE (self), FALSE);
+
+ if (self->file_info != NULL)
+ return g_file_info_get_file_type (self->file_info) == G_FILE_TYPE_DIRECTORY;
+
+ return G_FILE_TYPE_UNKNOWN;
+}
+
+const gchar *
+gb_project_file_get_icon_name (GbProjectFile *self)
+{
+ if (gb_project_file_get_is_directory (self))
+ return "folder-symbolic";
+
+ return "text-x-generic-symbolic";
+}
+
+const gchar *
+gb_project_file_get_display_name (GbProjectFile *self)
+{
+ g_return_val_if_fail (GB_IS_PROJECT_FILE (self), NULL);
+
+ if (self->file_info != NULL)
+ return g_file_info_get_display_name (self->file_info);
+
+ return NULL;
+}
diff --git a/plugins/project-tree/gb-project-file.h b/plugins/project-tree/gb-project-file.h
new file mode 100644
index 0000000..a9eebad
--- /dev/null
+++ b/plugins/project-tree/gb-project-file.h
@@ -0,0 +1,48 @@
+/* gb-project-file.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 GB_PROJECT_FILE_H
+#define GB_PROJECT_FILE_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_PROJECT_FILE (gb_project_file_get_type())
+
+G_DECLARE_FINAL_TYPE (GbProjectFile, gb_project_file, GB, PROJECT_FILE, GObject)
+
+GbProjectFile *gb_project_file_new (GFile *directory,
+ GFileInfo *file_info);
+GFile *gb_project_file_get_file (GbProjectFile *self);
+void gb_project_file_set_file (GbProjectFile *self,
+ GFile *file);
+GFileInfo *gb_project_file_get_file_info (GbProjectFile *self);
+void gb_project_file_set_file_info (GbProjectFile *self,
+ GFileInfo *file_info);
+gboolean gb_project_file_get_is_directory (GbProjectFile *self);
+const gchar *gb_project_file_get_display_name (GbProjectFile *self);
+const gchar *gb_project_file_get_icon_name (GbProjectFile *self);
+gint gb_project_file_compare_directories_first (GbProjectFile *a,
+ GbProjectFile *b);
+gint gb_project_file_compare (GbProjectFile *a,
+ GbProjectFile *b);
+
+G_END_DECLS
+
+#endif /* GB_PROJECT_FILE_H */
diff --git a/plugins/project-tree/gb-project-tree-actions.c b/plugins/project-tree/gb-project-tree-actions.c
new file mode 100644
index 0000000..aa62505
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree-actions.c
@@ -0,0 +1,944 @@
+/* gb-project-tree-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 "gb-project-tree-actions"
+
+#include <glib/gi18n.h>
+#include <gio/gdesktopappinfo.h>
+#include <ide.h>
+
+#ifdef HAVE_VTE
+# include <vte/vte.h>
+#endif
+
+#include "gb-file-manager.h"
+#include "gb-new-file-popover.h"
+#include "gb-project-file.h"
+#include "gb-project-tree.h"
+#include "gb-project-tree-actions.h"
+#include "gb-project-tree-private.h"
+#include "gb-rename-file-popover.h"
+
+#if 0
+#include "gb-view-stack.h"
+#endif
+
+#if 0
+static void
+action_set (GActionGroup *group,
+ const gchar *action_name,
+ const gchar *first_param,
+ ...)
+{
+ GAction *action;
+ va_list args;
+
+ g_assert (G_IS_ACTION_GROUP (group));
+ g_assert (G_IS_ACTION_MAP (group));
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (group), action_name);
+ g_assert (G_IS_SIMPLE_ACTION (action));
+
+ va_start (args, first_param);
+ g_object_set_valist (G_OBJECT (action), first_param, args);
+ va_end (args);
+}
+
+static gboolean
+project_file_is_directory (GObject *object)
+{
+ g_assert (!object || G_IS_OBJECT (object));
+
+ return (GB_IS_PROJECT_FILE (object) &&
+ gb_project_file_get_is_directory (GB_PROJECT_FILE (object)));
+}
+
+static void
+gb_project_tree_actions_refresh (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbProjectTree *self = user_data;
+ GbTreeNode *selected;
+ GObject *item = NULL;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ if ((selected = gb_tree_get_selected (GB_TREE (self))))
+ {
+ item = gb_tree_node_get_item (selected);
+ if (item != NULL)
+ g_object_ref (item);
+ }
+
+ gb_tree_rebuild (GB_TREE (self));
+
+ if (item != NULL)
+ {
+ selected = gb_tree_find_item (GB_TREE (self), item);
+ if (selected != NULL)
+ {
+ gb_tree_node_expand (selected, TRUE);
+ gb_tree_node_select (selected);
+ gb_tree_scroll_to_node (GB_TREE (self), selected);
+ }
+ g_object_unref (item);
+ }
+}
+
+static void
+gb_project_tree_actions_collapse_all_nodes (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbProjectTree *self = user_data;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ gtk_tree_view_collapse_all (GTK_TREE_VIEW (self));
+}
+
+static void
+gb_project_tree_actions_open (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+#if 0
+ GbProjectTree *self = user_data;
+ IdeWorkbench *workbench;
+ GbTreeNode *selected;
+ GObject *item;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ workbench = gb_widget_get_workbench (GTK_WIDGET (self));
+ g_assert (GB_IS_WORKBENCH (workbench));
+
+ if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
+ !(item = gb_tree_node_get_item (selected)))
+ return;
+
+ item = gb_tree_node_get_item (selected);
+
+ if (GB_IS_PROJECT_FILE (item))
+ {
+ GFileInfo *file_info;
+ GFile *file;
+
+ file_info = gb_project_file_get_file_info (GB_PROJECT_FILE (item));
+ if (!file_info)
+ return;
+
+ if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+ return;
+
+ file = gb_project_file_get_file (GB_PROJECT_FILE (item));
+ if (!file)
+ return;
+
+ ide_workbench_open (workbench, file);
+ }
+#endif
+}
+
+static void
+gb_project_tree_actions_open_with (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+#if 0
+ g_autoptr(GDesktopAppInfo) app_info = NULL;
+ g_autoptr(GdkAppLaunchContext) launch_context = NULL;
+ GbProjectTree *self = user_data;
+ GbTreeNode *selected;
+ IdeWorkbench *workbench;
+ GdkDisplay *display;
+ GFileInfo *file_info;
+ GFile *file;
+ const gchar *app_id;
+ GObject *item;
+ GList *files;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+ g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING));
+
+ if (!(workbench = gb_widget_get_workbench (GTK_WIDGET (self))) ||
+ !(selected = gb_tree_get_selected (GB_TREE (self))) ||
+ !(item = gb_tree_node_get_item (selected)) ||
+ !GB_IS_PROJECT_FILE (item) ||
+ !(app_id = g_variant_get_string (variant, NULL)) ||
+ !(file_info = gb_project_file_get_file_info (GB_PROJECT_FILE (item))) ||
+ !(file = gb_project_file_get_file (GB_PROJECT_FILE (item))) ||
+ !(app_info = g_desktop_app_info_new (app_id)))
+ return;
+
+ display = gtk_widget_get_display (GTK_WIDGET (self));
+ launch_context = gdk_display_get_app_launch_context (display);
+
+ files = g_list_append (NULL, file);
+ g_app_info_launch (G_APP_INFO (app_info), files, G_APP_LAUNCH_CONTEXT (launch_context), NULL);
+ g_list_free (files);
+#endif
+}
+
+static void
+gb_project_tree_actions_open_with_editor (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+#if 0
+ IdeWorkbench *workbench;
+ GbProjectTree *self = user_data;
+ GFileInfo *file_info;
+ GFile *file;
+ GbTreeNode *selected;
+ GObject *item;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
+ !(item = gb_tree_node_get_item (selected)) ||
+ !GB_IS_PROJECT_FILE (item) ||
+ !(file_info = gb_project_file_get_file_info (GB_PROJECT_FILE (item))) ||
+ (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) ||
+ !(file = gb_project_file_get_file (GB_PROJECT_FILE (item))) ||
+ !(workbench = gb_widget_get_workbench (GTK_WIDGET (self))))
+ return;
+
+ ide_workbench_open_with_editor (workbench, file);
+#endif
+}
+
+static void
+gb_project_tree_actions_open_containing_folder (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbProjectTree *self = user_data;
+ GbTreeNode *selected;
+ GObject *item;
+ GFile *file;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
+ !(item = gb_tree_node_get_item (selected)) ||
+ !GB_IS_PROJECT_FILE (item))
+ return;
+
+ file = gb_project_file_get_file (GB_PROJECT_FILE (item));
+
+ gb_file_manager_show (file, NULL);
+}
+
+/* Based on gdesktopappinfo.c in GIO */
+static gchar *
+find_terminal_executable (void)
+{
+ gsize i;
+ gchar *path = NULL;
+ g_autoptr(GSettings) terminal_settings = NULL;
+ g_autofree gchar *gsettings_terminal = NULL;
+ const gchar *terminals[] = {
+ NULL, /* GSettings */
+ "x-terminal-emulator", /* Debian's alternative system */
+ "gnome-terminal",
+ NULL, /* getenv ("TERM") */
+ "nxterm", "color-xterm",
+ "rxvt", "xterm", "dtterm"
+ };
+
+ /* This is deprecated, but at least the user can specify it! */
+ terminal_settings = g_settings_new ("org.gnome.desktop.default-applications.terminal");
+ gsettings_terminal = g_settings_get_string (terminal_settings, "exec");
+ terminals[0] = gsettings_terminal;
+
+ /* This is generally one of the fallback terminals */
+ terminals[3] = g_getenv ("TERM");
+
+ for (i = 0; i < G_N_ELEMENTS (terminals) && path == NULL; ++i)
+ {
+ if (terminals[i] != NULL)
+ {
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS
+ path = g_find_program_in_path (terminals[i]);
+ G_GNUC_END_IGNORE_DEPRECATIONS
+ }
+ }
+
+ return path;
+}
+
+static void
+gb_project_tree_actions_open_in_terminal (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbProjectTree *self = user_data;
+ GbTreeNode *selected;
+ GObject *item;
+ GFile *file;
+ g_autofree gchar *workdir = NULL;
+ g_autofree gchar *terminal_executable = NULL;
+ const gchar *argv[] = { NULL, NULL };
+ g_auto(GStrv) env = NULL;
+ GError *error = NULL;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
+ !(item = gb_tree_node_get_item (selected)) ||
+ !GB_IS_PROJECT_FILE (item))
+ return;
+
+ file = gb_project_file_get_file (GB_PROJECT_FILE (item));
+
+ if (!gb_project_file_get_is_directory (GB_PROJECT_FILE (item)))
+ {
+ g_autoptr(GFile) parent;
+
+ parent = g_file_get_parent (file);
+ workdir = g_file_get_path (parent);
+ }
+ else
+ {
+ workdir = g_file_get_path (file);
+ }
+
+ if (workdir == NULL)
+ {
+ g_warning ("Cannot load non-native file in terminal.");
+ return;
+ }
+
+ terminal_executable = find_terminal_executable ();
+ argv[0] = terminal_executable;
+ g_return_if_fail (terminal_executable != NULL);
+
+#ifdef HAVE_VTE
+ {
+ /*
+ * Overwrite SHELL to the users default shell.
+ * Failure to do so typically results in /bin/sh being used.
+ */
+ gchar *shell;
+
+ shell = vte_get_user_shell ();
+ g_setenv ("SHELL", shell, TRUE);
+ g_free (shell);
+ }
+#endif
+
+ env = g_get_environ ();
+
+ /* Can't use GdkAppLaunchContext as
+ * we cannot set the working directory.
+ */
+ if (!g_spawn_async (workdir, (gchar **)argv, env,
+ G_SPAWN_STDERR_TO_DEV_NULL,
+ NULL, NULL, NULL, &error))
+ {
+ g_warning ("%s", error->message);
+ g_clear_error (&error);
+ return;
+ }
+}
+
+static void
+gb_project_tree_actions__make_directory_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = (GFile *)object;
+ g_autoptr(GbTreeNode) node = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (G_IS_FILE (file));
+ g_assert (GB_IS_TREE_NODE (node));
+
+ if (!g_file_make_directory_finish (file, result, &error))
+ {
+ /* todo: show error messsage */
+ return;
+ }
+
+ gb_tree_node_invalidate (node);
+ gb_tree_node_expand (node, FALSE);
+ gb_tree_node_select (node);
+}
+
+static void
+gb_project_tree_actions__create_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = (GFile *)object;
+ g_autoptr(GbTreeNode) node = user_data;
+ g_autoptr(GError) error = NULL;
+ GbProjectTree *self;
+ IdeWorkbench *workbench;
+
+ g_assert (G_IS_FILE (file));
+ g_assert (GB_IS_TREE_NODE (node));
+
+ if (!g_file_create_finish (file, result, &error))
+ {
+ /* todo: show error messsage */
+ return;
+ }
+
+ self = GB_PROJECT_TREE (gb_tree_node_get_tree (node));
+ if (self == NULL)
+ return;
+
+ workbench = gb_widget_get_workbench (GTK_WIDGET (self));
+ if (workbench == NULL)
+ return;
+
+ ide_workbench_open (workbench, file);
+
+ gb_tree_node_invalidate (node);
+ gb_tree_node_expand (node, FALSE);
+ gb_tree_node_select (node);
+}
+
+static void
+gb_project_tree_actions__popover_create_file_cb (GbProjectTree *self,
+ GFile *file,
+ GFileType file_type,
+ GbNewFilePopover *popover)
+{
+ GbTreeNode *selected;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+ g_assert (G_IS_FILE (file));
+ g_assert ((file_type == G_FILE_TYPE_DIRECTORY) ||
+ (file_type == G_FILE_TYPE_REGULAR));
+ g_assert (GB_IS_NEW_FILE_POPOVER (popover));
+
+ selected = gb_tree_get_selected (GB_TREE (self));
+
+ g_assert (selected != NULL);
+ g_assert (GB_IS_TREE_NODE (selected));
+
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ {
+ g_file_make_directory_async (file,
+ G_PRIORITY_DEFAULT,
+ NULL, /* cancellable */
+ gb_project_tree_actions__make_directory_cb,
+ g_object_ref (selected));
+ }
+ else if (file_type == G_FILE_TYPE_REGULAR)
+ {
+ g_file_create_async (file,
+ G_FILE_CREATE_NONE,
+ G_PRIORITY_DEFAULT,
+ NULL, /* cancellable */
+ gb_project_tree_actions__create_cb,
+ g_object_ref (selected));
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
+
+ self->expanded_in_new = FALSE;
+
+ gtk_widget_hide (GTK_WIDGET (popover));
+ gtk_widget_destroy (GTK_WIDGET (popover));
+}
+
+static void
+gb_project_tree_actions__popover_closed_cb (GbProjectTree *self,
+ GtkPopover *popover)
+{
+ GbTreeNode *selected;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+ g_assert (GTK_IS_POPOVER (popover));
+
+ if (!(selected = gb_tree_get_selected (GB_TREE (self))) || !self->expanded_in_new)
+ return;
+
+ gb_tree_node_collapse (selected);
+}
+
+static void
+gb_project_tree_actions_new (GbProjectTree *self,
+ GFileType file_type)
+{
+ GbTreeNode *selected;
+ GObject *item;
+ GtkPopover *popover;
+ GbProjectFile *project_file;
+ GFile *file = NULL;
+ gboolean is_dir = FALSE;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+ g_assert ((file_type == G_FILE_TYPE_DIRECTORY) ||
+ (file_type == G_FILE_TYPE_REGULAR));
+
+again:
+ if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
+ !(item = gb_tree_node_get_item (selected)) ||
+ !GB_IS_PROJECT_FILE (item))
+ return;
+
+ if (!(project_file = GB_PROJECT_FILE (item)) ||
+ !(file = gb_project_file_get_file (project_file)))
+ return;
+
+ is_dir = project_file_is_directory (item);
+
+ g_assert (G_IS_FILE (file));
+
+ /*
+ * If this item is an GbProjectFile and not a directory, then we really
+ * want to create a sibling.
+ */
+ if (!is_dir)
+ {
+ GtkTreePath *path;
+
+ selected = gb_tree_node_get_parent (selected);
+ gb_tree_node_select (selected);
+ path = gb_tree_node_get_path (selected);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), path, NULL, FALSE, 0, 0);
+ gtk_tree_path_free (path);
+
+ goto again;
+ }
+
+ if ((self->expanded_in_new = !gb_tree_node_get_expanded (selected)))
+ gb_tree_node_expand (selected, FALSE);
+
+ popover = g_object_new (GB_TYPE_NEW_FILE_POPOVER,
+ "directory", file,
+ "file-type", file_type,
+ "position", GTK_POS_RIGHT,
+ NULL);
+ g_signal_connect_object (popover,
+ "create-file",
+ G_CALLBACK (gb_project_tree_actions__popover_create_file_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (popover,
+ "closed",
+ G_CALLBACK (gb_project_tree_actions__popover_closed_cb),
+ self,
+ G_CONNECT_SWAPPED);
+
+ gb_tree_node_show_popover (selected, popover);
+}
+
+static void
+gb_project_tree_actions_new_directory (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbProjectTree *self = user_data;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ gb_project_tree_actions_new (self, G_FILE_TYPE_DIRECTORY);
+}
+
+static void
+gb_project_tree_actions_new_file (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbProjectTree *self = user_data;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ gb_project_tree_actions_new (self, G_FILE_TYPE_REGULAR);
+}
+
+static gboolean
+find_child_node (GbTree *tree,
+ GbTreeNode *parent,
+ GbTreeNode *child,
+ gpointer user_data)
+{
+ GObject *item = gb_tree_node_get_item (child);
+ GFile *target = user_data;
+ GFile *child_file;
+
+ if (GB_IS_PROJECT_FILE (item) &&
+ (child_file = gb_project_file_get_file (GB_PROJECT_FILE (item))) &&
+ g_file_equal (child_file, target))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+gb_project_tree_actions__project_rename_file_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeProject *project = (IdeProject *)object;
+ g_autoptr(GbRenameFilePopover) popover = user_data;
+ g_autoptr(GError) error = NULL;
+ GbTreeNode *node;
+ GbTreeNode *parent;
+ GFile *file;
+ GbTree *tree;
+
+ g_assert (IDE_IS_PROJECT (project));
+ g_assert (GB_IS_RENAME_FILE_POPOVER (popover));
+
+ if (!ide_project_rename_file_finish (project, result, &error))
+ {
+ /* todo: display error */
+ g_warning ("%s", error->message);
+ return;
+ }
+
+ file = g_object_get_data (G_OBJECT (popover), "G_FILE");
+ tree = GB_TREE (gtk_popover_get_relative_to (GTK_POPOVER (popover)));
+
+ g_assert (G_IS_FILE (file));
+ g_assert (GB_IS_TREE (tree));
+
+ node = gb_tree_get_selected (tree);
+ if (node == NULL)
+ goto cleanup;
+
+ parent = gb_tree_node_get_parent (node);
+
+ gb_tree_node_invalidate (parent);
+ gb_tree_node_expand (parent, FALSE);
+
+ node = gb_tree_find_child_node (tree, parent, find_child_node, file);
+
+ if (node != NULL)
+ gb_tree_node_select (node);
+ else
+ gb_tree_node_select (parent);
+
+cleanup:
+ gtk_widget_hide (GTK_WIDGET (popover));
+ gtk_widget_destroy (GTK_WIDGET (popover));
+}
+
+static void
+gb_project_tree_actions__rename_file_cb (GbProjectTree *self,
+ GFile *orig_file,
+ GFile *new_file,
+ GbRenameFilePopover *popover)
+{
+ IdeWorkbench *workbench;
+ IdeContext *context;
+ IdeProject *project;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+ g_assert (G_IS_FILE (orig_file));
+ g_assert (G_IS_FILE (new_file));
+ g_assert (GTK_IS_POPOVER (popover));
+
+ workbench = gb_widget_get_workbench (GTK_WIDGET (self));
+ context = ide_workbench_get_context (workbench);
+ project = ide_context_get_project (context);
+
+ /* todo: set busy spinner in popover */
+
+ g_object_set_data_full (G_OBJECT (popover),
+ "G_FILE",
+ g_object_ref (new_file),
+ g_object_unref);
+
+ ide_project_rename_file_async (project, orig_file, new_file, NULL,
+ gb_project_tree_actions__project_rename_file_cb,
+ g_object_ref (popover));
+}
+
+static void
+gb_project_tree_actions_rename_file (GSimpleAction *action,
+ GVariant *variant,
+ gpointer user_data)
+{
+ GbProjectTree *self = user_data;
+ GbTreeNode *selected;
+ GtkPopover *popover;
+ GObject *item;
+ GFile *file;
+ GFileInfo *file_info;
+ gboolean is_dir;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ if (!(selected = gb_tree_get_selected (GB_TREE (self))) ||
+ !(item = gb_tree_node_get_item (selected)) ||
+ !GB_IS_PROJECT_FILE (item) ||
+ !(file = gb_project_file_get_file (GB_PROJECT_FILE (item))) ||
+ !(file_info = gb_project_file_get_file_info (GB_PROJECT_FILE (item))))
+ return;
+
+ is_dir = (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY);
+
+ popover = g_object_new (GB_TYPE_RENAME_FILE_POPOVER,
+ "file", file,
+ "is-directory", is_dir,
+ "position", GTK_POS_RIGHT,
+ NULL);
+ g_signal_connect_object (popover,
+ "rename-file",
+ G_CALLBACK (gb_project_tree_actions__rename_file_cb),
+ self,
+ G_CONNECT_SWAPPED);
+ gb_tree_node_show_popover (selected, popover);
+}
+
+static void
+gb_project_tree_actions__trash_file_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeProject *project = (IdeProject *)object;
+ g_autoptr(GbTreeNode) node = user_data;
+ g_autoptr(GError) error = NULL;
+ GbTreeNode *parent;
+
+ g_assert (IDE_IS_PROJECT (project));
+ g_assert (GB_IS_TREE_NODE (node));
+
+ if (!ide_project_trash_file_finish (project, result, &error))
+ {
+ /* todo: warning dialog */
+ g_warning ("%s", error->message);
+ return;
+ }
+
+ if ((parent = gb_tree_node_get_parent (node)))
+ {
+ gb_tree_node_invalidate (parent);
+ gb_tree_node_expand (parent, FALSE);
+ gb_tree_node_select (parent);
+ }
+}
+
+typedef struct
+{
+ GbDocument *document;
+ GList *views;
+} ViewsRemoval;
+
+static void
+gb_project_tree_actions_close_views_cb (GtkWidget *widget,
+ gpointer user_data)
+{
+ GbDocument *document;
+ ViewsRemoval *removal = user_data;
+ IdeView *view = (IdeView *)widget;
+
+ g_assert (GB_IS_VIEW (view));
+ g_assert (removal != NULL);
+ g_assert (GB_IS_DOCUMENT (removal->document));
+
+ document = gb_view_get_document (view);
+
+ if (document == removal->document)
+ removal->views = g_list_prepend (removal->views, g_object_ref (view));
+}
+
+static void
+gb_project_tree_actions_move_to_trash (GSimpleAction *action,
+ GVariant *param,
+ gpointer user_data)
+{
+ GbProjectTree *self = user_data;
+ IdeBufferManager *buffer_manager;
+ IdeWorkbench *workbench;
+ IdeContext *context;
+ ViewsRemoval removal = { 0 };
+ IdeBuffer *buffer;
+ IdeProject *project;
+ GbTreeNode *node;
+ GFile *file;
+ GObject *item;
+ GList *iter;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ workbench = gb_widget_get_workbench (GTK_WIDGET (self));
+ context = ide_workbench_get_context (workbench);
+ project = ide_context_get_project (context);
+ buffer_manager = ide_context_get_buffer_manager (context);
+
+ if (!(node = gb_tree_get_selected (GB_TREE (self))) ||
+ !(item = gb_tree_node_get_item (node)) ||
+ !GB_IS_PROJECT_FILE (item) ||
+ !(file = gb_project_file_get_file (GB_PROJECT_FILE (item))))
+ return;
+
+ /*
+ * Find all of the views that contain this file.
+ * We do not close them until we leave the foreach callback.
+ */
+ if ((buffer = ide_buffer_manager_find_buffer (buffer_manager, file)))
+ {
+ removal.document = g_object_ref (buffer);
+ ide_workbench_views_foreach (workbench,
+ gb_project_tree_actions_close_views_cb,
+ &removal);
+ g_object_unref (removal.document);
+ }
+
+ /*
+ * Close all of the views that match the document.
+ */
+ for (iter = removal.views; iter; iter = iter->next)
+ {
+ GtkWidget *stack;
+
+ stack = gtk_widget_get_ancestor (iter->data, GB_TYPE_VIEW_STACK);
+ if (stack != NULL)
+ gb_view_stack_remove (GB_VIEW_STACK (stack), iter->data);
+ }
+
+ g_list_free_full (removal.views, g_object_unref);
+
+ /*
+ * Now move the file to the trash.
+ */
+ ide_project_trash_file_async (project,
+ file,
+ NULL,
+ gb_project_tree_actions__trash_file_cb,
+ g_object_ref (node));
+}
+
+static gboolean
+is_files_node (GbTreeNode *node)
+{
+ if (node != NULL)
+ {
+ GObject *item = gb_tree_node_get_item (node);
+ GbTreeNode *parent = gb_tree_node_get_parent (node);
+ GObject *parent_item = gb_tree_node_get_item (parent);
+
+ return (GB_IS_PROJECT_FILE (item) && !GB_IS_PROJECT_FILE (parent_item));
+ }
+
+ return FALSE;
+}
+
+static GActionEntry GbProjectTreeActions[] = {
+ { "collapse-all-nodes", gb_project_tree_actions_collapse_all_nodes },
+ { "move-to-trash", gb_project_tree_actions_move_to_trash },
+ { "new-directory", gb_project_tree_actions_new_directory },
+ { "new-file", gb_project_tree_actions_new_file },
+ { "open", gb_project_tree_actions_open },
+ { "open-containing-folder", gb_project_tree_actions_open_containing_folder },
+ { "open-in-terminal", gb_project_tree_actions_open_in_terminal },
+ { "open-with", gb_project_tree_actions_open_with, "s" },
+ { "open-with-editor", gb_project_tree_actions_open_with_editor },
+ { "refresh", gb_project_tree_actions_refresh },
+ { "rename-file", gb_project_tree_actions_rename_file },
+};
+#endif
+
+void
+gb_project_tree_actions_init (GbProjectTree *self)
+{
+#if 0
+ g_autoptr(GSettings) settings = NULL;
+ g_autoptr(GSettings) tree_settings = NULL;
+ g_autoptr(GSimpleActionGroup) actions = NULL;
+ GAction *action;
+
+ actions = g_simple_action_group_new ();
+
+ settings = g_settings_new ("org.gtk.Settings.FileChooser");
+ action = g_settings_create_action (settings, "sort-directories-first");
+ g_action_map_add_action (G_ACTION_MAP (actions), action);
+ g_clear_object (&action);
+
+ g_action_map_add_action_entries (G_ACTION_MAP (actions),
+ GbProjectTreeActions,
+ G_N_ELEMENTS (GbProjectTreeActions),
+ self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self),
+ "project-tree",
+ G_ACTION_GROUP (actions));
+
+ tree_settings = g_settings_new ("org.gnome.builder.project-tree");
+
+ action = g_settings_create_action (tree_settings, "show-ignored-files");
+ g_action_map_add_action (G_ACTION_MAP (actions), action);
+ g_clear_object (&action);
+
+ action = g_settings_create_action (tree_settings, "show-icons");
+ g_action_map_add_action (G_ACTION_MAP (actions), action);
+ g_clear_object (&action);
+
+ gb_project_tree_actions_update (self);
+#endif
+}
+
+void
+gb_project_tree_actions_update (GbProjectTree *self)
+{
+#if 0
+ GActionGroup *group;
+ GbTreeNode *selection;
+ GObject *item = NULL;
+
+ IDE_ENTRY;
+
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ group = gtk_widget_get_action_group (GTK_WIDGET (self), "project-tree");
+ g_assert (G_IS_SIMPLE_ACTION_GROUP (group));
+
+ selection = gb_tree_get_selected (GB_TREE (self));
+ if (selection != NULL)
+ item = gb_tree_node_get_item (selection);
+
+ action_set (group, "new-file",
+ "enabled", GB_IS_PROJECT_FILE (item),
+ NULL);
+ action_set (group, "new-directory",
+ "enabled", GB_IS_PROJECT_FILE (item),
+ NULL);
+ action_set (group, "open",
+ "enabled", (GB_IS_PROJECT_FILE (item) && !project_file_is_directory (item)),
+ NULL);
+ action_set (group, "open-with-editor",
+ "enabled", (GB_IS_PROJECT_FILE (item) && !project_file_is_directory (item)),
+ NULL);
+ action_set (group, "open-containing-folder",
+ "enabled", GB_IS_PROJECT_FILE (item),
+ NULL);
+ action_set (group, "open-in-terminal",
+ "enabled", GB_IS_PROJECT_FILE (item),
+ NULL);
+ action_set (group, "rename-file",
+ "enabled", (GB_IS_PROJECT_FILE (item) && !is_files_node (selection)),
+ NULL);
+ action_set (group, "move-to-trash",
+ "enabled", (GB_IS_PROJECT_FILE (item) && !is_files_node (selection)),
+ NULL);
+
+ IDE_EXIT;
+#endif
+}
diff --git a/plugins/project-tree/gb-project-tree-actions.h b/plugins/project-tree/gb-project-tree-actions.h
new file mode 100644
index 0000000..2837c84
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree-actions.h
@@ -0,0 +1,31 @@
+/* gb-project-tree-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 GB_PROJECT_TREE_ACTIONS_H
+#define GB_PROJECT_TREE_ACTIONS_H
+
+#include "gb-project-tree.h"
+
+G_BEGIN_DECLS
+
+void gb_project_tree_actions_init (GbProjectTree *self);
+void gb_project_tree_actions_update (GbProjectTree *self);
+
+G_END_DECLS
+
+#endif /* GB_PROJECT_TREE_ACTIONS_H */
diff --git a/plugins/project-tree/gb-project-tree-addin.c b/plugins/project-tree/gb-project-tree-addin.c
new file mode 100644
index 0000000..c4d5c1d
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree-addin.c
@@ -0,0 +1,69 @@
+/* gb-project-tree-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <ide.h>
+
+#include "gb-project-tree.h"
+#include "gb-project-tree-addin.h"
+
+static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
+
+struct _GbProjectTreeAddin
+{
+ GObject parent_instance;
+};
+
+G_DEFINE_TYPE_EXTENDED (GbProjectTreeAddin, gb_project_tree_addin, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
+
+static void
+gb_project_tree_addin_load (IdeWorkbenchAddin *addin,
+ IdeWorkbench *workbench)
+{
+ IdePerspective *editor;
+ GtkWidget *pane;
+ GtkWidget *tree;
+
+ g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
+ g_assert (IDE_IS_WORKBENCH (workbench));
+
+ editor = ide_workbench_get_perspective_by_name (workbench, "editor");
+ pane = ide_layout_get_left_pane (IDE_LAYOUT (editor));
+ tree = g_object_new (GB_TYPE_PROJECT_TREE,
+ "headers-visible", FALSE,
+ "visible", TRUE,
+ NULL);
+ ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), tree, _("Project Tree"), "folder-symbolic");
+}
+
+static void
+workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
+{
+ iface->load = gb_project_tree_addin_load;
+}
+
+static void
+gb_project_tree_addin_class_init (GbProjectTreeAddinClass *klass)
+{
+}
+
+static void
+gb_project_tree_addin_init (GbProjectTreeAddin *addin)
+{
+}
diff --git a/plugins/project-tree/gb-project-tree-addin.h b/plugins/project-tree/gb-project-tree-addin.h
new file mode 100644
index 0000000..2b107b8
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree-addin.h
@@ -0,0 +1,32 @@
+/* gb-project-tree-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GB_PROJECT_TREE_ADDIN_H
+#define GB_PROJECT_TREE_ADDIN_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_PROJECT_TREE_ADDIN (gb_project_tree_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbProjectTreeAddin, gb_project_tree_addin, GB, PROJECT_TREE_ADDIN, GObject)
+
+G_END_DECLS
+
+#endif /* GB_PROJECT_TREE_ADDIN_H */
diff --git a/plugins/project-tree/gb-project-tree-builder.c b/plugins/project-tree/gb-project-tree-builder.c
new file mode 100644
index 0000000..4e9304b
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree-builder.c
@@ -0,0 +1,422 @@
+/* gb-project-tree-builder.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 "gb-project-file.h"
+#include "gb-project-tree.h"
+#include "gb-project-tree-builder.h"
+#include "gb-tree.h"
+
+struct _GbProjectTreeBuilder
+{
+ GbTreeBuilder parent_instance;
+
+ GSettings *file_chooser_settings;
+
+ guint sort_directories_first : 1;
+};
+
+G_DEFINE_TYPE (GbProjectTreeBuilder, gb_project_tree_builder, GB_TYPE_TREE_BUILDER)
+
+GbTreeBuilder *
+gb_project_tree_builder_new (void)
+{
+ return g_object_new (GB_TYPE_PROJECT_TREE_BUILDER, NULL);
+}
+
+static void
+build_context (GbProjectTreeBuilder *self,
+ GbTreeNode *node)
+{
+ g_autoptr(GbProjectFile) item = NULL;
+ g_autoptr(GFileInfo) file_info = NULL;
+ g_autofree gchar *name = NULL;
+ GbTreeNode *child;
+ IdeContext *context;
+ IdeVcs *vcs;
+ GFile *workdir;
+
+ g_return_if_fail (GB_IS_PROJECT_TREE_BUILDER (self));
+ g_return_if_fail (GB_IS_TREE_NODE (node));
+
+ context = IDE_CONTEXT (gb_tree_node_get_item (node));
+ vcs = ide_context_get_vcs (context);
+ workdir = ide_vcs_get_working_directory (vcs);
+
+ file_info = g_file_info_new ();
+
+ g_file_info_set_file_type (file_info, G_FILE_TYPE_DIRECTORY);
+
+ name = g_file_get_basename (workdir);
+ g_file_info_set_name (file_info, name);
+ g_file_info_set_display_name (file_info, name);
+
+ item = g_object_new (GB_TYPE_PROJECT_FILE,
+ "file", workdir,
+ "file-info", file_info,
+ NULL);
+
+ child = g_object_new (GB_TYPE_TREE_NODE,
+ "item", item,
+ "text", _("Files"),
+ "icon-name", "folder-symbolic",
+ NULL);
+ gb_tree_node_append (node, child);
+}
+
+static IdeVcs *
+get_vcs (GbTreeNode *node)
+{
+ GbTree *tree;
+ GbTreeNode *root;
+ IdeContext *context;
+
+ g_assert (GB_IS_TREE_NODE (node));
+
+ tree = gb_tree_node_get_tree (node);
+ root = gb_tree_get_root (tree);
+ context = IDE_CONTEXT (gb_tree_node_get_item (root));
+
+ return ide_context_get_vcs (context);
+}
+
+static gint
+compare_nodes_func (GbTreeNode *a,
+ GbTreeNode *b,
+ gpointer user_data)
+{
+ GbProjectFile *file_a = GB_PROJECT_FILE (gb_tree_node_get_item (a));
+ GbProjectFile *file_b = GB_PROJECT_FILE (gb_tree_node_get_item (b));
+ GbProjectTreeBuilder *self = user_data;
+
+ if (self->sort_directories_first)
+ return gb_project_file_compare_directories_first (file_a, file_b);
+ else
+ return gb_project_file_compare (file_a, file_b);
+}
+
+static void
+build_file (GbProjectTreeBuilder *self,
+ GbTreeNode *node)
+{
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+ GbProjectFile *project_file;
+ gpointer file_info_ptr;
+ IdeVcs *vcs;
+ GFile *file;
+ GbTree *tree;
+ gboolean show_ignored_files;
+
+ g_return_if_fail (GB_IS_PROJECT_TREE_BUILDER (self));
+ g_return_if_fail (GB_IS_TREE_NODE (node));
+
+ project_file = GB_PROJECT_FILE (gb_tree_node_get_item (node));
+
+ tree = gb_tree_builder_get_tree (GB_TREE_BUILDER (self));
+ show_ignored_files = gb_project_tree_get_show_ignored_files (GB_PROJECT_TREE (tree));
+
+ vcs = get_vcs (node);
+
+ /*
+ * TODO: Make this all async.
+ */
+
+ if (!gb_project_file_get_is_directory (project_file))
+ return;
+
+ file = gb_project_file_get_file (project_file);
+
+ enumerator = g_file_enumerate_children (file,
+ G_FILE_ATTRIBUTE_STANDARD_NAME","
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (enumerator == NULL)
+ return;
+
+ while ((file_info_ptr = g_file_enumerator_next_file (enumerator, NULL, NULL)))
+ {
+ g_autoptr(GFileInfo) item_file_info = file_info_ptr;
+ g_autoptr(GFile) item_file = NULL;
+ g_autoptr(GbProjectFile) item = NULL;
+ GbTreeNode *child;
+ const gchar *name;
+ const gchar *display_name;
+ const gchar *icon_name;
+ gboolean ignored;
+
+ name = g_file_info_get_name (item_file_info);
+ item_file = g_file_get_child (file, name);
+
+ ignored = ide_vcs_is_ignored (vcs, item_file, NULL);
+ if (ignored && !show_ignored_files)
+ continue;
+
+ item = gb_project_file_new (item_file, item_file_info);
+
+ display_name = gb_project_file_get_display_name (item);
+ icon_name = gb_project_file_get_icon_name (item);
+
+ child = g_object_new (GB_TYPE_TREE_NODE,
+ "icon-name", icon_name,
+ "text", display_name,
+ "item", item,
+ "use-dim-label", ignored,
+ NULL);
+
+ gb_tree_node_insert_sorted (node, child, compare_nodes_func, self);
+
+ if (g_file_info_get_file_type (item_file_info) == G_FILE_TYPE_DIRECTORY)
+ gb_tree_node_set_children_possible (child, TRUE);
+ }
+}
+
+static void
+gb_project_tree_builder_build_node (GbTreeBuilder *builder,
+ GbTreeNode *node)
+{
+ GbProjectTreeBuilder *self = (GbProjectTreeBuilder *)builder;
+ GObject *item;
+
+ g_return_if_fail (GB_IS_PROJECT_TREE_BUILDER (self));
+
+ item = gb_tree_node_get_item (node);
+
+ if (IDE_IS_CONTEXT (item))
+ build_context (self, node);
+ else if (GB_IS_PROJECT_FILE (item))
+ build_file (self, node);
+}
+
+static gchar *
+get_content_type (GFile *file)
+{
+ g_autofree gchar *name = NULL;
+
+ g_assert (G_IS_FILE (file));
+
+ name = g_file_get_basename (file);
+
+ return g_content_type_guess (name, NULL, 0, NULL);
+}
+
+static void
+populate_mime_handlers (GMenu *menu,
+ GbProjectFile *project_file)
+{
+ g_autofree gchar *content_type = NULL;
+ GList *list;
+ GList *iter;
+ GFile *file;
+
+ g_assert (G_IS_MENU (menu));
+ g_assert (GB_IS_PROJECT_FILE (project_file));
+
+ g_menu_remove_all (menu);
+
+ file = gb_project_file_get_file (project_file);
+ if (file == NULL)
+ return;
+
+ content_type = get_content_type (file);
+ if (content_type == NULL)
+ return;
+
+ list = g_app_info_get_all_for_type (content_type);
+
+ for (iter = list; iter; iter = iter->next)
+ {
+ g_autoptr(GMenuItem) menu_item = NULL;
+ g_autofree gchar *detailed_action = NULL;
+ GAppInfo *app_info = iter->data;
+ const gchar *display_name;
+ const gchar *app_id;
+
+ display_name = g_app_info_get_display_name (app_info);
+ app_id = g_app_info_get_id (app_info);
+
+ detailed_action = g_strdup_printf ("project-tree.open-with('%s')", app_id);
+ menu_item = g_menu_item_new (display_name, detailed_action);
+
+ g_menu_append_item (menu, menu_item);
+ }
+
+ g_list_free_full (list, g_object_unref);
+}
+
+static void
+gb_project_tree_builder_node_popup (GbTreeBuilder *builder,
+ GbTreeNode *node,
+ GMenu *menu)
+{
+ GtkApplication *app;
+ GObject *item;
+ GMenu *submenu;
+ IdeVcs *vcs;
+ GFile *workdir;
+ GFile *file;
+
+ g_assert (GB_IS_PROJECT_TREE_BUILDER (builder));
+ g_assert (GB_IS_TREE_NODE (node));
+ g_assert (G_IS_MENU (menu));
+
+ app = GTK_APPLICATION (g_application_get_default ());
+ item = gb_tree_node_get_item (node);
+
+ if (GB_IS_PROJECT_FILE (item))
+ {
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-build");
+ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (submenu));
+ }
+
+ vcs = get_vcs (node);
+ workdir = ide_vcs_get_working_directory (vcs);
+
+ if (GB_IS_PROJECT_FILE (item) &&
+ (file = gb_project_file_get_file (GB_PROJECT_FILE (item))) &&
+ !g_file_equal (file, workdir))
+ {
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-move-to-trash");
+ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (submenu));
+
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-rename");
+ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (submenu));
+
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-open-containing");
+ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (submenu));
+
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-open");
+ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (submenu));
+
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-open-by-mime-section");
+ populate_mime_handlers (submenu, GB_PROJECT_FILE (item));
+
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-new");
+ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (submenu));
+ }
+ else if (GB_IS_PROJECT_FILE (item))
+ {
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-open-containing");
+ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (submenu));
+
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-new");
+ g_menu_prepend_section (menu, NULL, G_MENU_MODEL (submenu));
+ }
+
+ submenu = gtk_application_get_menu_by_id (app, "gb-project-tree-display-options");
+ g_menu_append_section (menu, NULL, G_MENU_MODEL (submenu));
+}
+
+static gboolean
+gb_project_tree_builder_node_activated (GbTreeBuilder *builder,
+ GbTreeNode *node)
+{
+ GObject *item;
+
+ g_assert (GB_IS_PROJECT_TREE_BUILDER (builder));
+
+ item = gb_tree_node_get_item (node);
+
+ if (GB_IS_PROJECT_FILE (item))
+ {
+ GtkWidget *workbench;
+ GbTree *tree;
+ GFile *file;
+
+ if (gb_project_file_get_is_directory (GB_PROJECT_FILE (item)))
+ goto failure;
+
+ file = gb_project_file_get_file (GB_PROJECT_FILE (item));
+ if (!file)
+ goto failure;
+
+ tree = gb_tree_node_get_tree (node);
+ if (!tree)
+ goto failure;
+
+ workbench = gtk_widget_get_ancestor (GTK_WIDGET (tree), IDE_TYPE_WORKBENCH);
+ ide_workbench_open_async (IDE_WORKBENCH (workbench), &file, 1, NULL, NULL, NULL);
+
+ return TRUE;
+ }
+
+failure:
+ return FALSE;
+}
+
+static void
+gb_project_tree_builder_rebuild (GSettings *settings,
+ const gchar *key,
+ GbProjectTreeBuilder *self)
+{
+ GbTree *tree;
+ gboolean sort_directories_first;
+
+ g_assert (G_IS_SETTINGS (settings));
+ g_assert (GB_IS_PROJECT_TREE_BUILDER (self));
+
+ sort_directories_first = g_settings_get_boolean (settings, "sort-directories-first");
+
+ if (sort_directories_first != self->sort_directories_first)
+ {
+ self->sort_directories_first = sort_directories_first;
+ if ((tree = gb_tree_builder_get_tree (GB_TREE_BUILDER (self))))
+ gb_tree_rebuild (tree);
+ }
+}
+
+static void
+gb_project_tree_builder_finalize (GObject *object)
+{
+ GbProjectTreeBuilder *self = (GbProjectTreeBuilder *)object;
+
+ g_clear_object (&self->file_chooser_settings);
+
+ G_OBJECT_CLASS (gb_project_tree_builder_parent_class)->finalize (object);
+}
+
+static void
+gb_project_tree_builder_class_init (GbProjectTreeBuilderClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GbTreeBuilderClass *tree_builder_class = GB_TREE_BUILDER_CLASS (klass);
+
+ object_class->finalize = gb_project_tree_builder_finalize;
+
+ tree_builder_class->build_node = gb_project_tree_builder_build_node;
+ tree_builder_class->node_activated = gb_project_tree_builder_node_activated;
+ tree_builder_class->node_popup = gb_project_tree_builder_node_popup;
+}
+
+static void
+gb_project_tree_builder_init (GbProjectTreeBuilder *self)
+{
+ self->file_chooser_settings = g_settings_new ("org.gtk.Settings.FileChooser");
+ self->sort_directories_first = g_settings_get_boolean (self->file_chooser_settings,
+ "sort-directories-first");
+
+ g_signal_connect (self->file_chooser_settings,
+ "changed::sort-directories-first",
+ G_CALLBACK (gb_project_tree_builder_rebuild),
+ self);
+}
diff --git a/plugins/project-tree/gb-project-tree-builder.h b/plugins/project-tree/gb-project-tree-builder.h
new file mode 100644
index 0000000..2924185
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree-builder.h
@@ -0,0 +1,37 @@
+/* gb-project-tree-builder.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 GB_PROJECT_TREE_BUILDER_H
+#define GB_PROJECT_TREE_BUILDER_H
+
+#include <ide.h>
+
+#include "gb-tree-builder.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_PROJECT_TREE_BUILDER (gb_project_tree_builder_get_type())
+
+G_DECLARE_FINAL_TYPE (GbProjectTreeBuilder, gb_project_tree_builder,
+ GB, PROJECT_TREE_BUILDER, GbTreeBuilder)
+
+GbTreeBuilder *gb_project_tree_builder_new (void);
+
+G_END_DECLS
+
+#endif /* GB_PROJECT_TREE_BUILDER_H */
diff --git a/plugins/project-tree/gb-project-tree-private.h b/plugins/project-tree/gb-project-tree-private.h
new file mode 100644
index 0000000..d63b95f
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree-private.h
@@ -0,0 +1,38 @@
+/* gb-project-tree-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 GB_PROJECT_TREE_PRIVATE_H
+#define GB_PROJECT_TREE_PRIVATE_H
+
+#include "gb-tree.h"
+
+G_BEGIN_DECLS
+
+struct _GbProjectTree
+{
+ GbTree parent_instance;
+
+ GSettings *settings;
+
+ guint expanded_in_new : 1;
+ guint show_ignored_files : 1;
+};
+
+G_END_DECLS
+
+#endif /* GB_PROJECT_TREE_PRIVATE_H */
diff --git a/plugins/project-tree/gb-project-tree.c b/plugins/project-tree/gb-project-tree.c
new file mode 100644
index 0000000..b6b8545
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree.c
@@ -0,0 +1,212 @@
+/* gb-project-tree.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 "gb-project-tree.h"
+#include "gb-project-tree-actions.h"
+#include "gb-project-tree-builder.h"
+#include "gb-project-tree-private.h"
+
+G_DEFINE_TYPE (GbProjectTree, gb_project_tree, GB_TYPE_TREE)
+
+enum {
+ PROP_0,
+ PROP_SHOW_IGNORED_FILES,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+GtkWidget *
+gb_project_tree_new (void)
+{
+ return g_object_new (GB_TYPE_PROJECT_TREE, NULL);
+}
+
+IdeContext *
+gb_project_tree_get_context (GbProjectTree *self)
+{
+ GbTreeNode *root;
+ GObject *item;
+
+ g_return_val_if_fail (GB_IS_PROJECT_TREE (self), NULL);
+
+ if ((root = gb_tree_get_root (GB_TREE (self))) &&
+ (item = gb_tree_node_get_item (root)) &&
+ IDE_IS_OBJECT (item))
+ return ide_object_get_context (IDE_OBJECT (item));
+
+ return NULL;
+}
+
+void
+gb_project_tree_set_context (GbProjectTree *self,
+ IdeContext *context)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GbTreeNode *root;
+
+ g_return_if_fail (GB_IS_PROJECT_TREE (self));
+ g_return_if_fail (!context || IDE_IS_CONTEXT (context));
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
+
+ root = gb_tree_node_new ();
+ gb_tree_node_set_item (root, G_OBJECT (context));
+ gb_tree_set_root (GB_TREE (self), root);
+
+ /*
+ * If we only have one toplevel item (underneath root), expand it.
+ */
+ if ((gtk_tree_model_iter_n_children (model, NULL) == 1) &&
+ gtk_tree_model_get_iter_first (model, &iter))
+ {
+ g_autoptr(GbTreeNode) node = NULL;
+
+ gtk_tree_model_get (model, &iter, 0, &node, -1);
+ if (node != NULL)
+ gb_tree_node_expand (node, FALSE);
+ }
+}
+
+
+static void
+gb_project_tree_notify_selection (GbProjectTree *self)
+{
+ g_assert (GB_IS_PROJECT_TREE (self));
+
+ gb_project_tree_actions_update (self);
+}
+
+static void
+gb_project_tree_finalize (GObject *object)
+{
+ GbProjectTree *self = (GbProjectTree *)object;
+
+ g_clear_object (&self->settings);
+
+ G_OBJECT_CLASS (gb_project_tree_parent_class)->finalize (object);
+}
+
+static void
+gb_project_tree_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbProjectTree *self = GB_PROJECT_TREE(object);
+
+ switch (prop_id)
+ {
+ case PROP_SHOW_IGNORED_FILES:
+ g_value_set_boolean (value, gb_project_tree_get_show_ignored_files (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_project_tree_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbProjectTree *self = GB_PROJECT_TREE(object);
+
+ switch (prop_id)
+ {
+ case PROP_SHOW_IGNORED_FILES:
+ gb_project_tree_set_show_ignored_files (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_project_tree_class_init (GbProjectTreeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gb_project_tree_finalize;
+ object_class->get_property = gb_project_tree_get_property;
+ object_class->set_property = gb_project_tree_set_property;
+
+ properties [PROP_SHOW_IGNORED_FILES] =
+ g_param_spec_boolean ("show-ignored-files",
+ "Show Ignored Files",
+ "If files ignored by the VCS should be displayed.",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gb_project_tree_init (GbProjectTree *self)
+{
+ GbTreeBuilder *builder;
+
+ self->settings = g_settings_new ("org.gnome.builder.project-tree");
+
+ g_settings_bind (self->settings, "show-icons",
+ self, "show-icons",
+ G_SETTINGS_BIND_DEFAULT);
+ g_settings_bind (self->settings, "show-ignored-files",
+ self, "show-ignored-files",
+ G_SETTINGS_BIND_DEFAULT);
+
+ builder = gb_project_tree_builder_new ();
+ gb_tree_add_builder (GB_TREE (self), builder);
+
+ g_signal_connect (self,
+ "notify::selection",
+ G_CALLBACK (gb_project_tree_notify_selection),
+ NULL);
+
+ gb_project_tree_actions_init (self);
+}
+
+gboolean
+gb_project_tree_get_show_ignored_files (GbProjectTree *self)
+{
+ g_return_val_if_fail (GB_IS_PROJECT_TREE (self), FALSE);
+
+ return self->show_ignored_files;
+}
+
+void
+gb_project_tree_set_show_ignored_files (GbProjectTree *self,
+ gboolean show_ignored_files)
+{
+ g_return_if_fail (GB_IS_PROJECT_TREE (self));
+
+ show_ignored_files = !!show_ignored_files;
+
+ if (show_ignored_files != self->show_ignored_files)
+ {
+ self->show_ignored_files = show_ignored_files;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_IGNORED_FILES]);
+ gb_tree_rebuild (GB_TREE (self));
+ }
+}
diff --git a/plugins/project-tree/gb-project-tree.h b/plugins/project-tree/gb-project-tree.h
new file mode 100644
index 0000000..9e7358e
--- /dev/null
+++ b/plugins/project-tree/gb-project-tree.h
@@ -0,0 +1,42 @@
+/* gb-project-tree.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 GB_PROJECT_TREE_H
+#define GB_PROJECT_TREE_H
+
+#include <ide.h>
+
+#include "gb-tree.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_PROJECT_TREE (gb_project_tree_get_type())
+
+G_DECLARE_FINAL_TYPE (GbProjectTree, gb_project_tree, GB, PROJECT_TREE, GbTree)
+
+GtkWidget *gb_project_tree_new (void);
+void gb_project_tree_set_context (GbProjectTree *self,
+ IdeContext *context);
+IdeContext *gb_project_tree_get_context (GbProjectTree *self);
+gboolean gb_project_tree_get_show_ignored_files (GbProjectTree *self);
+void gb_project_tree_set_show_ignored_files (GbProjectTree *self,
+ gboolean show_ignored_files);
+
+G_END_DECLS
+
+#endif /* GB_PROJECT_TREE_H */
diff --git a/plugins/project-tree/gb-rename-file-popover.c b/plugins/project-tree/gb-rename-file-popover.c
new file mode 100644
index 0000000..4532300
--- /dev/null
+++ b/plugins/project-tree/gb-rename-file-popover.c
@@ -0,0 +1,362 @@
+/* gb-rename-file-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 <glib/gi18n.h>
+#include <ide.h>
+
+#include "gb-rename-file-popover.h"
+
+struct _GbRenameFilePopover
+{
+ GtkPopover parent_instance;
+
+ GCancellable *cancellable;
+ GFile *file;
+
+ GtkEntry *entry;
+ GtkButton *button;
+ GtkLabel *label;
+ GtkLabel *message;
+
+ guint is_directory : 1;
+};
+
+enum {
+ PROP_0,
+ PROP_FILE,
+ PROP_IS_DIRECTORY,
+ LAST_PROP
+};
+
+enum {
+ RENAME_FILE,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (GbRenameFilePopover, gb_rename_file_popover, GTK_TYPE_POPOVER)
+
+static GParamSpec *properties [LAST_PROP];
+static guint signals [LAST_SIGNAL];
+
+GFile *
+gb_rename_file_popover_get_file (GbRenameFilePopover *self)
+{
+ g_return_val_if_fail (GB_IS_RENAME_FILE_POPOVER (self), NULL);
+
+ return self->file;
+}
+
+static void
+gb_rename_file_popover_set_file (GbRenameFilePopover *self,
+ GFile *file)
+{
+ g_return_if_fail (GB_IS_RENAME_FILE_POPOVER (self));
+ g_return_if_fail (G_IS_FILE (file));
+
+ if (g_set_object (&self->file, file))
+ {
+ if (file != NULL)
+ {
+ gchar *name;
+ gchar *label;
+
+ name = g_file_get_basename (file);
+ label = g_strdup_printf (_("Rename %s"), name);
+
+ gtk_label_set_label (self->label, label);
+
+ g_free (name);
+ g_free (label);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
+ }
+}
+
+static void
+gb_rename_file_popover_set_is_directory (GbRenameFilePopover *self,
+ gboolean is_directory)
+{
+ g_return_if_fail (GB_IS_RENAME_FILE_POPOVER (self));
+
+ is_directory = !!is_directory;
+
+ if (is_directory != self->is_directory)
+ {
+ self->is_directory = is_directory;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_IS_DIRECTORY]);
+ }
+}
+
+static void
+gb_rename_file_popover__file_query_info (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = (GFile *)object;
+ g_autoptr(GFileInfo) file_info = NULL;
+ g_autoptr(GbRenameFilePopover) self = user_data;
+ g_autoptr(GError) error = NULL;
+ GFileType file_type;
+
+ file_info = g_file_query_info_finish (file, result, &error);
+
+ if (file_info == NULL &&
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ return;
+
+ if ((file_info == NULL) &&
+ g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+ {
+ gtk_label_set_label (self->message, NULL);
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button), TRUE);
+ return;
+ }
+
+ if (file_info == NULL)
+ {
+ gtk_label_set_label (self->message, error->message);
+ return;
+ }
+
+ file_type = g_file_info_get_file_type (file_info);
+
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ gtk_label_set_label (self->message,
+ _("A folder with that name already exists."));
+ else
+ gtk_label_set_label (self->message,
+ _("A file with that name already exists."));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
+}
+
+static void
+gb_rename_file_popover__entry_changed (GbRenameFilePopover *self,
+ GtkEntry *entry)
+{
+ g_autoptr(GFile) parent = NULL;
+ g_autoptr(GFile) file = NULL;
+ const gchar *text;
+
+ g_assert (GB_IS_RENAME_FILE_POPOVER (self));
+ g_assert (GTK_IS_ENTRY (entry));
+ g_assert (self->file != NULL);
+ g_assert (G_IS_FILE (self->file));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
+ gtk_label_set_label (self->message, NULL);
+
+ text = gtk_entry_get_text (entry);
+ if (ide_str_empty0 (text))
+ return;
+
+ if (strchr (text, G_DIR_SEPARATOR) != NULL)
+ {
+ gtk_label_set_label (self->message,
+ _("File name must not contain subdirectories."));
+ return;
+ }
+
+ if (self->cancellable)
+ {
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ self->cancellable = g_cancellable_new ();
+
+ parent = g_file_get_parent (self->file);
+ file = g_file_get_child (parent, text);
+
+ g_file_query_info_async (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ self->cancellable,
+ gb_rename_file_popover__file_query_info,
+ g_object_ref (self));
+}
+
+static void
+gb_rename_file_popover__entry_activate (GbRenameFilePopover *self,
+ GtkEntry *entry)
+{
+ g_assert (GB_IS_RENAME_FILE_POPOVER (self));
+ g_assert (GTK_IS_ENTRY (entry));
+
+ if (gtk_widget_get_sensitive (GTK_WIDGET (self->button)))
+ gtk_widget_activate (GTK_WIDGET (self->button));
+}
+
+static void
+gb_rename_file_popover__button_clicked (GbRenameFilePopover *self,
+ GtkButton *button)
+{
+ g_autoptr(GFile) file = NULL;
+ g_autoptr(GFile) parent = NULL;
+ const gchar *path;
+
+ g_assert (GB_IS_RENAME_FILE_POPOVER (self));
+ g_assert (GTK_IS_BUTTON (button));
+ g_assert (self->file != NULL);
+ g_assert (G_IS_FILE (self->file));
+
+ path = gtk_entry_get_text (self->entry);
+ if (ide_str_empty0 (path))
+ return;
+
+ parent = g_file_get_parent (self->file);
+ file = g_file_get_child (parent, path);
+
+ /* only activate once */
+ gtk_widget_set_sensitive (GTK_WIDGET (self->button), FALSE);
+
+ g_signal_emit (self, signals [RENAME_FILE], 0, self->file, file);
+}
+
+static void
+gb_rename_file_popover_finalize (GObject *object)
+{
+ GbRenameFilePopover *self = (GbRenameFilePopover *)object;
+
+ if (self->cancellable != NULL)
+ {
+ if (!g_cancellable_is_cancelled (self->cancellable))
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+ }
+
+ g_clear_object (&self->file);
+
+ G_OBJECT_CLASS (gb_rename_file_popover_parent_class)->finalize (object);
+}
+
+static void
+gb_rename_file_popover_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GbRenameFilePopover *self = GB_RENAME_FILE_POPOVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILE:
+ g_value_set_object (value, self->file);
+ break;
+
+ case PROP_IS_DIRECTORY:
+ g_value_set_boolean (value, self->is_directory);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_rename_file_popover_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GbRenameFilePopover *self = GB_RENAME_FILE_POPOVER (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILE:
+ gb_rename_file_popover_set_file (self, g_value_get_object (value));
+ break;
+
+ case PROP_IS_DIRECTORY:
+ gb_rename_file_popover_set_is_directory (self, g_value_get_boolean (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gb_rename_file_popover_class_init (GbRenameFilePopoverClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gb_rename_file_popover_finalize;
+ object_class->get_property = gb_rename_file_popover_get_property;
+ object_class->set_property = gb_rename_file_popover_set_property;
+
+ properties [PROP_FILE] =
+ g_param_spec_object ("file",
+ "File",
+ "File",
+ G_TYPE_FILE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_IS_DIRECTORY] =
+ g_param_spec_boolean ("is-directory",
+ "Is Directory",
+ "Is Directory",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, LAST_PROP, properties);
+
+ signals [RENAME_FILE] =
+ g_signal_new ("rename-file",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_FILE,
+ G_TYPE_FILE);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/builder/ui/gb-rename-file-popover.ui");
+ gtk_widget_class_bind_template_child (widget_class, GbRenameFilePopover, button);
+ gtk_widget_class_bind_template_child (widget_class, GbRenameFilePopover, entry);
+ gtk_widget_class_bind_template_child (widget_class, GbRenameFilePopover, label);
+ gtk_widget_class_bind_template_child (widget_class, GbRenameFilePopover, message);
+}
+
+static void
+gb_rename_file_popover_init (GbRenameFilePopover *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ g_signal_connect_object (self->entry,
+ "changed",
+ G_CALLBACK (gb_rename_file_popover__entry_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->entry,
+ "activate",
+ G_CALLBACK (gb_rename_file_popover__entry_activate),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (self->button,
+ "clicked",
+ G_CALLBACK (gb_rename_file_popover__button_clicked),
+ self,
+ G_CONNECT_SWAPPED);
+}
diff --git a/plugins/project-tree/gb-rename-file-popover.h b/plugins/project-tree/gb-rename-file-popover.h
new file mode 100644
index 0000000..8c2d771
--- /dev/null
+++ b/plugins/project-tree/gb-rename-file-popover.h
@@ -0,0 +1,35 @@
+/* gb-rename-file-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 GB_RENAME_FILE_POPOVER_H
+#define GB_RENAME_FILE_POPOVER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_RENAME_FILE_POPOVER (gb_rename_file_popover_get_type())
+
+G_DECLARE_FINAL_TYPE (GbRenameFilePopover, gb_rename_file_popover,
+ GB, RENAME_FILE_POPOVER, GtkPopover)
+
+GFile *gb_rename_file_popover_get_file (GbRenameFilePopover *self);
+
+G_END_DECLS
+
+#endif /* GB_RENAME_FILE_POPOVER_H */
diff --git a/plugins/project-tree/project-tree-plugin.c b/plugins/project-tree/project-tree-plugin.c
new file mode 100644
index 0000000..4e8bbce
--- /dev/null
+++ b/plugins/project-tree/project-tree-plugin.c
@@ -0,0 +1,30 @@
+/* project-tree-plugin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libpeas/peas.h>
+#include <ide.h>
+
+#include "gb-project-tree-addin.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+ peas_object_module_register_extension_type (module,
+ IDE_TYPE_WORKBENCH_ADDIN,
+ GB_TYPE_PROJECT_TREE_ADDIN);
+}
diff --git a/plugins/project-tree/project-tree.plugin b/plugins/project-tree/project-tree.plugin
new file mode 100644
index 0000000..f610ba7
--- /dev/null
+++ b/plugins/project-tree/project-tree.plugin
@@ -0,0 +1,8 @@
+[Plugin]
+Module=project-tree-plugin
+Name=Project Tree
+Description=Provides a project tree
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2015 Christian Hergert
+Builtin=true
+Hidden=true
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]