From 528c1e5ff51e213936e800fc5a9a25da99c0bdf2 Mon Sep 17 00:00:00 2001 From: Perberos Date: Mon, 7 Nov 2011 16:46:58 -0300 Subject: initial --- plugins/filebrowser/Makefile.am | 104 + .../filebrowser.gedit-plugin.desktop.in | 10 + plugins/filebrowser/gedit-file-bookmarks-store.c | 879 +++++ plugins/filebrowser/gedit-file-bookmarks-store.h | 90 + .../gedit-file-browser-enum-register.c.template | 20 + .../gedit-file-browser-enum-types.c.template | 45 + .../gedit-file-browser-enum-types.h.template | 29 + plugins/filebrowser/gedit-file-browser-error.h | 41 + .../filebrowser/gedit-file-browser-marshal.list | 5 + plugins/filebrowser/gedit-file-browser-messages.c | 1033 ++++++ plugins/filebrowser/gedit-file-browser-messages.h | 35 + plugins/filebrowser/gedit-file-browser-plugin.c | 1254 +++++++ plugins/filebrowser/gedit-file-browser-plugin.h | 71 + plugins/filebrowser/gedit-file-browser-store.c | 3625 ++++++++++++++++++++ plugins/filebrowser/gedit-file-browser-store.h | 200 ++ plugins/filebrowser/gedit-file-browser-utils.c | 198 ++ plugins/filebrowser/gedit-file-browser-utils.h | 27 + plugins/filebrowser/gedit-file-browser-view.c | 1256 +++++++ plugins/filebrowser/gedit-file-browser-view.h | 84 + .../filebrowser/gedit-file-browser-widget-ui.xml | 54 + plugins/filebrowser/gedit-file-browser-widget.c | 3143 +++++++++++++++++ plugins/filebrowser/gedit-file-browser-widget.h | 121 + plugins/filebrowser/gedit-file-browser.schemas.in | 97 + 23 files changed, 12421 insertions(+) create mode 100755 plugins/filebrowser/Makefile.am create mode 100755 plugins/filebrowser/filebrowser.gedit-plugin.desktop.in create mode 100755 plugins/filebrowser/gedit-file-bookmarks-store.c create mode 100755 plugins/filebrowser/gedit-file-bookmarks-store.h create mode 100755 plugins/filebrowser/gedit-file-browser-enum-register.c.template create mode 100755 plugins/filebrowser/gedit-file-browser-enum-types.c.template create mode 100755 plugins/filebrowser/gedit-file-browser-enum-types.h.template create mode 100755 plugins/filebrowser/gedit-file-browser-error.h create mode 100755 plugins/filebrowser/gedit-file-browser-marshal.list create mode 100755 plugins/filebrowser/gedit-file-browser-messages.c create mode 100755 plugins/filebrowser/gedit-file-browser-messages.h create mode 100755 plugins/filebrowser/gedit-file-browser-plugin.c create mode 100755 plugins/filebrowser/gedit-file-browser-plugin.h create mode 100755 plugins/filebrowser/gedit-file-browser-store.c create mode 100755 plugins/filebrowser/gedit-file-browser-store.h create mode 100755 plugins/filebrowser/gedit-file-browser-utils.c create mode 100755 plugins/filebrowser/gedit-file-browser-utils.h create mode 100755 plugins/filebrowser/gedit-file-browser-view.c create mode 100755 plugins/filebrowser/gedit-file-browser-view.h create mode 100755 plugins/filebrowser/gedit-file-browser-widget-ui.xml create mode 100755 plugins/filebrowser/gedit-file-browser-widget.c create mode 100755 plugins/filebrowser/gedit-file-browser-widget.h create mode 100755 plugins/filebrowser/gedit-file-browser.schemas.in (limited to 'plugins/filebrowser') diff --git a/plugins/filebrowser/Makefile.am b/plugins/filebrowser/Makefile.am new file mode 100755 index 00000000..22301d5b --- /dev/null +++ b/plugins/filebrowser/Makefile.am @@ -0,0 +1,104 @@ +# filebrowser + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +BUILT_SOURCES = \ + gedit-file-browser-enum-types.h \ + gedit-file-browser-enum-types.c \ + gedit-file-browser-marshal.h \ + gedit-file-browser-marshal.c + +plugin_LTLIBRARIES = libfilebrowser.la + +NOINST_H_FILES = \ + gedit-file-bookmarks-store.h \ + gedit-file-browser-store.h \ + gedit-file-browser-view.h \ + gedit-file-browser-widget.h \ + gedit-file-browser-error.h \ + gedit-file-browser-utils.h \ + gedit-file-browser-plugin.h \ + gedit-file-browser-messages.h + +libfilebrowser_la_SOURCES = \ + $(BUILT_SOURCES) \ + gedit-file-bookmarks-store.c \ + gedit-file-browser-store.c \ + gedit-file-browser-view.c \ + gedit-file-browser-widget.c \ + gedit-file-browser-utils.c \ + gedit-file-browser-plugin.c \ + gedit-file-browser-messages.c \ + $(NOINST_H_FILES) + +libfilebrowser_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libfilebrowser_la_LIBADD = $(GEDIT_LIBS) + +# UI files (if you use ui for your plugin, list those files here) +uidir = $(GEDIT_PLUGINS_DATA_DIR)/filebrowser +ui_DATA = gedit-file-browser-widget-ui.xml + +plugin_in_files = filebrowser.gedit-plugin.desktop.in + +%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +gedit-file-browser-enum-types.h: gedit-file-browser-enum-types.h.template $(NOINST_H_FILES) $(GLIB_MKENUMS) + (cd $(srcdir) && $(GLIB_MKENUMS) --template gedit-file-browser-enum-types.h.template $(NOINST_H_FILES)) > $@ + +gedit-file-browser-enum-types.c: gedit-file-browser-enum-types.c.template gedit-file-browser-enum-register.c.template $(NOINST_H_FILES) $(GLIB_MKENUMS) + $(AM_V_GEN) (cd $(srcdir) && \ + $(GLIB_MKENUMS) --template gedit-file-browser-enum-types.c.template $(NOINST_H_FILES) && \ + $(GLIB_MKENUMS) --template gedit-file-browser-enum-register.c.template $(NOINST_H_FILES)) > $@ + +gedit-file-browser-marshal.h: gedit-file-browser-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN) $(GLIB_GENMARSHAL) $< --header --prefix=gedit_file_browser_marshal > $@ + +gedit-file-browser-marshal.c: gedit-file-browser-marshal.list $(GLIB_GENMARSHAL) + $(AM_V_GEN) echo "#include \"gedit-file-browser-marshal.h\"" > $@ && \ + $(GLIB_GENMARSHAL) $< --body --prefix=gedit_file_browser_marshal >> $@ + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +schemasdir = $(MATECONF_SCHEMA_FILE_DIR) +schemas_in_files = gedit-file-browser.schemas.in +schemas_DATA = $(schemas_in_files:.schemas.in=.schemas) +@INTLTOOL_SCHEMAS_RULE@ + +if MATECONF_SCHEMAS_INSTALL +install-data-local: + if test -z "$(DESTDIR)" ; then \ + for p in $(schemas_DATA) ; do \ + MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) $(MATECONFTOOL) --makefile-install-rule $(top_builddir)/plugins/filebrowser/$$p ; \ + done \ + fi +else +install-data-local: +endif + + +EXTRA_DIST = \ + $(ui_DATA) \ + $(plugin_in_files) \ + $(schemas_in_files) \ + gedit-file-browser-enum-types.h.template \ + gedit-file-browser-enum-types.c.template \ + gedit-file-browser-enum-register.c.template \ + gedit-file-browser-marshal.list + +CLEANFILES = \ + $(plugin_DATA) \ + $(schemas_DATA) \ + $(BUILT_SOURCES) + +DISTCLEANFILES = \ + $(plugin_DATA) \ + $(schemas_DATA) \ + $(BUILT_SOURCES) + +-include $(top_srcdir)/git.mk diff --git a/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in b/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in new file mode 100755 index 00000000..808816c5 --- /dev/null +++ b/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in @@ -0,0 +1,10 @@ +[Gedit Plugin] +Loader=C +Module=filebrowser +IAge=2 +_Name=File Browser Pane +_Description=Easy file access from the side pane +Icon=system-file-manager +Authors=Jesse van den Kieboom +Copyright=Copyright © 2006 Jesse van den Kieboom +Website=http://www.gedit.org diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.c b/plugins/filebrowser/gedit-file-bookmarks-store.c new file mode 100755 index 00000000..86e7f0c8 --- /dev/null +++ b/plugins/filebrowser/gedit-file-bookmarks-store.c @@ -0,0 +1,879 @@ +/* + * gedit-file-bookmarks-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-utils.h" + +#define GEDIT_FILE_BOOKMARKS_STORE_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_FILE_BOOKMARKS_STORE, \ + GeditFileBookmarksStorePrivate)) + +struct _GeditFileBookmarksStorePrivate +{ + GVolumeMonitor * volume_monitor; + GFileMonitor * bookmarks_monitor; +}; + +static void remove_node (GtkTreeModel * model, + GtkTreeIter * iter); + +static void on_fs_changed (GVolumeMonitor *monitor, + GObject *object, + GeditFileBookmarksStore *model); + +static void on_bookmarks_file_changed (GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + GeditFileBookmarksStore * model); +static gboolean find_with_flags (GtkTreeModel * model, + GtkTreeIter * iter, + gpointer obj, + guint flags, + guint notflags); + +GEDIT_PLUGIN_DEFINE_TYPE(GeditFileBookmarksStore, gedit_file_bookmarks_store, GTK_TYPE_TREE_STORE) + +static void +gedit_file_bookmarks_store_dispose (GObject * object) +{ + GeditFileBookmarksStore *obj = GEDIT_FILE_BOOKMARKS_STORE (object); + + if (obj->priv->volume_monitor != NULL) { + g_signal_handlers_disconnect_by_func (obj->priv->volume_monitor, + on_fs_changed, + obj); + + g_object_unref (obj->priv->volume_monitor); + obj->priv->volume_monitor = NULL; + } + + if (obj->priv->bookmarks_monitor != NULL) { + g_object_unref (obj->priv->bookmarks_monitor); + obj->priv->bookmarks_monitor = NULL; + } + + G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->dispose (object); +} + +static void +gedit_file_bookmarks_store_finalize (GObject * object) +{ + G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->finalize (object); +} + +static void +gedit_file_bookmarks_store_class_init (GeditFileBookmarksStoreClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_file_bookmarks_store_dispose; + object_class->finalize = gedit_file_bookmarks_store_finalize; + + g_type_class_add_private (object_class, sizeof (GeditFileBookmarksStorePrivate)); +} + +static void +gedit_file_bookmarks_store_init (GeditFileBookmarksStore * obj) +{ + obj->priv = GEDIT_FILE_BOOKMARKS_STORE_GET_PRIVATE (obj); +} + +/* Private */ +static void +add_node (GeditFileBookmarksStore *model, + GdkPixbuf *pixbuf, + const gchar *name, + GObject *obj, + guint flags, + GtkTreeIter *iter) +{ + GtkTreeIter newiter; + + gtk_tree_store_append (GTK_TREE_STORE (model), &newiter, NULL); + + gtk_tree_store_set (GTK_TREE_STORE (model), &newiter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, pixbuf, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, name, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, obj, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, flags, + -1); + + if (iter != NULL) + *iter = newiter; +} + +static gboolean +add_file (GeditFileBookmarksStore *model, + GFile *file, + const gchar *name, + guint flags, + GtkTreeIter *iter) +{ + GdkPixbuf *pixbuf = NULL; + gboolean native; + gchar *newname; + + native = g_file_is_native (file); + + if (native && !g_file_query_exists (file, NULL)) { + return FALSE; + } + + if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_HOME) + pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("user-home", GTK_ICON_SIZE_MENU); + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP) + pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("user-desktop", GTK_ICON_SIZE_MENU); + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT) + pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("drive-harddisk", GTK_ICON_SIZE_MENU); + + if (pixbuf == NULL) { + /* getting the icon is a sync get_info call, so we just do it for local files */ + if (native) { + pixbuf = gedit_file_browser_utils_pixbuf_from_file (file, GTK_ICON_SIZE_MENU); + } else { + pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("folder", GTK_ICON_SIZE_MENU); + } + } + + if (name == NULL) { + newname = gedit_file_browser_utils_file_basename (file); + } else { + newname = g_strdup (name); + } + + add_node (model, pixbuf, newname, G_OBJECT (file), flags, iter); + + if (pixbuf) + g_object_unref (pixbuf); + + g_free (newname); + + return TRUE; +} + +static void +check_mount_separator (GeditFileBookmarksStore * model, guint flags, + gboolean added) +{ + GtkTreeIter iter; + gboolean found; + + found = + find_with_flags (GTK_TREE_MODEL (model), &iter, NULL, + flags | + GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR, 0); + + if (added && !found) { + /* Add the separator */ + add_node (model, NULL, NULL, NULL, + flags | GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR, + NULL); + } else if (!added && found) { + remove_node (GTK_TREE_MODEL (model), &iter); + } +} + +static void +init_special_directories (GeditFileBookmarksStore * model) +{ + gchar const *path; + GFile * file; + + path = g_get_home_dir (); + if (path != NULL) + { + file = g_file_new_for_path (path); + add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_HOME | + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL); + g_object_unref (file); + } + + path = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); + if (path != NULL) + { + file = g_file_new_for_path (path); + add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP | + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL); + g_object_unref (file); + } + + path = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS); + if (path != NULL) + { + file = g_file_new_for_path (path); + add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_DOCUMENTS | + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL); + g_object_unref (file); + } + + file = g_file_new_for_uri ("file:///"); + add_file (model, file, _("File System"), GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, NULL); + g_object_unref (file); + + check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, TRUE); +} + +static void +get_fs_properties (gpointer fs, + gchar **name, + GdkPixbuf **pixbuf, + guint *flags) +{ + GIcon *icon = NULL; + + *flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS; + *name = NULL; + *pixbuf = NULL; + + if (G_IS_DRIVE (fs)) + { + icon = g_drive_get_icon (G_DRIVE (fs)); + *name = g_drive_get_name (G_DRIVE (fs)); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE; + } + else if (G_IS_VOLUME (fs)) + { + icon = g_volume_get_icon (G_VOLUME (fs)); + *name = g_volume_get_name (G_VOLUME (fs)); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME; + } + else if (G_IS_MOUNT (fs)) + { + icon = g_mount_get_icon (G_MOUNT (fs)); + *name = g_mount_get_name (G_MOUNT (fs)); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT; + } + + if (icon) + { + *pixbuf = gedit_file_browser_utils_pixbuf_from_icon (icon, GTK_ICON_SIZE_MENU); + g_object_unref (icon); + } +} + + +static void +add_fs (GeditFileBookmarksStore *model, + gpointer fs, + guint flags, + GtkTreeIter *iter) +{ + gchar *name; + GdkPixbuf *pixbuf; + guint fsflags; + + get_fs_properties (fs, &name, &pixbuf, &fsflags); + add_node (model, pixbuf, name, fs, flags | fsflags, iter); + + if (pixbuf) + g_object_unref (pixbuf); + + g_free (name); + check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_FS, TRUE); +} + +static void +process_volume_cb (GVolume *volume, + GeditFileBookmarksStore *model) +{ + GMount *mount; + guint flags = GEDIT_FILE_BOOKMARKS_STORE_NONE; + mount = g_volume_get_mount (volume); + + /* CHECK: should we use the LOCAL/REMOTE thing still? */ + if (mount) + { + /* Show mounted volume */ + add_fs (model, mount, flags, NULL); + g_object_unref (mount); + } + else if (g_volume_can_mount (volume)) + { + /* We also show the unmounted volume here so users can + mount it if they want to access it */ + add_fs (model, volume, flags, NULL); + } +} + +static void +process_drive_novolumes (GeditFileBookmarksStore *model, + GDrive *drive) +{ + if (g_drive_is_media_removable (drive) && + !g_drive_is_media_check_automatic (drive) && + g_drive_can_poll_for_media (drive)) + { + /* This can be the case for floppy drives or other + drives where media detection fails. We show the + drive and poll for media when the user activates + it */ + add_fs (model, drive, GEDIT_FILE_BOOKMARKS_STORE_NONE, NULL); + } +} + +static void +process_drive_cb (GDrive *drive, + GeditFileBookmarksStore *model) +{ + GList *volumes; + + volumes = g_drive_get_volumes (drive); + + if (volumes) + { + /* Add all volumes for the drive */ + g_list_foreach (volumes, (GFunc)process_volume_cb, model); + g_list_free (volumes); + } + else + { + process_drive_novolumes (model, drive); + } +} + +static void +init_drives (GeditFileBookmarksStore *model) +{ + GList *drives; + + drives = g_volume_monitor_get_connected_drives (model->priv->volume_monitor); + + g_list_foreach (drives, (GFunc)process_drive_cb, model); + g_list_foreach (drives, (GFunc)g_object_unref, NULL); + g_list_free (drives); +} + +static void +process_volume_nodrive_cb (GVolume *volume, + GeditFileBookmarksStore *model) +{ + GDrive *drive; + + drive = g_volume_get_drive (volume); + + if (drive) + { + g_object_unref (drive); + return; + } + + process_volume_cb (volume, model); +} + +static void +init_volumes (GeditFileBookmarksStore *model) +{ + GList *volumes; + + volumes = g_volume_monitor_get_volumes (model->priv->volume_monitor); + + g_list_foreach (volumes, (GFunc)process_volume_nodrive_cb, model); + g_list_foreach (volumes, (GFunc)g_object_unref, NULL); + g_list_free (volumes); +} + +static void +process_mount_novolume_cb (GMount *mount, + GeditFileBookmarksStore *model) +{ + GVolume *volume; + + volume = g_mount_get_volume (mount); + + if (volume) + { + g_object_unref (volume); + } + else if (!g_mount_is_shadowed (mount)) + { + /* Add the mount */ + add_fs (model, mount, GEDIT_FILE_BOOKMARKS_STORE_NONE, NULL); + } +} + +static void +init_mounts (GeditFileBookmarksStore *model) +{ + GList *mounts; + + mounts = g_volume_monitor_get_mounts (model->priv->volume_monitor); + + g_list_foreach (mounts, (GFunc)process_mount_novolume_cb, model); + g_list_foreach (mounts, (GFunc)g_object_unref, NULL); + g_list_free (mounts); +} + +static void +init_fs (GeditFileBookmarksStore * model) +{ + if (model->priv->volume_monitor == NULL) { + const gchar **ptr; + const gchar *signals[] = { + "drive-connected", "drive-changed", "drive-disconnected", + "volume-added", "volume-removed", "volume-changed", + "mount-added", "mount-removed", "mount-changed", + NULL + }; + + model->priv->volume_monitor = g_volume_monitor_get (); + + /* Connect signals */ + for (ptr = signals; *ptr; ptr++) + { + g_signal_connect (model->priv->volume_monitor, + *ptr, + G_CALLBACK (on_fs_changed), model); + } + } + + /* First go through all the connected drives */ + init_drives (model); + + /* Then add all volumes, not associated with a drive */ + init_volumes (model); + + /* Then finally add all mounts that have no volume */ + init_mounts (model); +} + +static gboolean +add_bookmark (GeditFileBookmarksStore * model, + gchar const * name, + gchar const * uri) +{ + GFile * file; + gboolean ret; + guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK; + GtkTreeIter iter; + + file = g_file_new_for_uri (uri); + + if (g_file_is_native (file)) { + flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_LOCAL_BOOKMARK; + } else { + flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK; + } + + ret = add_file (model, file, name, flags, &iter); + + g_object_unref (file); + + return ret; +} + +static void +init_bookmarks (GeditFileBookmarksStore * model) +{ + gchar *bookmarks; + GError *error = NULL; + gchar *contents; + gchar **lines; + gchar **line; + gboolean added = FALSE; + + /* Read the bookmarks file */ + bookmarks = g_build_filename (g_get_home_dir (), + ".gtk-bookmarks", + NULL); + + if (g_file_get_contents (bookmarks, &contents, NULL, &error)) { + lines = g_strsplit (contents, "\n", 0); + + for (line = lines; *line; ++line) { + if (**line) { + gchar *pos; + gchar *name; + + /* CHECK: is this really utf8? */ + pos = g_utf8_strchr (*line, -1, ' '); + + if (pos != NULL) { + *pos = '\0'; + name = pos + 1; + } else { + name = NULL; + } + + /* the bookmarks file should contain valid + * URIs, but paranoia is good */ + if (gedit_utils_is_valid_uri (*line)) { + added |= add_bookmark (model, name, *line); + } + } + } + + g_strfreev (lines); + g_free (contents); + + /* Add a watch */ + if (model->priv->bookmarks_monitor == NULL) { + GFile * file; + + file = g_file_new_for_path (bookmarks); + model->priv->bookmarks_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_unref (file); + + g_signal_connect (model->priv->bookmarks_monitor, + "changed", + (GCallback)on_bookmarks_file_changed, + model); + } + } else { + /* The bookmarks file doesn't exist (which is perfectly fine) */ + g_error_free (error); + } + + if (added) { + /* Bookmarks separator */ + add_node (model, NULL, NULL, NULL, + GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK | + GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR, NULL); + } + + g_free (bookmarks); +} + +static gint flags_order[] = { + GEDIT_FILE_BOOKMARKS_STORE_IS_HOME, + GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP, + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, + GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, + GEDIT_FILE_BOOKMARKS_STORE_IS_FS, + GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK, + -1 +}; + +static gint +utf8_casecmp (gchar const *s1, const gchar * s2) +{ + gchar *n1; + gchar *n2; + gint result; + + n1 = g_utf8_casefold (s1, -1); + n2 = g_utf8_casefold (s2, -1); + + result = g_utf8_collate (n1, n2); + + g_free (n1); + g_free (n2); + + return result; +} + +static gint +bookmarks_compare_names (GtkTreeModel * model, GtkTreeIter * a, + GtkTreeIter * b) +{ + gchar *n1; + gchar *n2; + gint result; + guint f1; + guint f2; + + gtk_tree_model_get (model, a, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &n1, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f1, + -1); + gtk_tree_model_get (model, b, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &n2, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f2, + -1); + + /* do not sort actual bookmarks to keep same order as in caja */ + if ((f1 & GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK) && + (f2 & GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK)) + result = 0; + else if (n1 == NULL && n2 == NULL) + result = 0; + else if (n1 == NULL) + result = -1; + else if (n2 == NULL) + result = 1; + else + result = utf8_casecmp (n1, n2); + + g_free (n1); + g_free (n2); + + return result; +} + +static gint +bookmarks_compare_flags (GtkTreeModel * model, GtkTreeIter * a, + GtkTreeIter * b) +{ + guint f1; + guint f2; + gint *flags; + guint sep; + + sep = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR; + + gtk_tree_model_get (model, a, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f1, + -1); + gtk_tree_model_get (model, b, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f2, + -1); + + for (flags = flags_order; *flags != -1; ++flags) { + if ((f1 & *flags) != (f2 & *flags)) { + if (f1 & *flags) { + return -1; + } else { + return 1; + } + } else if ((f1 & *flags) && (f1 & sep) != (f2 & sep)) { + if (f1 & sep) + return -1; + else + return 1; + } + } + + return 0; +} + +static gint +bookmarks_compare_func (GtkTreeModel * model, GtkTreeIter * a, + GtkTreeIter * b, gpointer user_data) +{ + gint result; + + result = bookmarks_compare_flags (model, a, b); + + if (result == 0) + result = bookmarks_compare_names (model, a, b); + + return result; +} + +static gboolean +find_with_flags (GtkTreeModel * model, GtkTreeIter * iter, gpointer obj, + guint flags, guint notflags) +{ + GtkTreeIter child; + guint childflags = 0; + GObject * childobj; + gboolean fequal; + + if (!gtk_tree_model_get_iter_first (model, &child)) + return FALSE; + + do { + gtk_tree_model_get (model, &child, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + &childobj, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + &childflags, -1); + + fequal = (obj == childobj); + + if (childobj) + g_object_unref (childobj); + + if ((obj == NULL || fequal) && + (childflags & flags) == flags + && !(childflags & notflags)) { + *iter = child; + return TRUE; + } + } while (gtk_tree_model_iter_next (model, &child)); + + return FALSE; +} + +static void +remove_node (GtkTreeModel * model, GtkTreeIter * iter) +{ + guint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!(flags & GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR)) { + if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS) { + check_mount_separator (GEDIT_FILE_BOOKMARKS_STORE (model), + flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS, + FALSE); + } + } + + gtk_tree_store_remove (GTK_TREE_STORE (model), iter); +} + +static void +remove_bookmarks (GeditFileBookmarksStore * model) +{ + GtkTreeIter iter; + + while (find_with_flags (GTK_TREE_MODEL (model), &iter, NULL, + GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK, + 0)) { + remove_node (GTK_TREE_MODEL (model), &iter); + } +} + +static void +initialize_fill (GeditFileBookmarksStore * model) +{ + init_special_directories (model); + init_fs (model); + init_bookmarks (model); +} + +/* Public */ +GeditFileBookmarksStore * +gedit_file_bookmarks_store_new (void) +{ + GeditFileBookmarksStore *model; + GType column_types[] = { + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_OBJECT, + G_TYPE_UINT + }; + + model = g_object_new (GEDIT_TYPE_FILE_BOOKMARKS_STORE, NULL); + gtk_tree_store_set_column_types (GTK_TREE_STORE (model), + GEDIT_FILE_BOOKMARKS_STORE_N_COLUMNS, + column_types); + + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model), + bookmarks_compare_func, + NULL, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), + GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, + GTK_SORT_ASCENDING); + + initialize_fill (model); + + return model; +} + +gchar * +gedit_file_bookmarks_store_get_uri (GeditFileBookmarksStore * model, + GtkTreeIter * iter) +{ + GObject * obj; + GFile * file = NULL; + guint flags; + gchar * ret = NULL; + gboolean isfs; + + g_return_val_if_fail (GEDIT_IS_FILE_BOOKMARKS_STORE (model), NULL); + g_return_val_if_fail (iter != NULL, NULL); + + gtk_tree_model_get (GTK_TREE_MODEL (model), iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + &flags, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + &obj, + -1); + + if (obj == NULL) + return NULL; + + isfs = (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS); + + if (isfs && (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT)) + { + file = g_mount_get_root (G_MOUNT (obj)); + } + else if (!isfs) + { + file = g_object_ref (obj); + } + + g_object_unref (obj); + + if (file) + { + ret = g_file_get_uri (file); + g_object_unref (file); + } + + return ret; +} + +void +gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore * model) +{ + gtk_tree_store_clear (GTK_TREE_STORE (model)); + initialize_fill (model); +} + +static void +on_fs_changed (GVolumeMonitor *monitor, + GObject *object, + GeditFileBookmarksStore *model) +{ + GtkTreeModel *tree_model = GTK_TREE_MODEL (model); + guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS; + guint noflags = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR; + GtkTreeIter iter; + + /* clear all fs items */ + while (find_with_flags (tree_model, &iter, NULL, flags, noflags)) + remove_node (tree_model, &iter); + + /* then reinitialize */ + init_fs (model); +} + +static void +on_bookmarks_file_changed (GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + GeditFileBookmarksStore * model) +{ + switch (event_type) { + case G_FILE_MONITOR_EVENT_CHANGED: + case G_FILE_MONITOR_EVENT_CREATED: + /* Re-initialize bookmarks */ + remove_bookmarks (model); + init_bookmarks (model); + break; + case G_FILE_MONITOR_EVENT_DELETED: // FIXME: shouldn't we also monitor the directory? + /* Remove bookmarks */ + remove_bookmarks (model); + g_object_unref (monitor); + model->priv->bookmarks_monitor = NULL; + break; + default: + break; + } +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.h b/plugins/filebrowser/gedit-file-bookmarks-store.h new file mode 100755 index 00000000..bd20911e --- /dev/null +++ b/plugins/filebrowser/gedit-file-bookmarks-store.h @@ -0,0 +1,90 @@ +/* + * gedit-file-bookmarks-store.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GEDIT_FILE_BOOKMARKS_STORE_H__ +#define __GEDIT_FILE_BOOKMARKS_STORE_H__ + +#include + +G_BEGIN_DECLS +#define GEDIT_TYPE_FILE_BOOKMARKS_STORE (gedit_file_bookmarks_store_get_type ()) +#define GEDIT_FILE_BOOKMARKS_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStore)) +#define GEDIT_FILE_BOOKMARKS_STORE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStore const)) +#define GEDIT_FILE_BOOKMARKS_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStoreClass)) +#define GEDIT_IS_FILE_BOOKMARKS_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE)) +#define GEDIT_IS_FILE_BOOKMARKS_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BOOKMARKS_STORE)) +#define GEDIT_FILE_BOOKMARKS_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStoreClass)) + +typedef struct _GeditFileBookmarksStore GeditFileBookmarksStore; +typedef struct _GeditFileBookmarksStoreClass GeditFileBookmarksStoreClass; +typedef struct _GeditFileBookmarksStorePrivate GeditFileBookmarksStorePrivate; + +enum +{ + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON = 0, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + GEDIT_FILE_BOOKMARKS_STORE_N_COLUMNS +}; + +enum +{ + GEDIT_FILE_BOOKMARKS_STORE_NONE = 0, + GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR = 1 << 0, /* Separator item */ + GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR = 1 << 1, /* Special user dir */ + GEDIT_FILE_BOOKMARKS_STORE_IS_HOME = 1 << 2, /* The special Home user directory */ + GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP = 1 << 3, /* The special Desktop user directory */ + GEDIT_FILE_BOOKMARKS_STORE_IS_DOCUMENTS = 1 << 4, /* The special Documents user directory */ + GEDIT_FILE_BOOKMARKS_STORE_IS_FS = 1 << 5, /* A mount object */ + GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT = 1 << 6, /* A mount object */ + GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME = 1 << 7, /* A volume object */ + GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE = 1 << 8, /* A drive object */ + GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT = 1 << 9, /* The root file system (file:///) */ + GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK = 1 << 10, /* A gtk bookmark */ + GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK = 1 << 11, /* A remote gtk bookmark */ + GEDIT_FILE_BOOKMARKS_STORE_IS_LOCAL_BOOKMARK = 1 << 12 /* A local gtk bookmark */ +}; + +struct _GeditFileBookmarksStore +{ + GtkTreeStore parent; + + GeditFileBookmarksStorePrivate *priv; +}; + +struct _GeditFileBookmarksStoreClass +{ + GtkTreeStoreClass parent_class; +}; + +GType gedit_file_bookmarks_store_get_type (void) G_GNUC_CONST; +GType gedit_file_bookmarks_store_register_type (GTypeModule * module); + +GeditFileBookmarksStore *gedit_file_bookmarks_store_new (void); +gchar *gedit_file_bookmarks_store_get_uri (GeditFileBookmarksStore * model, + GtkTreeIter * iter); +void gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore * model); + +G_END_DECLS +#endif /* __GEDIT_FILE_BOOKMARKS_STORE_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-enum-register.c.template b/plugins/filebrowser/gedit-file-browser-enum-register.c.template new file mode 100755 index 00000000..63a9c562 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-register.c.template @@ -0,0 +1,20 @@ +/*** BEGIN file-header ***/ +void +gedit_file_browser_enum_and_flag_register_type (GTypeModule * module) +{ +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + /* Enumerations from "@filename@" */ + +/*** END file-production ***/ + +/*** BEGIN enumeration-production ***/ + register_@enum_name@ (module); + +/*** END enumeration-production ***/ + +/*** BEGIN file-tail ***/ +} + +/*** END file-tail ***/ diff --git a/plugins/filebrowser/gedit-file-browser-enum-types.c.template b/plugins/filebrowser/gedit-file-browser-enum-types.c.template new file mode 100755 index 00000000..4e89370d --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-types.c.template @@ -0,0 +1,45 @@ +/*** BEGIN file-header ***/ +#include "gedit-file-browser-enum-types.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +#include "@filename@" + +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +static GType @enum_name@_type = 0; + +static GType +register_@enum_name@ (GTypeModule *module) +{ + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, + "@VALUENAME@", + "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + + @enum_name@_type = + g_type_module_register_@type@ (module, + "@EnumName@", + values); + + return @enum_name@_type; +} + +GType +@enum_name@_get_type (void) +{ + return @enum_name@_type; +} + +/*** END value-tail ***/ diff --git a/plugins/filebrowser/gedit-file-browser-enum-types.h.template b/plugins/filebrowser/gedit-file-browser-enum-types.h.template new file mode 100755 index 00000000..aea4fad9 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-types.h.template @@ -0,0 +1,29 @@ +/*** BEGIN file-header ***/ +#ifndef __GEDIT_FILE_BROWSER_ENUM_TYPES_H__ +#define __GEDIT_FILE_BROWSER_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* Enumerations from "@filename@" */ + +/*** END file-production ***/ + +/*** BEGIN enumeration-production ***/ +#define GEDIT_TYPE_@ENUMSHORT@ (@enum_name@_get_type()) +GType @enum_name@_get_type (void) G_GNUC_CONST; + +/*** END enumeration-production ***/ + +/*** BEGIN file-tail ***/ +void gedit_file_browser_enum_and_flag_register_type (GTypeModule * module); + +G_END_DECLS + +#endif /* __GEDIT_FILE_BROWSER_ENUM_TYPES_H__ */ +/*** END file-tail ***/ + diff --git a/plugins/filebrowser/gedit-file-browser-error.h b/plugins/filebrowser/gedit-file-browser-error.h new file mode 100755 index 00000000..ec5b8618 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-error.h @@ -0,0 +1,41 @@ +/* + * gedit-file-browser-error.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GEDIT_FILE_BROWSER_ERROR_H__ +#define __GEDIT_FILE_BROWSER_ERROR_H__ + +G_BEGIN_DECLS + +typedef enum { + GEDIT_FILE_BROWSER_ERROR_NONE, + GEDIT_FILE_BROWSER_ERROR_RENAME, + GEDIT_FILE_BROWSER_ERROR_DELETE, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY, + GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, + GEDIT_FILE_BROWSER_ERROR_NUM +} GeditFileBrowserError; + +G_END_DECLS + +#endif /* __GEDIT_FILE_BROWSER_ERROR_H__ */ diff --git a/plugins/filebrowser/gedit-file-browser-marshal.list b/plugins/filebrowser/gedit-file-browser-marshal.list new file mode 100755 index 00000000..5fa72c8b --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-marshal.list @@ -0,0 +1,5 @@ +VOID:UINT,STRING +VOID:STRING,STRING +BOOL:OBJECT,POINTER +BOOL:POINTER +BOOL:VOID diff --git a/plugins/filebrowser/gedit-file-browser-messages.c b/plugins/filebrowser/gedit-file-browser-messages.c new file mode 100755 index 00000000..b587edf1 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-messages.c @@ -0,0 +1,1033 @@ +#include "gedit-file-browser-messages.h" +#include "gedit-file-browser-store.h" +#include + +#define MESSAGE_OBJECT_PATH "/plugins/filebrowser" +#define WINDOW_DATA_KEY "GeditFileBrowserMessagesWindowData" + +#define BUS_CONNECT(bus, name, data) gedit_message_bus_connect(bus, MESSAGE_OBJECT_PATH, #name, (GeditMessageCallback) message_##name##_cb, data, NULL) + +typedef struct +{ + GeditWindow *window; + GeditMessage *message; +} MessageCacheData; + +typedef struct +{ + guint row_inserted_id; + guint row_deleted_id; + guint root_changed_id; + guint begin_loading_id; + guint end_loading_id; + + GList *merge_ids; + GtkActionGroup *merged_actions; + + GeditMessageBus *bus; + GeditFileBrowserWidget *widget; + GHashTable *row_tracking; + + GHashTable *filters; +} WindowData; + +typedef struct +{ + gulong id; + + GeditWindow *window; + GeditMessage *message; +} FilterData; + +static WindowData * +window_data_new (GeditWindow *window, + GeditFileBrowserWidget *widget) +{ + WindowData *data = g_slice_new (WindowData); + GtkUIManager *manager; + GList *groups; + + data->bus = gedit_window_get_message_bus (window); + data->widget = widget; + data->row_tracking = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + (GDestroyNotify)gtk_tree_row_reference_free); + + data->filters = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify)g_free, + NULL); + + manager = gedit_file_browser_widget_get_ui_manager (widget); + + data->merge_ids = NULL; + data->merged_actions = gtk_action_group_new ("MessageMergedActions"); + + groups = gtk_ui_manager_get_action_groups (manager); + gtk_ui_manager_insert_action_group (manager, data->merged_actions, g_list_length (groups)); + + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, data); + + return data; +} + +static WindowData * +get_window_data (GeditWindow * window) +{ + return (WindowData *) (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY)); +} + +static void +window_data_free (GeditWindow *window) +{ + WindowData *data = get_window_data (window); + GtkUIManager *manager; + GList *item; + + g_hash_table_destroy (data->row_tracking); + g_hash_table_destroy (data->filters); + + manager = gedit_file_browser_widget_get_ui_manager (data->widget); + gtk_ui_manager_remove_action_group (manager, data->merged_actions); + + for (item = data->merge_ids; item; item = item->next) + gtk_ui_manager_remove_ui (manager, GPOINTER_TO_INT (item->data)); + + g_list_free (data->merge_ids); + g_object_unref (data->merged_actions); + + g_slice_free (WindowData, data); + + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); +} + +static FilterData * +filter_data_new (GeditWindow *window, + GeditMessage *message) +{ + FilterData *data = g_slice_new (FilterData); + WindowData *wdata; + + data->window = window; + data->id = 0; + data->message = message; + + wdata = get_window_data (window); + + g_hash_table_insert (wdata->filters, + gedit_message_type_identifier (gedit_message_get_object_path (message), + gedit_message_get_method (message)), + data); + + return data; +} + +static void +filter_data_free (FilterData *data) +{ + WindowData *wdata = get_window_data (data->window); + gchar *identifier; + + identifier = gedit_message_type_identifier (gedit_message_get_object_path (data->message), + gedit_message_get_method (data->message)); + + g_hash_table_remove (wdata->filters, identifier); + g_free (identifier); + + g_object_unref (data->message); + g_slice_free (FilterData, data); +} + +static GtkTreePath * +track_row_lookup (WindowData *data, + const gchar *id) +{ + GtkTreeRowReference *ref; + + ref = (GtkTreeRowReference *)g_hash_table_lookup (data->row_tracking, id); + + if (!ref) + return NULL; + + return gtk_tree_row_reference_get_path (ref); +} + +static void +message_cache_data_free (MessageCacheData *data) +{ + g_object_unref (data->message); + g_slice_free (MessageCacheData, data); +} + +static MessageCacheData * +message_cache_data_new (GeditWindow *window, + GeditMessage *message) +{ + MessageCacheData *data = g_slice_new (MessageCacheData); + + data->window = window; + data->message = message; + + return data; +} + +static void +message_get_root_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GeditFileBrowserStore *store; + gchar *uri; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + uri = gedit_file_browser_store_get_virtual_root (store); + + gedit_message_set (message, "uri", uri, NULL); + g_free (uri); +} + +static void +message_set_root_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gchar *root = NULL; + gchar *virtual = NULL; + + gedit_message_get (message, "uri", &root, NULL); + + if (!root) + return; + + if (gedit_message_has_key (message, "virtual")) + gedit_message_get (message, "virtual", &virtual, NULL); + + if (virtual) + gedit_file_browser_widget_set_root_and_virtual_root (data->widget, root, virtual); + else + gedit_file_browser_widget_set_root (data->widget, root, TRUE); + + g_free (root); + g_free (virtual); +} + +static void +message_set_emblem_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gchar *id = NULL; + gchar *emblem = NULL; + GtkTreePath *path; + GeditFileBrowserStore *store; + + gedit_message_get (message, "id", &id, "emblem", &emblem, NULL); + + if (!id || !emblem) + { + g_free (id); + g_free (emblem); + + return; + } + + path = track_row_lookup (data, id); + + if (path != NULL) + { + GError *error = NULL; + GdkPixbuf *pixbuf; + + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + emblem, + 10, + 0, + &error); + + if (pixbuf) + { + GValue value = { 0, }; + GtkTreeIter iter; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path)) + { + g_value_init (&value, GDK_TYPE_PIXBUF); + g_value_set_object (&value, pixbuf); + + gedit_file_browser_store_set_value (store, + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM, + &value); + + g_value_unset (&value); + } + + g_object_unref (pixbuf); + } + + if (error) + g_error_free (error); + } + + g_free (id); + g_free (emblem); +} + +static gchar * +item_id (const gchar *path, + const gchar *uri) +{ + return g_strconcat (path, "::", uri, NULL); +} + +static gchar * +track_row (WindowData *data, + GeditFileBrowserStore *store, + GtkTreePath *path, + const gchar *uri) +{ + GtkTreeRowReference *ref; + gchar *id; + gchar *pathstr; + + pathstr = gtk_tree_path_to_string (path); + id = item_id (pathstr, uri); + + ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), path); + g_hash_table_insert (data->row_tracking, g_strdup (id), ref); + + g_free (pathstr); + + return id; +} + +static void +set_item_message (WindowData *data, + GtkTreeIter *iter, + GtkTreePath *path, + GeditMessage *message) +{ + GeditFileBrowserStore *store; + gchar *uri = NULL; + guint flags = 0; + gchar *track_id; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!uri) + return; + + if (path && gtk_tree_path_get_depth (path) != 0) + track_id = track_row (data, store, path, uri); + else + track_id = NULL; + + gedit_message_set (message, + "id", track_id, + "uri", uri, + NULL); + + if (gedit_message_has_key (message, "is_directory")) + { + gedit_message_set (message, + "is_directory", FILE_IS_DIR (flags), + NULL); + } + + g_free (uri); + g_free (track_id); +} + +static gboolean +custom_message_filter_func (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GtkTreeIter *iter, + FilterData *data) +{ + WindowData *wdata = get_window_data (data->window); + gchar *uri = NULL; + guint flags = 0; + gboolean filter = FALSE; + GtkTreePath *path; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!uri || FILE_IS_DUMMY (flags)) + { + g_free (uri); + return FALSE; + } + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + set_item_message (wdata, iter, path, data->message); + gtk_tree_path_free (path); + + gedit_message_set (data->message, "filter", filter, NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + gedit_message_get (data->message, "filter", &filter, NULL); + + return !filter; +} + +static void +message_add_filter_cb (GeditMessageBus *bus, + GeditMessage *message, + GeditWindow *window) +{ + gchar *object_path = NULL; + gchar *method = NULL; + gulong id; + GeditMessageType *message_type; + GeditMessage *cbmessage; + FilterData *filter_data; + WindowData *data = get_window_data (window); + + gedit_message_get (message, + "object_path", &object_path, + "method", &method, + NULL); + + // Check if there exists such a 'callback' message + if (!object_path || !method) + { + g_free (object_path); + g_free (method); + + return; + } + + message_type = gedit_message_bus_lookup (bus, object_path, method); + + if (!message_type) + { + g_free (object_path); + g_free (method); + + return; + } + + // Check if the message type has the correct arguments + if (gedit_message_type_lookup (message_type, "id") != G_TYPE_STRING || + gedit_message_type_lookup (message_type, "uri") != G_TYPE_STRING || + gedit_message_type_lookup (message_type, "is_directory") != G_TYPE_BOOLEAN || + gedit_message_type_lookup (message_type, "filter") != G_TYPE_BOOLEAN) + { + return; + } + + cbmessage = gedit_message_type_instantiate (message_type, + "id", NULL, + "uri", NULL, + "is_directory", FALSE, + "filter", FALSE, + NULL); + + // Register the custom filter on the widget + filter_data = filter_data_new (window, cbmessage); + id = gedit_file_browser_widget_add_filter (data->widget, + (GeditFileBrowserWidgetFilterFunc)custom_message_filter_func, + filter_data, + (GDestroyNotify)filter_data_free); + + filter_data->id = id; +} + +static void +message_remove_filter_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gulong id = 0; + + gedit_message_get (message, "id", &id, NULL); + + if (!id) + return; + + gedit_file_browser_widget_remove_filter (data->widget, id); +} + +static void +message_up_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GeditFileBrowserStore *store = gedit_file_browser_widget_get_browser_store (data->widget); + + gedit_file_browser_store_set_virtual_root_up (store); +} + +static void +message_history_back_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_history_back (data->widget); +} + +static void +message_history_forward_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_history_forward (data->widget); +} + +static void +message_refresh_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_refresh (data->widget); +} + +static void +message_set_show_hidden_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gboolean active = FALSE; + GeditFileBrowserStore *store; + GeditFileBrowserStoreFilterMode mode; + + gedit_message_get (message, "active", &active, NULL); + + store = gedit_file_browser_widget_get_browser_store (data->widget); + mode = gedit_file_browser_store_get_filter_mode (store); + + if (active) + mode &= ~GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; + else + mode |= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; + + gedit_file_browser_store_set_filter_mode (store, mode); +} + +static void +message_set_show_binary_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gboolean active = FALSE; + GeditFileBrowserStore *store; + GeditFileBrowserStoreFilterMode mode; + + gedit_message_get (message, "active", &active, NULL); + + store = gedit_file_browser_widget_get_browser_store (data->widget); + mode = gedit_file_browser_store_get_filter_mode (store); + + if (active) + mode &= ~GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY; + else + mode |= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY; + + gedit_file_browser_store_set_filter_mode (store, mode); +} + +static void +message_show_bookmarks_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_show_bookmarks (data->widget); +} + +static void +message_show_files_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gedit_file_browser_widget_show_files (data->widget); +} + +static void +message_add_context_item_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GtkAction *action = NULL; + gchar *path = NULL; + gchar *name; + GtkUIManager *manager; + guint merge_id; + + gedit_message_get (message, + "action", &action, + "path", &path, + NULL); + + if (!action || !path) + { + if (action) + g_object_unref (action); + + g_free (path); + return; + } + + gtk_action_group_add_action (data->merged_actions, action); + manager = gedit_file_browser_widget_get_ui_manager (data->widget); + name = g_strconcat (gtk_action_get_name (action), "MenuItem", NULL); + merge_id = gtk_ui_manager_new_merge_id (manager); + + gtk_ui_manager_add_ui (manager, + merge_id, + path, + name, + gtk_action_get_name (action), + GTK_UI_MANAGER_AUTO, + FALSE); + + if (gtk_ui_manager_get_widget (manager, path)) + { + data->merge_ids = g_list_prepend (data->merge_ids, GINT_TO_POINTER (merge_id)); + gedit_message_set (message, "id", merge_id, NULL); + } + else + { + gedit_message_set (message, "id", 0, NULL); + } + + g_object_unref (action); + g_free (path); + g_free (name); +} + +static void +message_remove_context_item_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + guint merge_id = 0; + GtkUIManager *manager; + + gedit_message_get (message, "id", &merge_id, NULL); + + if (merge_id == 0) + return; + + manager = gedit_file_browser_widget_get_ui_manager (data->widget); + + data->merge_ids = g_list_remove (data->merge_ids, GINT_TO_POINTER (merge_id)); + gtk_ui_manager_remove_ui (manager, merge_id); +} + +static void +message_get_view_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GeditFileBrowserView *view; + view = gedit_file_browser_widget_get_browser_view (data->widget); + + gedit_message_set (message, "view", view, NULL); +} + +static void +register_methods (GeditWindow *window, + GeditFileBrowserWidget *widget) +{ + GeditMessageBus *bus = gedit_window_get_message_bus (window); + WindowData *data = get_window_data (window); + + /* Register method calls */ + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "get_root", + 1, + "uri", G_TYPE_STRING, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "set_root", + 1, + "uri", G_TYPE_STRING, + "virtual", G_TYPE_STRING, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "set_emblem", + 0, + "id", G_TYPE_STRING, + "emblem", G_TYPE_STRING, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "add_filter", + 1, + "object_path", G_TYPE_STRING, + "method", G_TYPE_STRING, + "id", G_TYPE_ULONG, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "remove_filter", + 0, + "id", G_TYPE_ULONG, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "add_context_item", + 1, + "action", GTK_TYPE_ACTION, + "path", G_TYPE_STRING, + "id", G_TYPE_UINT, + NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "remove_context_item", + 0, + "id", G_TYPE_UINT, + NULL); + + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "up", 0, NULL); + + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "history_back", 0, NULL); + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "history_forward", 0, NULL); + + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "refresh", 0, NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "set_show_hidden", + 0, + "active", G_TYPE_BOOLEAN, + NULL); + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "set_show_binary", + 0, + "active", G_TYPE_BOOLEAN, + NULL); + + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "show_bookmarks", 0, NULL); + gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "show_files", 0, NULL); + + gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "get_view", + 1, + "view", GEDIT_TYPE_FILE_BROWSER_VIEW, + NULL); + + BUS_CONNECT (bus, get_root, data); + BUS_CONNECT (bus, set_root, data); + BUS_CONNECT (bus, set_emblem, data); + BUS_CONNECT (bus, add_filter, window); + BUS_CONNECT (bus, remove_filter, data); + + BUS_CONNECT (bus, add_context_item, data); + BUS_CONNECT (bus, remove_context_item, data); + + BUS_CONNECT (bus, up, data); + BUS_CONNECT (bus, history_back, data); + BUS_CONNECT (bus, history_forward, data); + + BUS_CONNECT (bus, refresh, data); + + BUS_CONNECT (bus, set_show_hidden, data); + BUS_CONNECT (bus, set_show_binary, data); + + BUS_CONNECT (bus, show_bookmarks, data); + BUS_CONNECT (bus, show_files, data); + + BUS_CONNECT (bus, get_view, data); +} + +static void +store_row_inserted (GeditFileBrowserStore *store, + GtkTreePath *path, + GtkTreeIter *iter, + MessageCacheData *data) +{ + gchar *uri = NULL; + guint flags = 0; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags)) + { + WindowData *wdata = get_window_data (data->window); + + set_item_message (wdata, iter, path, data->message); + gedit_message_bus_send_message_sync (wdata->bus, data->message); + } + + g_free (uri); +} + +static void +store_row_deleted (GeditFileBrowserStore *store, + GtkTreePath *path, + MessageCacheData *data) +{ + GtkTreeIter iter; + gchar *uri = NULL; + guint flags = 0; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags)) + { + WindowData *wdata = get_window_data (data->window); + + set_item_message (wdata, &iter, path, data->message); + gedit_message_bus_send_message_sync (wdata->bus, data->message); + } + + g_free (uri); +} + +static void +store_virtual_root_changed (GeditFileBrowserStore *store, + GParamSpec *spec, + MessageCacheData *data) +{ + WindowData *wdata = get_window_data (data->window); + gchar *uri; + + uri = gedit_file_browser_store_get_virtual_root (store); + + if (!uri) + return; + + gedit_message_set (data->message, + "uri", uri, + NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + + g_free (uri); +} + +static void +store_begin_loading (GeditFileBrowserStore *store, + GtkTreeIter *iter, + MessageCacheData *data) +{ + GtkTreePath *path; + WindowData *wdata = get_window_data (data->window); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + + set_item_message (wdata, iter, path, data->message); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + gtk_tree_path_free (path); +} + +static void +store_end_loading (GeditFileBrowserStore *store, + GtkTreeIter *iter, + MessageCacheData *data) +{ + GtkTreePath *path; + WindowData *wdata = get_window_data (data->window); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + + set_item_message (wdata, iter, path, data->message); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + gtk_tree_path_free (path); +} + +static void +register_signals (GeditWindow *window, + GeditFileBrowserWidget *widget) +{ + GeditMessageBus *bus = gedit_window_get_message_bus (window); + GeditFileBrowserStore *store; + GeditMessageType *inserted_type; + GeditMessageType *deleted_type; + GeditMessageType *begin_loading_type; + GeditMessageType *end_loading_type; + GeditMessageType *root_changed_type; + + GeditMessage *message; + WindowData *data; + + /* Register signals */ + root_changed_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "root_changed", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + NULL); + + begin_loading_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "begin_loading", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + NULL); + + end_loading_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "end_loading", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + NULL); + + inserted_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "inserted", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + "is_directory", G_TYPE_BOOLEAN, + NULL); + + deleted_type = gedit_message_bus_register (bus, + MESSAGE_OBJECT_PATH, "deleted", + 0, + "id", G_TYPE_STRING, + "uri", G_TYPE_STRING, + "is_directory", G_TYPE_BOOLEAN, + NULL); + + store = gedit_file_browser_widget_get_browser_store (widget); + + message = gedit_message_type_instantiate (inserted_type, + "id", NULL, + "uri", NULL, + "is_directory", FALSE, + NULL); + + data = get_window_data (window); + + data->row_inserted_id = + g_signal_connect_data (store, + "row-inserted", + G_CALLBACK (store_row_inserted), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = gedit_message_type_instantiate (deleted_type, + "id", NULL, + "uri", NULL, + "is_directory", FALSE, + NULL); + data->row_deleted_id = + g_signal_connect_data (store, + "row-deleted", + G_CALLBACK (store_row_deleted), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = gedit_message_type_instantiate (root_changed_type, + "id", NULL, + "uri", NULL, + NULL); + data->root_changed_id = + g_signal_connect_data (store, + "notify::virtual-root", + G_CALLBACK (store_virtual_root_changed), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = gedit_message_type_instantiate (begin_loading_type, + "id", NULL, + "uri", NULL, + NULL); + data->begin_loading_id = + g_signal_connect_data (store, + "begin_loading", + G_CALLBACK (store_begin_loading), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = gedit_message_type_instantiate (end_loading_type, + "id", NULL, + "uri", NULL, + NULL); + data->end_loading_id = + g_signal_connect_data (store, + "end_loading", + G_CALLBACK (store_end_loading), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); +} + +static void +message_unregistered (GeditMessageBus *bus, + GeditMessageType *message_type, + GeditWindow *window) +{ + gchar *identifier = gedit_message_type_identifier (gedit_message_type_get_object_path (message_type), + gedit_message_type_get_method (message_type)); + FilterData *data; + WindowData *wdata = get_window_data (window); + + data = g_hash_table_lookup (wdata->filters, identifier); + + if (data) + gedit_file_browser_widget_remove_filter (wdata->widget, data->id); + + g_free (identifier); +} + +void +gedit_file_browser_messages_register (GeditWindow *window, + GeditFileBrowserWidget *widget) +{ + window_data_new (window, widget); + + register_methods (window, widget); + register_signals (window, widget); + + g_signal_connect (gedit_window_get_message_bus (window), + "unregistered", + G_CALLBACK (message_unregistered), + window); +} + +static void +cleanup_signals (GeditWindow *window) +{ + WindowData *data = get_window_data (window); + GeditFileBrowserStore *store; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + + g_signal_handler_disconnect (store, data->row_inserted_id); + g_signal_handler_disconnect (store, data->row_deleted_id); + g_signal_handler_disconnect (store, data->root_changed_id); + g_signal_handler_disconnect (store, data->begin_loading_id); + g_signal_handler_disconnect (store, data->end_loading_id); + + g_signal_handlers_disconnect_by_func (data->bus, message_unregistered, window); +} + +void +gedit_file_browser_messages_unregister (GeditWindow *window) +{ + GeditMessageBus *bus = gedit_window_get_message_bus (window); + + cleanup_signals (window); + gedit_message_bus_unregister_all (bus, MESSAGE_OBJECT_PATH); + + window_data_free (window); +} diff --git a/plugins/filebrowser/gedit-file-browser-messages.h b/plugins/filebrowser/gedit-file-browser-messages.h new file mode 100755 index 00000000..e62094e8 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-messages.h @@ -0,0 +1,35 @@ +/* + * gedit-file-browser-messages.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2008 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GEDIT_FILE_BROWSER_MESSAGES_H__ +#define __GEDIT_FILE_BROWSER_MESSAGES_H__ + +#include +#include +#include "gedit-file-browser-widget.h" + +void gedit_file_browser_messages_register (GeditWindow *window, + GeditFileBrowserWidget *widget); +void gedit_file_browser_messages_unregister (GeditWindow *window); + +#endif /* __GEDIT_FILE_BROWSER_MESSAGES_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-plugin.c b/plugins/filebrowser/gedit-file-browser-plugin.c new file mode 100755 index 00000000..f2da19f5 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-plugin.c @@ -0,0 +1,1254 @@ +/* + * gedit-file-browser-plugin.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "gedit-file-browser-enum-types.h" +#include "gedit-file-browser-plugin.h" +#include "gedit-file-browser-utils.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-widget.h" +#include "gedit-file-browser-messages.h" + +#define WINDOW_DATA_KEY "GeditFileBrowserPluginWindowData" +#define FILE_BROWSER_BASE_KEY "/apps/gedit-2/plugins/filebrowser" +#define CAJA_CLICK_POLICY_BASE_KEY "/apps/caja/preferences" +#define CAJA_CLICK_POLICY_KEY "click_policy" +#define CAJA_ENABLE_DELETE_KEY "enable_delete" +#define CAJA_CONFIRM_TRASH_KEY "confirm_trash" +#define TERMINAL_EXEC_KEY "/desktop/mate/applications/terminal/exec" + +#define GEDIT_FILE_BROWSER_PLUGIN_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginPrivate)) + +struct _GeditFileBrowserPluginPrivate +{ + gpointer dummy; +}; + +typedef struct _GeditFileBrowserPluginData +{ + GeditFileBrowserWidget * tree_widget; + gulong merge_id; + GtkActionGroup * action_group; + GtkActionGroup * single_selection_action_group; + gboolean auto_root; + gulong end_loading_handle; + gboolean confirm_trash; + + guint click_policy_handle; + guint enable_delete_handle; + guint confirm_trash_handle; +} GeditFileBrowserPluginData; + +static void on_uri_activated_cb (GeditFileBrowserWidget * widget, + gchar const *uri, + GeditWindow * window); +static void on_error_cb (GeditFileBrowserWidget * widget, + guint code, + gchar const *message, + GeditWindow * window); +static void on_model_set_cb (GeditFileBrowserView * widget, + GParamSpec *arg1, + GeditWindow * window); +static void on_virtual_root_changed_cb (GeditFileBrowserStore * model, + GParamSpec * param, + GeditWindow * window); +static void on_filter_mode_changed_cb (GeditFileBrowserStore * model, + GParamSpec * param, + GeditWindow * window); +static void on_rename_cb (GeditFileBrowserStore * model, + const gchar * olduri, + const gchar * newuri, + GeditWindow * window); +static void on_filter_pattern_changed_cb (GeditFileBrowserWidget * widget, + GParamSpec * param, + GeditWindow * window); +static void on_tab_added_cb (GeditWindow * window, + GeditTab * tab, + GeditFileBrowserPluginData * data); +static gboolean on_confirm_delete_cb (GeditFileBrowserWidget * widget, + GeditFileBrowserStore * store, + GList * rows, + GeditWindow * window); +static gboolean on_confirm_no_trash_cb (GeditFileBrowserWidget * widget, + GList * files, + GeditWindow * window); + +GEDIT_PLUGIN_REGISTER_TYPE_WITH_CODE (GeditFileBrowserPlugin, filetree_plugin, \ + gedit_file_browser_enum_and_flag_register_type (type_module); \ + gedit_file_browser_store_register_type (type_module); \ + gedit_file_bookmarks_store_register_type (type_module); \ + gedit_file_browser_view_register_type (type_module); \ + gedit_file_browser_widget_register_type (type_module); \ +) + + +static void +filetree_plugin_init (GeditFileBrowserPlugin * plugin) +{ + plugin->priv = GEDIT_FILE_BROWSER_PLUGIN_GET_PRIVATE (plugin); +} + +static void +filetree_plugin_finalize (GObject * object) +{ + //GeditFileBrowserPlugin * plugin = GEDIT_FILE_BROWSER_PLUGIN (object); + + G_OBJECT_CLASS (filetree_plugin_parent_class)->finalize (object); +} + +static GeditFileBrowserPluginData * +get_plugin_data (GeditWindow * window) +{ + return (GeditFileBrowserPluginData *) (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY)); +} + +static void +on_end_loading_cb (GeditFileBrowserStore * store, + GtkTreeIter * iter, + GeditFileBrowserPluginData * data) +{ + /* Disconnect the signal */ + g_signal_handler_disconnect (store, data->end_loading_handle); + data->end_loading_handle = 0; + data->auto_root = FALSE; +} + +static void +prepare_auto_root (GeditFileBrowserPluginData *data) +{ + GeditFileBrowserStore *store; + + data->auto_root = TRUE; + + store = gedit_file_browser_widget_get_browser_store (data->tree_widget); + + if (data->end_loading_handle != 0) { + g_signal_handler_disconnect (store, data->end_loading_handle); + data->end_loading_handle = 0; + } + + data->end_loading_handle = g_signal_connect (store, + "end-loading", + G_CALLBACK (on_end_loading_cb), + data); +} + +static void +restore_default_location (GeditFileBrowserPluginData *data) +{ + gchar * root; + gchar * virtual_root; + gboolean bookmarks; + gboolean remote; + MateConfClient * client; + + client = mateconf_client_get_default (); + if (!client) + return; + + bookmarks = !mateconf_client_get_bool (client, + FILE_BROWSER_BASE_KEY "/on_load/tree_view", + NULL); + + if (bookmarks) { + g_object_unref (client); + gedit_file_browser_widget_show_bookmarks (data->tree_widget); + return; + } + + root = mateconf_client_get_string (client, + FILE_BROWSER_BASE_KEY "/on_load/root", + NULL); + virtual_root = mateconf_client_get_string (client, + FILE_BROWSER_BASE_KEY "/on_load/virtual_root", + NULL); + + remote = mateconf_client_get_bool (client, + FILE_BROWSER_BASE_KEY "/on_load/enable_remote", + NULL); + + if (root != NULL && *root != '\0') { + GFile *file; + + file = g_file_new_for_uri (root); + + if (remote || g_file_is_native (file)) { + if (virtual_root != NULL && *virtual_root != '\0') { + prepare_auto_root (data); + gedit_file_browser_widget_set_root_and_virtual_root (data->tree_widget, + root, + virtual_root); + } else { + prepare_auto_root (data); + gedit_file_browser_widget_set_root (data->tree_widget, + root, + TRUE); + } + } + + g_object_unref (file); + } + + g_object_unref (client); + g_free (root); + g_free (virtual_root); +} + +static void +restore_filter (GeditFileBrowserPluginData * data) +{ + MateConfClient * client; + gchar *filter_mode; + GeditFileBrowserStoreFilterMode mode; + gchar *pattern; + + client = mateconf_client_get_default (); + if (!client) + return; + + /* Get filter_mode */ + filter_mode = mateconf_client_get_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + NULL); + + /* Filter mode */ + mode = gedit_file_browser_store_filter_mode_get_default (); + + if (filter_mode != NULL) { + if (strcmp (filter_mode, "hidden") == 0) { + mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; + } else if (strcmp (filter_mode, "binary") == 0) { + mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY; + } else if (strcmp (filter_mode, "hidden_and_binary") == 0 || + strcmp (filter_mode, "binary_and_hidden") == 0) { + mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN | + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY; + } else if (strcmp (filter_mode, "none") == 0 || + *filter_mode == '\0') { + mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_NONE; + } + } + + /* Set the filter mode */ + gedit_file_browser_store_set_filter_mode ( + gedit_file_browser_widget_get_browser_store (data->tree_widget), + mode); + + pattern = mateconf_client_get_string (client, + FILE_BROWSER_BASE_KEY "/filter_pattern", + NULL); + + gedit_file_browser_widget_set_filter_pattern (data->tree_widget, + pattern); + + g_object_unref (client); + g_free (filter_mode); + g_free (pattern); +} + +static GeditFileBrowserViewClickPolicy +click_policy_from_string (gchar const *click_policy) +{ + if (click_policy && strcmp (click_policy, "single") == 0) + return GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE; + else + return GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE; +} + +static void +on_click_policy_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + MateConfValue *value; + GeditFileBrowserPluginData * data; + gchar const *click_policy; + GeditFileBrowserViewClickPolicy policy = GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE; + GeditFileBrowserView *view; + + data = (GeditFileBrowserPluginData *)(user_data); + value = mateconf_entry_get_value (entry); + + if (value && value->type == MATECONF_VALUE_STRING) { + click_policy = mateconf_value_get_string (value); + + policy = click_policy_from_string (click_policy); + } + + view = gedit_file_browser_widget_get_browser_view (data->tree_widget); + gedit_file_browser_view_set_click_policy (view, policy); +} + +static void +on_enable_delete_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + MateConfValue *value; + GeditFileBrowserPluginData *data; + gboolean enable = FALSE; + + data = (GeditFileBrowserPluginData *)(user_data); + value = mateconf_entry_get_value (entry); + + if (value && value->type == MATECONF_VALUE_BOOL) + enable = mateconf_value_get_bool (value); + + g_object_set (G_OBJECT (data->tree_widget), "enable-delete", enable, NULL); +} + +static void +on_confirm_trash_changed (MateConfClient *client, + guint cnxn_id, + MateConfEntry *entry, + gpointer user_data) +{ + MateConfValue *value; + GeditFileBrowserPluginData *data; + gboolean enable = FALSE; + + data = (GeditFileBrowserPluginData *)(user_data); + value = mateconf_entry_get_value (entry); + + if (value && value->type == MATECONF_VALUE_BOOL) + enable = mateconf_value_get_bool (value); + + data->confirm_trash = enable; +} + +static void +install_caja_prefs (GeditFileBrowserPluginData *data) +{ + MateConfClient *client; + gchar *pref; + gboolean prefb; + GeditFileBrowserViewClickPolicy policy; + GeditFileBrowserView *view; + + client = mateconf_client_get_default (); + if (!client) + return; + + mateconf_client_add_dir (client, + CAJA_CLICK_POLICY_BASE_KEY, + MATECONF_CLIENT_PRELOAD_NONE, + NULL); + + /* Get click_policy */ + pref = mateconf_client_get_string (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CLICK_POLICY_KEY, + NULL); + + policy = click_policy_from_string (pref); + + view = gedit_file_browser_widget_get_browser_view (data->tree_widget); + gedit_file_browser_view_set_click_policy (view, policy); + + if (pref) { + data->click_policy_handle = + mateconf_client_notify_add (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CLICK_POLICY_KEY, + on_click_policy_changed, + data, + NULL, + NULL); + g_free (pref); + } + + /* Get enable_delete */ + prefb = mateconf_client_get_bool (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_ENABLE_DELETE_KEY, + NULL); + + g_object_set (G_OBJECT (data->tree_widget), "enable-delete", prefb, NULL); + + data->enable_delete_handle = + mateconf_client_notify_add (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_ENABLE_DELETE_KEY, + on_enable_delete_changed, + data, + NULL, + NULL); + + /* Get confirm_trash */ + prefb = mateconf_client_get_bool (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CONFIRM_TRASH_KEY, + NULL); + + data->confirm_trash = prefb; + + data->confirm_trash_handle = + mateconf_client_notify_add (client, + CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CONFIRM_TRASH_KEY, + on_confirm_trash_changed, + data, + NULL, + NULL); + g_object_unref (client); +} + +static void +set_root_from_doc (GeditFileBrowserPluginData * data, + GeditDocument * doc) +{ + GFile *file; + GFile *parent; + + if (doc == NULL) + return; + + file = gedit_document_get_location (doc); + if (file == NULL) + return; + + parent = g_file_get_parent (file); + + if (parent != NULL) { + gchar * root; + + root = g_file_get_uri (parent); + + gedit_file_browser_widget_set_root (data->tree_widget, + root, + TRUE); + + g_object_unref (parent); + g_free (root); + } + + g_object_unref (file); +} + +static void +on_action_set_active_root (GtkAction * action, + GeditWindow * window) +{ + GeditFileBrowserPluginData *data; + + data = get_plugin_data (window); + set_root_from_doc (data, + gedit_window_get_active_document (window)); +} + +static gchar * +get_terminal (void) +{ + MateConfClient * client; + gchar * terminal; + + client = mateconf_client_get_default (); + terminal = mateconf_client_get_string (client, + TERMINAL_EXEC_KEY, + NULL); + g_object_unref (client); + + if (terminal == NULL) { + const gchar *term = g_getenv ("TERM"); + + if (term != NULL) + terminal = g_strdup (term); + else + terminal = g_strdup ("xterm"); + } + + return terminal; +} + +static void +on_action_open_terminal (GtkAction * action, + GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + gchar * terminal; + gchar * wd = NULL; + gchar * local; + gchar * argv[2]; + GFile * file; + + GtkTreeIter iter; + GeditFileBrowserStore * store; + + data = get_plugin_data (window); + + /* Get the current directory */ + if (!gedit_file_browser_widget_get_selected_directory (data->tree_widget, &iter)) + return; + + store = gedit_file_browser_widget_get_browser_store (data->tree_widget); + gtk_tree_model_get (GTK_TREE_MODEL (store), + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &wd, + -1); + + if (wd == NULL) + return; + + terminal = get_terminal (); + + file = g_file_new_for_uri (wd); + local = g_file_get_path (file); + g_object_unref (file); + + argv[0] = terminal; + argv[1] = NULL; + + g_spawn_async (local, + argv, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, + NULL, + NULL, + NULL); + + g_free (terminal); + g_free (wd); + g_free (local); +} + +static void +on_selection_changed_cb (GtkTreeSelection *selection, + GeditWindow *window) +{ + GeditFileBrowserPluginData * data; + GtkTreeView * tree_view; + GtkTreeModel * model; + GtkTreeIter iter; + gboolean sensitive; + gchar * uri; + + data = get_plugin_data (window); + + tree_view = GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (data->tree_widget)); + model = gtk_tree_view_get_model (tree_view); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + sensitive = gedit_file_browser_widget_get_selected_directory (data->tree_widget, &iter); + + if (sensitive) { + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, -1); + + sensitive = gedit_utils_uri_has_file_scheme (uri); + g_free (uri); + } + + gtk_action_set_sensitive ( + gtk_action_group_get_action (data->single_selection_action_group, + "OpenTerminal"), + sensitive); +} + +#define POPUP_UI "" \ +"" \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +" " \ +"" + +static GtkActionEntry extra_actions[] = +{ + {"SetActiveRoot", GTK_STOCK_JUMP_TO, N_("_Set root to active document"), + NULL, + N_("Set the root to the active document location"), + G_CALLBACK (on_action_set_active_root)} +}; + +static GtkActionEntry extra_single_selection_actions[] = { + {"OpenTerminal", "utilities-terminal", N_("_Open terminal here"), + NULL, + N_("Open a terminal at the currently opened directory"), + G_CALLBACK (on_action_open_terminal)} +}; + +static void +add_popup_ui (GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + GtkUIManager * manager; + GtkActionGroup * action_group; + GError * error = NULL; + + data = get_plugin_data (window); + manager = gedit_file_browser_widget_get_ui_manager (data->tree_widget); + + action_group = gtk_action_group_new ("FileBrowserPluginExtra"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + extra_actions, + G_N_ELEMENTS (extra_actions), + window); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + data->action_group = action_group; + + action_group = gtk_action_group_new ("FileBrowserPluginSingleSelectionExtra"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + extra_single_selection_actions, + G_N_ELEMENTS (extra_single_selection_actions), + window); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + data->single_selection_action_group = action_group; + + data->merge_id = gtk_ui_manager_add_ui_from_string (manager, + POPUP_UI, + -1, + &error); + + if (data->merge_id == 0) { + g_warning("Unable to merge UI: %s", error->message); + g_error_free(error); + } +} + +static void +remove_popup_ui (GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + GtkUIManager * manager; + + data = get_plugin_data (window); + manager = gedit_file_browser_widget_get_ui_manager (data->tree_widget); + gtk_ui_manager_remove_ui (manager, data->merge_id); + + gtk_ui_manager_remove_action_group (manager, data->action_group); + g_object_unref (data->action_group); + + gtk_ui_manager_remove_action_group (manager, data->single_selection_action_group); + g_object_unref (data->single_selection_action_group); +} + +static void +impl_updateui (GeditPlugin * plugin, GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + GeditDocument * doc; + + data = get_plugin_data (window); + + doc = gedit_window_get_active_document (window); + + gtk_action_set_sensitive (gtk_action_group_get_action (data->action_group, + "SetActiveRoot"), + doc != NULL && + !gedit_document_is_untitled (doc)); +} + +static void +impl_activate (GeditPlugin * plugin, GeditWindow * window) +{ + GeditPanel * panel; + GeditFileBrowserPluginData * data; + GtkWidget * image; + GdkPixbuf * pixbuf; + GeditFileBrowserStore * store; + gchar *data_dir; + + data = g_new0 (GeditFileBrowserPluginData, 1); + + data_dir = gedit_plugin_get_data_dir (plugin); + data->tree_widget = GEDIT_FILE_BROWSER_WIDGET (gedit_file_browser_widget_new (data_dir)); + g_free (data_dir); + + g_signal_connect (data->tree_widget, + "uri-activated", + G_CALLBACK (on_uri_activated_cb), window); + + g_signal_connect (data->tree_widget, + "error", G_CALLBACK (on_error_cb), window); + + g_signal_connect (data->tree_widget, + "notify::filter-pattern", + G_CALLBACK (on_filter_pattern_changed_cb), + window); + + g_signal_connect (data->tree_widget, + "confirm-delete", + G_CALLBACK (on_confirm_delete_cb), + window); + + g_signal_connect (data->tree_widget, + "confirm-no-trash", + G_CALLBACK (on_confirm_no_trash_cb), + window); + + g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW + (gedit_file_browser_widget_get_browser_view + (data->tree_widget))), + "changed", + G_CALLBACK (on_selection_changed_cb), + window); + + panel = gedit_window_get_side_panel (window); + pixbuf = gedit_file_browser_utils_pixbuf_from_theme("system-file-manager", + GTK_ICON_SIZE_MENU); + + if (pixbuf) { + image = gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + } else { + image = gtk_image_new_from_stock(GTK_STOCK_INDEX, GTK_ICON_SIZE_MENU); + } + + gtk_widget_show(image); + gedit_panel_add_item (panel, + GTK_WIDGET (data->tree_widget), + _("File Browser"), + image); + gtk_widget_show (GTK_WIDGET (data->tree_widget)); + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, data); + + add_popup_ui (window); + + /* Restore filter options */ + restore_filter (data); + + /* Install caja preferences */ + install_caja_prefs (data); + + /* Connect signals to store the last visited location */ + g_signal_connect (gedit_file_browser_widget_get_browser_view (data->tree_widget), + "notify::model", + G_CALLBACK (on_model_set_cb), + window); + + store = gedit_file_browser_widget_get_browser_store (data->tree_widget); + g_signal_connect (store, + "notify::virtual-root", + G_CALLBACK (on_virtual_root_changed_cb), + window); + + g_signal_connect (store, + "notify::filter-mode", + G_CALLBACK (on_filter_mode_changed_cb), + window); + + g_signal_connect (store, + "rename", + G_CALLBACK (on_rename_cb), + window); + + g_signal_connect (window, + "tab-added", + G_CALLBACK (on_tab_added_cb), + data); + + /* Register messages on the bus */ + gedit_file_browser_messages_register (window, data->tree_widget); + + impl_updateui (plugin, window); +} + +static void +impl_deactivate (GeditPlugin * plugin, GeditWindow * window) +{ + GeditFileBrowserPluginData * data; + GeditPanel * panel; + MateConfClient *client; + + data = get_plugin_data (window); + + /* Unregister messages from the bus */ + gedit_file_browser_messages_unregister (window); + + /* Disconnect signals */ + g_signal_handlers_disconnect_by_func (window, + G_CALLBACK (on_tab_added_cb), + data); + + client = mateconf_client_get_default (); + mateconf_client_remove_dir (client, CAJA_CLICK_POLICY_BASE_KEY, NULL); + + if (data->click_policy_handle) + mateconf_client_notify_remove (client, data->click_policy_handle); + + if (data->enable_delete_handle) + mateconf_client_notify_remove (client, data->enable_delete_handle); + + if (data->confirm_trash_handle) + mateconf_client_notify_remove (client, data->confirm_trash_handle); + + g_object_unref (client); + remove_popup_ui (window); + + panel = gedit_window_get_side_panel (window); + gedit_panel_remove_item (panel, GTK_WIDGET (data->tree_widget)); + + g_free (data); + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); +} + +static void +filetree_plugin_class_init (GeditFileBrowserPluginClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass * plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = filetree_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_updateui; + + g_type_class_add_private (object_class, + sizeof (GeditFileBrowserPluginPrivate)); +} + +/* Callbacks */ +static void +on_uri_activated_cb (GeditFileBrowserWidget * tree_widget, + gchar const *uri, GeditWindow * window) +{ + gedit_commands_load_uri (window, uri, NULL, 0); +} + +static void +on_error_cb (GeditFileBrowserWidget * tree_widget, + guint code, gchar const *message, GeditWindow * window) +{ + gchar * title; + GtkWidget * dlg; + GeditFileBrowserPluginData * data; + + data = get_plugin_data (window); + + /* Do not show the error when the root has been set automatically */ + if (data->auto_root && (code == GEDIT_FILE_BROWSER_ERROR_SET_ROOT || + code == GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY)) + { + /* Show bookmarks */ + gedit_file_browser_widget_show_bookmarks (data->tree_widget); + return; + } + + switch (code) { + case GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY: + title = + _("An error occurred while creating a new directory"); + break; + case GEDIT_FILE_BROWSER_ERROR_NEW_FILE: + title = _("An error occurred while creating a new file"); + break; + case GEDIT_FILE_BROWSER_ERROR_RENAME: + title = + _ + ("An error occurred while renaming a file or directory"); + break; + case GEDIT_FILE_BROWSER_ERROR_DELETE: + title = + _ + ("An error occurred while deleting a file or directory"); + break; + case GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY: + title = + _ + ("An error occurred while opening a directory in the file manager"); + break; + case GEDIT_FILE_BROWSER_ERROR_SET_ROOT: + title = + _("An error occurred while setting a root directory"); + break; + case GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY: + title = + _("An error occurred while loading a directory"); + break; + default: + title = _("An error occurred"); + break; + } + + dlg = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "%s", title); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg), + "%s", message); + + gtk_dialog_run (GTK_DIALOG (dlg)); + gtk_widget_destroy (dlg); +} + +static void +on_model_set_cb (GeditFileBrowserView * widget, + GParamSpec *arg1, + GeditWindow * window) +{ + GeditFileBrowserPluginData * data = get_plugin_data (window); + GtkTreeModel * model; + MateConfClient * client; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (data->tree_widget))); + + if (model == NULL) + return; + + client = mateconf_client_get_default (); + mateconf_client_set_bool (client, + FILE_BROWSER_BASE_KEY "/on_load/tree_view", + GEDIT_IS_FILE_BROWSER_STORE (model), + NULL); + g_object_unref (client); +} + +static void +on_filter_mode_changed_cb (GeditFileBrowserStore * model, + GParamSpec * param, + GeditWindow * window) +{ + MateConfClient * client; + GeditFileBrowserStoreFilterMode mode; + + client = mateconf_client_get_default (); + + if (!client) + return; + + mode = gedit_file_browser_store_get_filter_mode (model); + + if ((mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) && + (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY)) { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + "hidden_and_binary", + NULL); + } else if (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + "hidden", + NULL); + } else if (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY) { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + "binary", + NULL); + } else { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_mode", + "none", + NULL); + } + + g_object_unref (client); + +} + +static void +on_rename_cb (GeditFileBrowserStore * store, + const gchar * olduri, + const gchar * newuri, + GeditWindow * window) +{ + GeditApp * app; + GList * documents; + GList * item; + GeditDocument * doc; + GFile * docfile; + GFile * oldfile; + GFile * newfile; + gchar * uri; + + /* Find all documents and set its uri to newuri where it matches olduri */ + app = gedit_app_get_default (); + documents = gedit_app_get_documents (app); + + oldfile = g_file_new_for_uri (olduri); + newfile = g_file_new_for_uri (newuri); + + for (item = documents; item; item = item->next) { + doc = GEDIT_DOCUMENT (item->data); + uri = gedit_document_get_uri (doc); + + if (!uri) + continue; + + docfile = g_file_new_for_uri (uri); + + if (g_file_equal (docfile, oldfile)) { + gedit_document_set_uri (doc, newuri); + } else { + gchar *relative; + + relative = g_file_get_relative_path (oldfile, docfile); + + if (relative) { + /* relative now contains the part in docfile without + the prefix oldfile */ + + g_object_unref (docfile); + g_free (uri); + + docfile = g_file_get_child (newfile, relative); + uri = g_file_get_uri (docfile); + + gedit_document_set_uri (doc, uri); + } + + g_free (relative); + } + + g_free (uri); + g_object_unref (docfile); + } + + g_object_unref (oldfile); + g_object_unref (newfile); + + g_list_free (documents); +} + +static void +on_filter_pattern_changed_cb (GeditFileBrowserWidget * widget, + GParamSpec * param, + GeditWindow * window) +{ + MateConfClient * client; + gchar * pattern; + + client = mateconf_client_get_default (); + + if (!client) + return; + + g_object_get (G_OBJECT (widget), "filter-pattern", &pattern, NULL); + + if (pattern == NULL) + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_pattern", + "", + NULL); + else + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/filter_pattern", + pattern, + NULL); + + g_free (pattern); +} + +static void +on_virtual_root_changed_cb (GeditFileBrowserStore * store, + GParamSpec * param, + GeditWindow * window) +{ + GeditFileBrowserPluginData * data = get_plugin_data (window); + gchar * root; + gchar * virtual_root; + MateConfClient * client; + + root = gedit_file_browser_store_get_root (store); + + if (!root) + return; + + client = mateconf_client_get_default (); + + if (!client) + return; + + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/on_load/root", + root, + NULL); + + virtual_root = gedit_file_browser_store_get_virtual_root (store); + + if (!virtual_root) { + /* Set virtual to same as root then */ + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/on_load/virtual_root", + root, + NULL); + } else { + mateconf_client_set_string (client, + FILE_BROWSER_BASE_KEY "/on_load/virtual_root", + virtual_root, + NULL); + } + + g_signal_handlers_disconnect_by_func (window, + G_CALLBACK (on_tab_added_cb), + data); + + g_object_unref (client); + g_free (root); + g_free (virtual_root); +} + +static void +on_tab_added_cb (GeditWindow * window, + GeditTab * tab, + GeditFileBrowserPluginData * data) +{ + MateConfClient *client; + gboolean open; + gboolean load_default = TRUE; + + client = mateconf_client_get_default (); + + if (!client) + return; + + open = mateconf_client_get_bool (client, + FILE_BROWSER_BASE_KEY "/open_at_first_doc", + NULL); + + if (open) { + GeditDocument *doc; + gchar *uri; + + doc = gedit_tab_get_document (tab); + + uri = gedit_document_get_uri (doc); + + if (uri != NULL && gedit_utils_uri_has_file_scheme (uri)) { + prepare_auto_root (data); + set_root_from_doc (data, doc); + load_default = FALSE; + } + + g_free (uri); + } + + if (load_default) + restore_default_location (data); + + g_object_unref (client); + + /* Disconnect this signal, it's only called once */ + g_signal_handlers_disconnect_by_func (window, + G_CALLBACK (on_tab_added_cb), + data); +} + +static gchar * +get_filename_from_path (GtkTreeModel *model, GtkTreePath *path) +{ + GtkTreeIter iter; + gchar *uri; + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + -1); + + return gedit_file_browser_utils_uri_basename (uri); +} + +static gboolean +on_confirm_no_trash_cb (GeditFileBrowserWidget * widget, + GList * files, + GeditWindow * window) +{ + gchar *normal; + gchar *message; + gchar *secondary; + gboolean result; + + message = _("Cannot move file to trash, do you\nwant to delete permanently?"); + + if (files->next == NULL) { + normal = gedit_file_browser_utils_file_basename (G_FILE (files->data)); + secondary = g_strdup_printf (_("The file \"%s\" cannot be moved to the trash."), normal); + g_free (normal); + } else { + secondary = g_strdup (_("The selected files cannot be moved to the trash.")); + } + + result = gedit_file_browser_utils_confirmation_dialog (window, + GTK_MESSAGE_QUESTION, + message, + secondary, + GTK_STOCK_DELETE, + NULL); + g_free (secondary); + + return result; +} + +static gboolean +on_confirm_delete_cb (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GList *paths, + GeditWindow *window) +{ + gchar *normal; + gchar *message; + gchar *secondary; + gboolean result; + GeditFileBrowserPluginData *data; + + data = get_plugin_data (window); + + if (!data->confirm_trash) + return TRUE; + + if (paths->next == NULL) { + normal = get_filename_from_path (GTK_TREE_MODEL (store), (GtkTreePath *)(paths->data)); + message = g_strdup_printf (_("Are you sure you want to permanently delete \"%s\"?"), normal); + g_free (normal); + } else { + message = g_strdup (_("Are you sure you want to permanently delete the selected files?")); + } + + secondary = _("If you delete an item, it is permanently lost."); + + result = gedit_file_browser_utils_confirmation_dialog (window, + GTK_MESSAGE_QUESTION, + message, + secondary, + GTK_STOCK_DELETE, + NULL); + + g_free (message); + + return result; +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-plugin.h b/plugins/filebrowser/gedit-file-browser-plugin.h new file mode 100755 index 00000000..19ca86bf --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-plugin.h @@ -0,0 +1,71 @@ +/* + * gedit-file-browser-plugin.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GEDIT_FILE_BROWSER_PLUGIN_H__ +#define __GEDIT_FILE_BROWSER_PLUGIN_H__ + +#include +#include +#include + +G_BEGIN_DECLS +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_FILE_BROWSER_PLUGIN (filetree_plugin_get_type ()) +#define GEDIT_FILE_BROWSER_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPlugin)) +#define GEDIT_FILE_BROWSER_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginClass)) +#define GEDIT_IS_FILE_BROWSER_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN)) +#define GEDIT_IS_FILE_BROWSER_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_FILE_BROWSER_PLUGIN)) +#define GEDIT_FILE_BROWSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginClass)) + +/* Private structure type */ +typedef struct _GeditFileBrowserPluginPrivate GeditFileBrowserPluginPrivate; +typedef struct _GeditFileBrowserPlugin GeditFileBrowserPlugin; +typedef struct _GeditFileBrowserPluginClass GeditFileBrowserPluginClass; + +struct _GeditFileBrowserPlugin +{ + GeditPlugin parent_instance; + + /*< private > */ + GeditFileBrowserPluginPrivate *priv; +}; + + + +struct _GeditFileBrowserPluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType filetree_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule * module); + +G_END_DECLS +#endif /* __GEDIT_FILE_BROWSER_PLUGIN_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-store.c b/plugins/filebrowser/gedit-file-browser-store.c new file mode 100755 index 00000000..6c4f5b51 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.c @@ -0,0 +1,3625 @@ +/* + * gedit-file-browser-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "gedit-file-browser-store.h" +#include "gedit-file-browser-marshal.h" +#include "gedit-file-browser-enum-types.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-utils.h" + +#define GEDIT_FILE_BROWSER_STORE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GEDIT_TYPE_FILE_BROWSER_STORE, \ + GeditFileBrowserStorePrivate)) + +#define NODE_IS_DIR(node) (FILE_IS_DIR((node)->flags)) +#define NODE_IS_HIDDEN(node) (FILE_IS_HIDDEN((node)->flags)) +#define NODE_IS_TEXT(node) (FILE_IS_TEXT((node)->flags)) +#define NODE_LOADED(node) (FILE_LOADED((node)->flags)) +#define NODE_IS_FILTERED(node) (FILE_IS_FILTERED((node)->flags)) +#define NODE_IS_DUMMY(node) (FILE_IS_DUMMY((node)->flags)) + +#define FILE_BROWSER_NODE_DIR(node) ((FileBrowserNodeDir *)(node)) + +#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100 +#define STANDARD_ATTRIBUTE_TYPES G_FILE_ATTRIBUTE_STANDARD_TYPE "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \ + G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \ + G_FILE_ATTRIBUTE_STANDARD_NAME "," \ + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \ + G_FILE_ATTRIBUTE_STANDARD_ICON + +typedef struct _FileBrowserNode FileBrowserNode; +typedef struct _FileBrowserNodeDir FileBrowserNodeDir; +typedef struct _AsyncData AsyncData; +typedef struct _AsyncNode AsyncNode; + +typedef gint (*SortFunc) (FileBrowserNode * node1, + FileBrowserNode * node2); + +struct _AsyncData +{ + GeditFileBrowserStore * model; + GCancellable * cancellable; + gboolean trash; + GList * files; + GList * iter; + gboolean removed; +}; + +struct _AsyncNode +{ + FileBrowserNodeDir *dir; + GCancellable *cancellable; + GSList *original_children; +}; + +typedef struct { + GeditFileBrowserStore * model; + gchar * virtual_root; + GMountOperation * operation; + GCancellable * cancellable; +} MountInfo; + +struct _FileBrowserNode +{ + GFile *file; + guint flags; + gchar *name; + + GdkPixbuf *icon; + GdkPixbuf *emblem; + + FileBrowserNode *parent; + gint pos; + gboolean inserted; +}; + +struct _FileBrowserNodeDir +{ + FileBrowserNode node; + GSList *children; + GHashTable *hidden_file_hash; + + GCancellable *cancellable; + GFileMonitor *monitor; + GeditFileBrowserStore *model; +}; + +struct _GeditFileBrowserStorePrivate +{ + FileBrowserNode *root; + FileBrowserNode *virtual_root; + GType column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NUM]; + + GeditFileBrowserStoreFilterMode filter_mode; + GeditFileBrowserStoreFilterFunc filter_func; + gpointer filter_user_data; + + SortFunc sort_func; + + GSList *async_handles; + MountInfo *mount_info; +}; + +static FileBrowserNode *model_find_node (GeditFileBrowserStore *model, + FileBrowserNode *node, + GFile *uri); +static void model_remove_node (GeditFileBrowserStore * model, + FileBrowserNode * node, + GtkTreePath * path, + gboolean free_nodes); + +static void set_virtual_root_from_node (GeditFileBrowserStore * model, + FileBrowserNode * node); + +static void gedit_file_browser_store_iface_init (GtkTreeModelIface * iface); +static GtkTreeModelFlags gedit_file_browser_store_get_flags (GtkTreeModel * tree_model); +static gint gedit_file_browser_store_get_n_columns (GtkTreeModel * tree_model); +static GType gedit_file_browser_store_get_column_type (GtkTreeModel * tree_model, + gint index); +static gboolean gedit_file_browser_store_get_iter (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreePath * path); +static GtkTreePath *gedit_file_browser_store_get_path (GtkTreeModel * tree_model, + GtkTreeIter * iter); +static void gedit_file_browser_store_get_value (GtkTreeModel * tree_model, + GtkTreeIter * iter, + gint column, + GValue * value); +static gboolean gedit_file_browser_store_iter_next (GtkTreeModel * tree_model, + GtkTreeIter * iter); +static gboolean gedit_file_browser_store_iter_children (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent); +static gboolean gedit_file_browser_store_iter_has_child (GtkTreeModel * tree_model, + GtkTreeIter * iter); +static gint gedit_file_browser_store_iter_n_children (GtkTreeModel * tree_model, + GtkTreeIter * iter); +static gboolean gedit_file_browser_store_iter_nth_child (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent, + gint n); +static gboolean gedit_file_browser_store_iter_parent (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * child); +static void gedit_file_browser_store_row_inserted (GtkTreeModel * tree_model, + GtkTreePath * path, + GtkTreeIter * iter); + +static void gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface * iface); +static gboolean gedit_file_browser_store_row_draggable (GtkTreeDragSource * drag_source, + GtkTreePath * path); +static gboolean gedit_file_browser_store_drag_data_delete (GtkTreeDragSource * drag_source, + GtkTreePath * path); +static gboolean gedit_file_browser_store_drag_data_get (GtkTreeDragSource * drag_source, + GtkTreePath * path, + GtkSelectionData * selection_data); + +static void file_browser_node_free (GeditFileBrowserStore * model, + FileBrowserNode * node); +static void model_add_node (GeditFileBrowserStore * model, + FileBrowserNode * child, + FileBrowserNode * parent); +static void model_clear (GeditFileBrowserStore * model, + gboolean free_nodes); +static gint model_sort_default (FileBrowserNode * node1, + FileBrowserNode * node2); +static void model_check_dummy (GeditFileBrowserStore * model, + FileBrowserNode * node); +static void next_files_async (GFileEnumerator * enumerator, + AsyncNode * async); + +GEDIT_PLUGIN_DEFINE_TYPE_WITH_CODE (GeditFileBrowserStore, gedit_file_browser_store, + G_TYPE_OBJECT, + GEDIT_PLUGIN_IMPLEMENT_INTERFACE (gedit_file_browser_store_tree_model, + GTK_TYPE_TREE_MODEL, + gedit_file_browser_store_iface_init) + GEDIT_PLUGIN_IMPLEMENT_INTERFACE (gedit_file_browser_store_drag_source, + GTK_TYPE_TREE_DRAG_SOURCE, + gedit_file_browser_store_drag_source_init)) + +/* Properties */ +enum { + PROP_0, + + PROP_ROOT, + PROP_VIRTUAL_ROOT, + PROP_FILTER_MODE +}; + +/* Signals */ +enum +{ + BEGIN_LOADING, + END_LOADING, + ERROR, + NO_TRASH, + RENAME, + BEGIN_REFRESH, + END_REFRESH, + UNLOAD, + NUM_SIGNALS +}; + +static guint model_signals[NUM_SIGNALS] = { 0 }; + +static void +cancel_mount_operation (GeditFileBrowserStore *obj) +{ + if (obj->priv->mount_info != NULL) + { + obj->priv->mount_info->model = NULL; + g_cancellable_cancel (obj->priv->mount_info->cancellable); + obj->priv->mount_info = NULL; + } +} + +static void +gedit_file_browser_store_finalize (GObject * object) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + GSList *item; + + /* Free all the nodes */ + file_browser_node_free (obj, obj->priv->root); + + /* Cancel any asynchronous operations */ + for (item = obj->priv->async_handles; item; item = item->next) + { + AsyncData *data = (AsyncData *) (item->data); + g_cancellable_cancel (data->cancellable); + + data->removed = TRUE; + } + + cancel_mount_operation (obj); + + g_slist_free (obj->priv->async_handles); + G_OBJECT_CLASS (gedit_file_browser_store_parent_class)->finalize (object); +} + +static void +set_gvalue_from_node (GValue *value, + FileBrowserNode *node) +{ + gchar * uri; + + if (node == NULL || !node->file) { + g_value_set_string (value, NULL); + } else { + uri = g_file_get_uri (node->file); + g_value_take_string (value, uri); + } +} + +static void +gedit_file_browser_store_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + + switch (prop_id) + { + case PROP_ROOT: + set_gvalue_from_node (value, obj->priv->root); + break; + case PROP_VIRTUAL_ROOT: + set_gvalue_from_node (value, obj->priv->virtual_root); + break; + case PROP_FILTER_MODE: + g_value_set_flags (value, obj->priv->filter_mode); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_store_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object); + + switch (prop_id) + { + case PROP_FILTER_MODE: + gedit_file_browser_store_set_filter_mode (obj, + g_value_get_flags (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_store_class_init (GeditFileBrowserStoreClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_file_browser_store_finalize; + + object_class->get_property = gedit_file_browser_store_get_property; + object_class->set_property = gedit_file_browser_store_set_property; + + g_object_class_install_property (object_class, PROP_ROOT, + g_param_spec_string ("root", + "Root", + "The root uri", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_VIRTUAL_ROOT, + g_param_spec_string ("virtual-root", + "Virtual Root", + "The virtual root uri", + NULL, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, PROP_FILTER_MODE, + g_param_spec_flags ("filter-mode", + "Filter Mode", + "The filter mode", + GEDIT_TYPE_FILE_BROWSER_STORE_FILTER_MODE, + gedit_file_browser_store_filter_mode_get_default (), + G_PARAM_READWRITE)); + + model_signals[BEGIN_LOADING] = + g_signal_new ("begin-loading", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + begin_loading), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, + GTK_TYPE_TREE_ITER); + model_signals[END_LOADING] = + g_signal_new ("end-loading", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + end_loading), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1, + GTK_TYPE_TREE_ITER); + model_signals[ERROR] = + g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + error), NULL, NULL, + gedit_file_browser_marshal_VOID__UINT_STRING, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + model_signals[NO_TRASH] = + g_signal_new ("no-trash", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + no_trash), g_signal_accumulator_true_handled, NULL, + gedit_file_browser_marshal_BOOL__POINTER, + G_TYPE_BOOLEAN, 1, G_TYPE_POINTER); + model_signals[RENAME] = + g_signal_new ("rename", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + rename), NULL, NULL, + gedit_file_browser_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, + G_TYPE_STRING, + G_TYPE_STRING); + model_signals[BEGIN_REFRESH] = + g_signal_new ("begin-refresh", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + begin_refresh), NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + model_signals[END_REFRESH] = + g_signal_new ("end-refresh", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + end_refresh), NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + model_signals[UNLOAD] = + g_signal_new ("unload", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, + unload), NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + g_type_class_add_private (object_class, + sizeof (GeditFileBrowserStorePrivate)); +} + +static void +gedit_file_browser_store_iface_init (GtkTreeModelIface * iface) +{ + iface->get_flags = gedit_file_browser_store_get_flags; + iface->get_n_columns = gedit_file_browser_store_get_n_columns; + iface->get_column_type = gedit_file_browser_store_get_column_type; + iface->get_iter = gedit_file_browser_store_get_iter; + iface->get_path = gedit_file_browser_store_get_path; + iface->get_value = gedit_file_browser_store_get_value; + iface->iter_next = gedit_file_browser_store_iter_next; + iface->iter_children = gedit_file_browser_store_iter_children; + iface->iter_has_child = gedit_file_browser_store_iter_has_child; + iface->iter_n_children = gedit_file_browser_store_iter_n_children; + iface->iter_nth_child = gedit_file_browser_store_iter_nth_child; + iface->iter_parent = gedit_file_browser_store_iter_parent; + iface->row_inserted = gedit_file_browser_store_row_inserted; +} + +static void +gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface * iface) +{ + iface->row_draggable = gedit_file_browser_store_row_draggable; + iface->drag_data_delete = gedit_file_browser_store_drag_data_delete; + iface->drag_data_get = gedit_file_browser_store_drag_data_get; +} + +static void +gedit_file_browser_store_init (GeditFileBrowserStore * obj) +{ + obj->priv = GEDIT_FILE_BROWSER_STORE_GET_PRIVATE (obj); + + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_URI] = + G_TYPE_STRING; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NAME] = + G_TYPE_STRING; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS] = + G_TYPE_UINT; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_ICON] = + GDK_TYPE_PIXBUF; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM] = + GDK_TYPE_PIXBUF; + + // Default filter mode is hiding the hidden files + obj->priv->filter_mode = gedit_file_browser_store_filter_mode_get_default (); + obj->priv->sort_func = model_sort_default; +} + +static gboolean +node_has_parent (FileBrowserNode * node, FileBrowserNode * parent) +{ + if (node->parent == NULL) + return FALSE; + + if (node->parent == parent) + return TRUE; + + return node_has_parent (node->parent, parent); +} + +static gboolean +node_in_tree (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + return node_has_parent (node, model->priv->virtual_root); +} + +static gboolean +model_node_visibility (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + if (node == NULL) + return FALSE; + + if (NODE_IS_DUMMY (node)) + return !NODE_IS_HIDDEN (node); + + if (node == model->priv->virtual_root) + return TRUE; + + if (!node_has_parent (node, model->priv->virtual_root)) + return FALSE; + + return !NODE_IS_FILTERED (node); +} + +static gboolean +model_node_inserted (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + return node == model->priv->virtual_root || (model_node_visibility (model, node) && node->inserted); +} + +/* Interface implementation */ + +static GtkTreeModelFlags +gedit_file_browser_store_get_flags (GtkTreeModel * tree_model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + (GtkTreeModelFlags) 0); + + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static gint +gedit_file_browser_store_get_n_columns (GtkTreeModel * tree_model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), 0); + + return GEDIT_FILE_BROWSER_STORE_COLUMN_NUM; +} + +static GType +gedit_file_browser_store_get_column_type (GtkTreeModel * tree_model, gint idx) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + G_TYPE_INVALID); + g_return_val_if_fail (idx < GEDIT_FILE_BROWSER_STORE_COLUMN_NUM && + idx >= 0, G_TYPE_INVALID); + + return GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[idx]; +} + +static gboolean +gedit_file_browser_store_get_iter (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreePath * path) +{ + gint * indices, depth, i; + FileBrowserNode * node; + GeditFileBrowserStore * model; + gint num; + + g_assert (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_assert (path != NULL); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + node = model->priv->virtual_root; + + for (i = 0; i < depth; ++i) { + GSList * item; + + if (node == NULL) + return FALSE; + + num = 0; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) { + FileBrowserNode * child; + + child = (FileBrowserNode *) (item->data); + + if (model_node_inserted (model, child)) { + if (num == indices[i]) { + node = child; + break; + } + + num++; + } + } + + if (item == NULL) + return FALSE; + + node = (FileBrowserNode *) (item->data); + } + + iter->user_data = node; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + + return node != NULL; +} + +static GtkTreePath * +gedit_file_browser_store_get_path_real (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + GtkTreePath *path; + gint num = 0; + + path = gtk_tree_path_new (); + + while (node != model->priv->virtual_root) { + GSList *item; + + if (node->parent == NULL) { + gtk_tree_path_free (path); + return NULL; + } + + num = 0; + + for (item = FILE_BROWSER_NODE_DIR (node->parent)->children; item; item = item->next) { + FileBrowserNode *check; + + check = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, check) && (check == node || check->inserted)) { + if (check == node) { + gtk_tree_path_prepend_index (path, + num); + break; + } + + ++num; + } else if (check == node) { + if (NODE_IS_DUMMY (node)) + g_warning ("Dummy not visible???"); + + gtk_tree_path_free (path); + return NULL; + } + } + + node = node->parent; + } + + return path; +} + +static GtkTreePath * +gedit_file_browser_store_get_path (GtkTreeModel * tree_model, + GtkTreeIter * iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), NULL); + g_return_val_if_fail (iter != NULL, NULL); + g_return_val_if_fail (iter->user_data != NULL, NULL); + + return gedit_file_browser_store_get_path_real (GEDIT_FILE_BROWSER_STORE (tree_model), + (FileBrowserNode *) (iter->user_data)); +} + +static void +gedit_file_browser_store_get_value (GtkTreeModel * tree_model, + GtkTreeIter * iter, + gint column, + GValue * value) +{ + FileBrowserNode *node; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *) (iter->user_data); + + g_value_init (value, GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[column]); + + switch (column) { + case GEDIT_FILE_BROWSER_STORE_COLUMN_URI: + set_gvalue_from_node (value, node); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_NAME: + g_value_set_string (value, node->name); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS: + g_value_set_uint (value, node->flags); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_ICON: + g_value_set_object (value, node->icon); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM: + g_value_set_object (value, node->emblem); + break; + default: + g_return_if_reached (); + } +} + +static gboolean +gedit_file_browser_store_iter_next (GtkTreeModel * tree_model, + GtkTreeIter * iter) +{ + GeditFileBrowserStore * model; + FileBrowserNode * node; + GSList * item; + GSList * first; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + node = (FileBrowserNode *) (iter->user_data); + + if (node->parent == NULL) + return FALSE; + + first = g_slist_next (g_slist_find (FILE_BROWSER_NODE_DIR (node->parent)->children, node)); + + for (item = first; item; item = item->next) { + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) { + iter->user_data = item->data; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_children (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent) +{ + FileBrowserNode * node; + GeditFileBrowserStore * model; + GSList * item; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (parent == NULL + || parent->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (parent == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *) (parent->user_data); + + if (node == NULL) + return FALSE; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) { + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) { + iter->user_data = item->data; + return TRUE; + } + } + + return FALSE; +} + +static gboolean +filter_tree_model_iter_has_child_real (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + GSList *item; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) { + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) + return TRUE; + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_has_child (GtkTreeModel * tree_model, + GtkTreeIter * iter) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (iter == NULL + || iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (iter == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *) (iter->user_data); + + return filter_tree_model_iter_has_child_real (model, node); +} + +static gint +gedit_file_browser_store_iter_n_children (GtkTreeModel * tree_model, + GtkTreeIter * iter) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + GSList *item; + gint num = 0; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (iter == NULL + || iter->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (iter == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *) (iter->user_data); + + if (!NODE_IS_DIR (node)) + return 0; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) + ++num; + + return num; +} + +static gboolean +gedit_file_browser_store_iter_nth_child (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent, gint n) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + GSList *item; + gint num = 0; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), + FALSE); + g_return_val_if_fail (parent == NULL + || parent->user_data != NULL, FALSE); + + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (parent == NULL) + node = model->priv->virtual_root; + else + node = (FileBrowserNode *) (parent->user_data); + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) { + if (model_node_inserted (model, (FileBrowserNode *) (item->data))) { + if (num == n) { + iter->user_data = item->data; + return TRUE; + } + + ++num; + } + } + + return FALSE; +} + +static gboolean +gedit_file_browser_store_iter_parent (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * child) +{ + FileBrowserNode *node; + GeditFileBrowserStore *model; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE); + g_return_val_if_fail (child != NULL, FALSE); + g_return_val_if_fail (child->user_data != NULL, FALSE); + + node = (FileBrowserNode *) (child->user_data); + model = GEDIT_FILE_BROWSER_STORE (tree_model); + + if (!node_in_tree (model, node)) + return FALSE; + + if (node->parent == NULL) + return FALSE; + + iter->user_data = node->parent; + return TRUE; +} + +static void +gedit_file_browser_store_row_inserted (GtkTreeModel * tree_model, + GtkTreePath * path, + GtkTreeIter * iter) +{ + FileBrowserNode * node = (FileBrowserNode *)(iter->user_data); + + node->inserted = TRUE; +} + +static gboolean +gedit_file_browser_store_row_draggable (GtkTreeDragSource * drag_source, + GtkTreePath * path) +{ + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), + &iter, path)) + { + return FALSE; + } + + gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + return !FILE_IS_DUMMY(flags); +} + +static gboolean +gedit_file_browser_store_drag_data_delete (GtkTreeDragSource * drag_source, + GtkTreePath * path) +{ + return FALSE; +} + +static gboolean +gedit_file_browser_store_drag_data_get (GtkTreeDragSource * drag_source, + GtkTreePath * path, + GtkSelectionData * selection_data) +{ + GtkTreeIter iter; + gchar *uri; + gchar *uris[2] = {0, }; + gboolean ret; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), + &iter, path)) + { + return FALSE; + } + + gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + -1); + + g_assert (uri); + + uris[0] = uri; + ret = gtk_selection_data_set_uris (selection_data, uris); + + g_free (uri); + + return ret; +} + +#define FILTER_HIDDEN(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) +#define FILTER_BINARY(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY) + +/* Private */ +static void +model_begin_loading (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + GtkTreeIter iter; + + iter.user_data = node; + g_signal_emit (model, model_signals[BEGIN_LOADING], 0, &iter); +} + +static void +model_end_loading (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + GtkTreeIter iter; + + iter.user_data = node; + g_signal_emit (model, model_signals[END_LOADING], 0, &iter); +} + +static void +model_node_update_visibility (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + GtkTreeIter iter; + + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + + if (FILTER_HIDDEN (model->priv->filter_mode) && + NODE_IS_HIDDEN (node)) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + else if (FILTER_BINARY (model->priv->filter_mode) && + (!NODE_IS_TEXT (node) && !NODE_IS_DIR (node))) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + else if (model->priv->filter_func) { + iter.user_data = node; + + if (!model->priv-> + filter_func (model, &iter, + model->priv->filter_user_data)) + node->flags |= + GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + } +} + +static gint +collate_nodes (FileBrowserNode * node1, FileBrowserNode * node2) +{ + if (node1->name == NULL) + return -1; + else if (node2->name == NULL) + return 1; + else { + gchar *k1, *k2; + gint result; + + k1 = g_utf8_collate_key_for_filename (node1->name, -1); + k2 = g_utf8_collate_key_for_filename (node2->name, -1); + + result = strcmp (k1, k2); + + g_free (k1); + g_free (k2); + + return result; + } +} + +static gint +model_sort_default (FileBrowserNode * node1, FileBrowserNode * node2) +{ + gint f1; + gint f2; + + f1 = NODE_IS_DUMMY (node1); + f2 = NODE_IS_DUMMY (node2); + + if (f1 && f2) + { + return 0; + } + else if (f1 || f2) + { + return f1 ? -1 : 1; + } + + f1 = NODE_IS_DIR (node1); + f2 = NODE_IS_DIR (node2); + + if (f1 != f2) + { + return f1 ? -1 : 1; + } + + f1 = NODE_IS_HIDDEN (node1); + f2 = NODE_IS_HIDDEN (node2); + + if (f1 != f2) + { + return f2 ? -1 : 1; + } + + return collate_nodes (node1, node2); +} + +static void +model_resort_node (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + FileBrowserNodeDir *dir; + GSList *item; + FileBrowserNode *child; + gint pos = 0; + GtkTreeIter iter; + GtkTreePath *path; + gint *neworder; + + dir = FILE_BROWSER_NODE_DIR (node->parent); + + if (!model_node_visibility (model, node->parent)) { + /* Just sort the children of the parent */ + dir->children = g_slist_sort (dir->children, + (GCompareFunc) (model->priv-> + sort_func)); + } else { + /* Store current positions */ + for (item = dir->children; item; item = item->next) { + child = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, child)) + child->pos = pos++; + } + + dir->children = g_slist_sort (dir->children, + (GCompareFunc) (model->priv-> + sort_func)); + neworder = g_new (gint, pos); + pos = 0; + + /* Store the new positions */ + for (item = dir->children; item; item = item->next) { + child = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, child)) + neworder[pos++] = child->pos; + } + + iter.user_data = node->parent; + path = + gedit_file_browser_store_get_path_real (model, + node->parent); + + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model), + path, &iter, neworder); + + g_free (neworder); + gtk_tree_path_free (path); + } +} + +static void +row_changed (GeditFileBrowserStore * model, + GtkTreePath ** path, + GtkTreeIter * iter) +{ + GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path); + + /* Insert a copy of the actual path here because the row-inserted + signal may alter the path */ + gtk_tree_model_row_changed (GTK_TREE_MODEL(model), *path, iter); + gtk_tree_path_free (*path); + + *path = gtk_tree_row_reference_get_path (ref); + gtk_tree_row_reference_free (ref); +} + +static void +row_inserted (GeditFileBrowserStore * model, + GtkTreePath ** path, + GtkTreeIter * iter) +{ + /* This function creates a row reference for the path because it's + uncertain what might change the actual model/view when we insert + a node, maybe another directory load is triggered for example. + Because functions that use this function rely on the notion that + the path remains pointed towards the inserted node, we use the + reference to keep track. */ + GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path); + GtkTreePath * copy = gtk_tree_path_copy (*path); + + gtk_tree_model_row_inserted (GTK_TREE_MODEL(model), copy, iter); + gtk_tree_path_free (copy); + + if (ref) + { + gtk_tree_path_free (*path); + + /* To restore the path, we get the path from the reference. But, since + we inserted a row, the path will be one index further than the + actual path of our node. We therefore call gtk_tree_path_prev */ + *path = gtk_tree_row_reference_get_path (ref); + gtk_tree_path_prev (*path); + } + + gtk_tree_row_reference_free (ref); +} + +static void +row_deleted (GeditFileBrowserStore * model, + const GtkTreePath * path) +{ + GtkTreePath *copy = gtk_tree_path_copy (path); + + /* Delete a copy of the actual path here because the row-deleted + signal may alter the path */ + gtk_tree_model_row_deleted (GTK_TREE_MODEL(model), copy); + gtk_tree_path_free (copy); +} + +static void +model_refilter_node (GeditFileBrowserStore * model, + FileBrowserNode * node, + GtkTreePath ** path) +{ + gboolean old_visible; + gboolean new_visible; + FileBrowserNodeDir *dir; + GSList *item; + GtkTreeIter iter; + GtkTreePath *tmppath = NULL; + gboolean in_tree; + + if (node == NULL) + return; + + old_visible = model_node_visibility (model, node); + model_node_update_visibility (model, node); + + in_tree = node_in_tree (model, node); + + if (path == NULL) + { + if (in_tree) + tmppath = gedit_file_browser_store_get_path_real (model, + node); + else + tmppath = gtk_tree_path_new_first (); + + path = &tmppath; + } + + if (NODE_IS_DIR (node)) { + if (in_tree) + gtk_tree_path_down (*path); + + dir = FILE_BROWSER_NODE_DIR (node); + + for (item = dir->children; item; item = item->next) { + model_refilter_node (model, + (FileBrowserNode *) (item->data), + path); + } + + if (in_tree) + gtk_tree_path_up (*path); + } + + if (in_tree) { + new_visible = model_node_visibility (model, node); + + if (old_visible != new_visible) { + if (old_visible) { + node->inserted = FALSE; + row_deleted (model, *path); + } else { + iter.user_data = node; + row_inserted (model, path, &iter); + gtk_tree_path_next (*path); + } + } else if (old_visible) { + gtk_tree_path_next (*path); + } + } + + model_check_dummy (model, node); + + if (tmppath) + gtk_tree_path_free (tmppath); +} + +static void +model_refilter (GeditFileBrowserStore * model) +{ + model_refilter_node (model, model->priv->root, NULL); +} + +static void +file_browser_node_set_name (FileBrowserNode * node) +{ + g_free (node->name); + + if (node->file) { + node->name = gedit_file_browser_utils_file_basename (node->file); + } else { + node->name = NULL; + } +} + +static void +file_browser_node_init (FileBrowserNode * node, GFile * file, + FileBrowserNode * parent) +{ + if (file != NULL) { + node->file = g_object_ref (file); + file_browser_node_set_name (node); + } + + node->parent = parent; +} + +static FileBrowserNode * +file_browser_node_new (GFile * file, FileBrowserNode * parent) +{ + FileBrowserNode *node = g_slice_new0 (FileBrowserNode); + + file_browser_node_init (node, file, parent); + return node; +} + +static FileBrowserNode * +file_browser_node_dir_new (GeditFileBrowserStore * model, + GFile * file, FileBrowserNode * parent) +{ + FileBrowserNode *node = + (FileBrowserNode *) g_slice_new0 (FileBrowserNodeDir); + + file_browser_node_init (node, file, parent); + + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY; + + FILE_BROWSER_NODE_DIR (node)->model = model; + + return node; +} + +static void +file_browser_node_free_children (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + GSList *item; + + if (node == NULL) + return; + + if (NODE_IS_DIR (node)) { + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) + file_browser_node_free (model, + (FileBrowserNode *) (item-> + data)); + + g_slist_free (FILE_BROWSER_NODE_DIR (node)->children); + FILE_BROWSER_NODE_DIR (node)->children = NULL; + + /* This node is no longer loaded */ + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; + } +} + +static void +file_browser_node_free (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + gchar *uri; + + if (node == NULL) + return; + + if (NODE_IS_DIR (node)) + { + FileBrowserNodeDir *dir; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (dir->cancellable) { + g_cancellable_cancel (dir->cancellable); + g_object_unref (dir->cancellable); + + model_end_loading (model, node); + } + + file_browser_node_free_children (model, node); + + if (dir->monitor) { + g_file_monitor_cancel (dir->monitor); + g_object_unref (dir->monitor); + } + + if (dir->hidden_file_hash) + g_hash_table_destroy (dir->hidden_file_hash); + } + + if (node->file) + { + uri = g_file_get_uri (node->file); + g_signal_emit (model, model_signals[UNLOAD], 0, uri); + + g_free (uri); + g_object_unref (node->file); + } + + if (node->icon) + g_object_unref (node->icon); + + if (node->emblem) + g_object_unref (node->emblem); + + g_free (node->name); + + if (NODE_IS_DIR (node)) + g_slice_free (FileBrowserNodeDir, (FileBrowserNodeDir *)node); + else + g_slice_free (FileBrowserNode, (FileBrowserNode *)node); +} + +/** + * model_remove_node_children: + * @model: the #GeditFileBrowserStore + * @node: the FileBrowserNode to remove + * @path: the path of the node, or NULL to let the path be calculated + * @free_nodes: whether to also remove the nodes from memory + * + * Removes all the children of node from the model. This function is used + * to remove the child nodes from the _model_. Don't use it to just free + * a node. + **/ +static void +model_remove_node_children (GeditFileBrowserStore * model, + FileBrowserNode * node, + GtkTreePath * path, + gboolean free_nodes) +{ + FileBrowserNodeDir *dir; + GtkTreePath *path_child; + GSList *list; + GSList *item; + + if (node == NULL || !NODE_IS_DIR (node)) + return; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (dir->children == NULL) + return; + + if (!model_node_visibility (model, node)) { + // Node is invisible and therefore the children can just + // be freed + if (free_nodes) + file_browser_node_free_children (model, node); + + return; + } + + if (path == NULL) + path_child = + gedit_file_browser_store_get_path_real (model, node); + else + path_child = gtk_tree_path_copy (path); + + gtk_tree_path_down (path_child); + + list = g_slist_copy (dir->children); + + for (item = list; item; item = item->next) { + model_remove_node (model, (FileBrowserNode *) (item->data), + path_child, free_nodes); + } + + g_slist_free (list); + gtk_tree_path_free (path_child); +} + +/** + * model_remove_node: + * @model: the #GeditFileBrowserStore + * @node: the FileBrowserNode to remove + * @path: the path to use to remove this node, or NULL to use the path + * calculated from the node itself + * @free_nodes: whether to also remove the nodes from memory + * + * Removes this node and all its children from the model. This function is used + * to remove the node from the _model_. Don't use it to just free + * a node. + **/ +static void +model_remove_node (GeditFileBrowserStore * model, + FileBrowserNode * node, + GtkTreePath * path, + gboolean free_nodes) +{ + gboolean free_path = FALSE; + FileBrowserNode *parent; + + if (path == NULL) { + path = + gedit_file_browser_store_get_path_real (model, node); + free_path = TRUE; + } + + model_remove_node_children (model, node, path, free_nodes); + + /* Only delete if the node is visible in the tree (but only when it's + not the virtual root) */ + if (model_node_visibility (model, node) && node != model->priv->virtual_root) + { + node->inserted = FALSE; + row_deleted (model, path); + } + + if (free_path) + gtk_tree_path_free (path); + + parent = node->parent; + + if (free_nodes) { + /* Remove the node from the parents children list */ + if (parent) + FILE_BROWSER_NODE_DIR (node->parent)->children = + g_slist_remove (FILE_BROWSER_NODE_DIR + (node->parent)->children, + node); + } + + /* If this is the virtual root, than set the parent as the virtual root */ + if (node == model->priv->virtual_root) + set_virtual_root_from_node (model, parent); + else if (parent && model_node_visibility (model, parent) && !(free_nodes && NODE_IS_DUMMY(node))) + model_check_dummy (model, parent); + + /* Now free the node if necessary */ + if (free_nodes) + file_browser_node_free (model, node); +} + +/** + * model_clear: + * @model: the #GeditFileBrowserStore + * @free_nodes: whether to also remove the nodes from memory + * + * Removes all nodes from the model. This function is used + * to remove all the nodes from the _model_. Don't use it to just free the + * nodes in the model. + **/ +static void +model_clear (GeditFileBrowserStore * model, gboolean free_nodes) +{ + GtkTreePath *path; + FileBrowserNodeDir *dir; + FileBrowserNode *dummy; + + path = gtk_tree_path_new (); + model_remove_node_children (model, model->priv->virtual_root, path, + free_nodes); + gtk_tree_path_free (path); + + /* Remove the dummy if there is one */ + if (model->priv->virtual_root) { + dir = FILE_BROWSER_NODE_DIR (model->priv->virtual_root); + + if (dir->children != NULL) { + dummy = (FileBrowserNode *) (dir->children->data); + + if (NODE_IS_DUMMY (dummy) + && model_node_visibility (model, dummy)) { + path = gtk_tree_path_new_first (); + + dummy->inserted = FALSE; + row_deleted (model, path); + gtk_tree_path_free (path); + } + } + } +} + +static void +file_browser_node_unload (GeditFileBrowserStore * model, + FileBrowserNode * node, gboolean remove_children) +{ + FileBrowserNodeDir *dir; + + if (node == NULL) + return; + + if (!NODE_IS_DIR (node) || !NODE_LOADED (node)) + return; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (remove_children) + model_remove_node_children (model, node, NULL, TRUE); + + if (dir->cancellable) { + g_cancellable_cancel (dir->cancellable); + g_object_unref (dir->cancellable); + + model_end_loading (model, node); + dir->cancellable = NULL; + } + + if (dir->monitor) { + g_file_monitor_cancel (dir->monitor); + g_object_unref (dir->monitor); + + dir->monitor = NULL; + } + + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; +} + +static void +model_recomposite_icon_real (GeditFileBrowserStore * tree_model, + FileBrowserNode * node, + GFileInfo * info) +{ + GdkPixbuf *icon; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (node != NULL); + + if (node->file == NULL) + return; + + if (info) { + GIcon *gicon = g_file_info_get_icon (info); + if (gicon != NULL) + icon = gedit_file_browser_utils_pixbuf_from_icon (gicon, GTK_ICON_SIZE_MENU); + else + icon = NULL; + } else { + icon = gedit_file_browser_utils_pixbuf_from_file (node->file, GTK_ICON_SIZE_MENU); + } + + if (node->icon) + g_object_unref (node->icon); + + if (node->emblem) { + gint icon_size; + + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size); + + if (icon == NULL) { + node->icon = + gdk_pixbuf_new (gdk_pixbuf_get_colorspace (node->emblem), + gdk_pixbuf_get_has_alpha (node->emblem), + gdk_pixbuf_get_bits_per_sample (node->emblem), + icon_size, + icon_size); + } else { + node->icon = gdk_pixbuf_copy (icon); + g_object_unref (icon); + } + + gdk_pixbuf_composite (node->emblem, node->icon, + icon_size - 10, icon_size - 10, 10, + 10, icon_size - 10, icon_size - 10, + 1, 1, GDK_INTERP_NEAREST, 255); + } else { + node->icon = icon; + } +} + +static void +model_recomposite_icon (GeditFileBrowserStore * tree_model, + GtkTreeIter * iter) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + model_recomposite_icon_real (tree_model, + (FileBrowserNode *) (iter->user_data), + NULL); +} + +static FileBrowserNode * +model_create_dummy_node (GeditFileBrowserStore * model, + FileBrowserNode * parent) +{ + FileBrowserNode *dummy; + + dummy = file_browser_node_new (NULL, parent); + dummy->name = g_strdup (_("(Empty)")); + + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY; + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + return dummy; +} + +static FileBrowserNode * +model_add_dummy_node (GeditFileBrowserStore * model, + FileBrowserNode * parent) +{ + FileBrowserNode *dummy; + + dummy = model_create_dummy_node (model, parent); + + if (model_node_visibility (model, parent)) + dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + model_add_node (model, dummy, parent); + + return dummy; +} + +static void +model_check_dummy (GeditFileBrowserStore * model, FileBrowserNode * node) +{ + // Hide the dummy child if needed + if (NODE_IS_DIR (node)) { + FileBrowserNode *dummy; + GtkTreeIter iter; + GtkTreePath *path; + guint flags; + FileBrowserNodeDir *dir; + + dir = FILE_BROWSER_NODE_DIR (node); + + if (dir->children == NULL) { + model_add_dummy_node (model, node); + return; + } + + dummy = (FileBrowserNode *) (dir->children->data); + + if (!NODE_IS_DUMMY (dummy)) { + dummy = model_create_dummy_node (model, node); + dir->children = g_slist_prepend (dir->children, dummy); + } + + if (!model_node_visibility (model, node)) { + dummy->flags |= + GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + return; + } + + /* Temporarily set the node to invisible to check + * for real children */ + flags = dummy->flags; + dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (!filter_tree_model_iter_has_child_real (model, node)) { + dummy->flags &= + ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (FILE_IS_HIDDEN (flags)) { + // Was hidden, needs to be inserted + iter.user_data = dummy; + path = + gedit_file_browser_store_get_path_real + (model, dummy); + + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + } else { + if (!FILE_IS_HIDDEN (flags)) { + // Was shown, needs to be removed + + // To get the path we need to set it to visible temporarily + dummy->flags &= + ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + path = + gedit_file_browser_store_get_path_real + (model, dummy); + dummy->flags |= + GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + dummy->inserted = FALSE; + row_deleted (model, path); + gtk_tree_path_free (path); + } + } + } +} + +static void +insert_node_sorted (GeditFileBrowserStore * model, + FileBrowserNode * child, + FileBrowserNode * parent) +{ + FileBrowserNodeDir *dir; + + dir = FILE_BROWSER_NODE_DIR (parent); + + if (model->priv->sort_func == NULL) { + dir->children = g_slist_append (dir->children, child); + } else { + dir->children = + g_slist_insert_sorted (dir->children, child, + (GCompareFunc) (model->priv-> + sort_func)); + } +} + +static void +model_add_node (GeditFileBrowserStore * model, FileBrowserNode * child, + FileBrowserNode * parent) +{ + /* Add child to parents children */ + insert_node_sorted (model, child, parent); + + if (model_node_visibility (model, parent) && + model_node_visibility (model, child)) { + GtkTreeIter iter; + GtkTreePath *path; + + iter.user_data = child; + path = gedit_file_browser_store_get_path_real (model, child); + + /* Emit row inserted */ + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, parent); + model_check_dummy (model, child); +} + +static void +model_add_nodes_batch (GeditFileBrowserStore * model, + GSList * children, + FileBrowserNode * parent) +{ + GSList *sorted_children; + GSList *child; + GSList *prev; + GSList *l; + FileBrowserNodeDir *dir; + + dir = FILE_BROWSER_NODE_DIR (parent); + + sorted_children = g_slist_sort (children, (GCompareFunc) model->priv->sort_func); + + child = sorted_children; + l = dir->children; + prev = NULL; + + model_check_dummy (model, parent); + + while (child) { + FileBrowserNode *node = child->data; + GtkTreeIter iter; + GtkTreePath *path; + + /* reached the end of the first list, just append the second */ + if (l == NULL) { + + dir->children = g_slist_concat (dir->children, child); + + for (l = child; l; l = l->next) { + if (model_node_visibility (model, parent) && + model_node_visibility (model, l->data)) { + iter.user_data = l->data; + path = gedit_file_browser_store_get_path_real (model, l->data); + + // Emit row inserted + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, l->data); + } + + break; + } + + if (model->priv->sort_func (l->data, node) > 0) { + GSList *next_child; + + if (prev == NULL) { + /* prepend to the list */ + dir->children = g_slist_prepend (dir->children, child); + } else { + prev->next = child; + } + + next_child = child->next; + prev = child; + child->next = l; + child = next_child; + + if (model_node_visibility (model, parent) && + model_node_visibility (model, node)) { + iter.user_data = node; + path = gedit_file_browser_store_get_path_real (model, node); + + // Emit row inserted + row_inserted (model, &path, &iter); + gtk_tree_path_free (path); + } + + model_check_dummy (model, node); + + /* try again at the same l position with the + * next child */ + } else { + + /* Move to the next item in the list */ + prev = l; + l = l->next; + } + } +} + +static gchar const * +backup_content_type (GFileInfo * info) +{ + gchar const * content; + + if (!g_file_info_get_is_backup (info)) + return NULL; + + content = g_file_info_get_content_type (info); + + if (!content || g_content_type_equals (content, "application/x-trash")) + return "text/plain"; + + return content; +} + +static void +file_browser_node_set_from_info (GeditFileBrowserStore * model, + FileBrowserNode * node, + GFileInfo * info, + gboolean isadded) +{ + FileBrowserNodeDir * dir; + gchar const * content; + gchar const * name; + gboolean free_info = FALSE; + GtkTreePath * path; + gchar * uri; + GError * error = NULL; + + if (info == NULL) { + info = g_file_query_info (node->file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + + if (!info) { + if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)) { + uri = g_file_get_uri (node->file); + g_warning ("Could not get info for %s: %s", uri, error->message); + g_free (uri); + } + g_error_free (error); + + return; + } + + free_info = TRUE; + } + + dir = FILE_BROWSER_NODE_DIR (node->parent); + name = g_file_info_get_name (info); + + if (g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info)) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + else if (dir != NULL && dir->hidden_file_hash != NULL && + g_hash_table_lookup (dir->hidden_file_hash, name) != NULL) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY; + else { + if (!(content = backup_content_type (info))) + content = g_file_info_get_content_type (info); + + if (!content || + g_content_type_is_unknown (content) || + g_content_type_is_a (content, "text/plain")) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT; + } + + model_recomposite_icon_real (model, node, info); + + if (free_info) + g_object_unref (info); + + if (isadded) { + path = gedit_file_browser_store_get_path_real (model, node); + model_refilter_node (model, node, &path); + gtk_tree_path_free (path); + + model_check_dummy (model, node->parent); + } else { + model_node_update_visibility (model, node); + } +} + +static FileBrowserNode * +node_list_contains_file (GSList *children, GFile * file) +{ + GSList *item; + + for (item = children; item; item = item->next) { + FileBrowserNode *node; + + node = (FileBrowserNode *) (item->data); + + if (node->file != NULL + && g_file_equal (node->file, file)) + return node; + } + + return NULL; +} + +static FileBrowserNode * +model_add_node_from_file (GeditFileBrowserStore * model, + FileBrowserNode * parent, + GFile * file, + GFileInfo * info) +{ + FileBrowserNode *node; + gboolean free_info = FALSE; + GError * error = NULL; + + if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL) { + if (info == NULL) { + info = g_file_query_info (file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + free_info = TRUE; + } + + if (!info) { + g_warning ("Error querying file info: %s", error->message); + g_error_free (error); + + /* FIXME: What to do now then... */ + node = file_browser_node_new (file, parent); + } else if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + node = file_browser_node_dir_new (model, file, parent); + } else { + node = file_browser_node_new (file, parent); + } + + file_browser_node_set_from_info (model, node, info, FALSE); + model_add_node (model, node, parent); + + if (info && free_info) + g_object_unref (info); + } + + return node; +} + +/* We pass in a copy of the list of parent->children so that we do + * not have to check if a file already exists among the ones we just + * added */ +static void +model_add_nodes_from_files (GeditFileBrowserStore * model, + FileBrowserNode * parent, + GSList * original_children, + GList * files) +{ + GList *item; + GSList *nodes = NULL; + + for (item = files; item; item = item->next) { + GFileInfo *info = G_FILE_INFO (item->data); + GFileType type; + gchar const * name; + GFile * file; + FileBrowserNode *node; + + type = g_file_info_get_file_type (info); + + /* Skip all non regular, non directory files */ + if (type != G_FILE_TYPE_REGULAR && + type != G_FILE_TYPE_DIRECTORY && + type != G_FILE_TYPE_SYMBOLIC_LINK) { + g_object_unref (info); + continue; + } + + name = g_file_info_get_name (info); + + /* Skip '.' and '..' directories */ + if (type == G_FILE_TYPE_DIRECTORY && + (strcmp (name, ".") == 0 || + strcmp (name, "..") == 0)) { + continue; + } + + file = g_file_get_child (parent->file, name); + + if ((node = node_list_contains_file (original_children, file)) == NULL) { + + if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { + node = file_browser_node_dir_new (model, file, parent); + } else { + node = file_browser_node_new (file, parent); + } + + file_browser_node_set_from_info (model, node, info, FALSE); + + nodes = g_slist_prepend (nodes, node); + } + + g_object_unref (file); + g_object_unref (info); + } + + if (nodes) + model_add_nodes_batch (model, nodes, parent); +} + +static FileBrowserNode * +model_add_node_from_dir (GeditFileBrowserStore * model, + FileBrowserNode * parent, + GFile * file) +{ + FileBrowserNode *node; + + /* Check if it already exists */ + if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL) { + node = file_browser_node_dir_new (model, file, parent); + file_browser_node_set_from_info (model, node, NULL, FALSE); + + if (node->name == NULL) { + file_browser_node_set_name (node); + } + + if (node->icon == NULL) { + node->icon = gedit_file_browser_utils_pixbuf_from_theme ("folder", GTK_ICON_SIZE_MENU); + } + + model_add_node (model, node, parent); + } + + return node; +} + +/* Read is sync, but we only do it for local files */ +static void +parse_dot_hidden_file (FileBrowserNode *directory) +{ + gsize file_size; + char *file_contents; + GFile *child; + GFileInfo *info; + GFileType type; + int i; + FileBrowserNodeDir * dir = FILE_BROWSER_NODE_DIR (directory); + + /* FIXME: We only support .hidden on file: uri's for the moment. + * Need to figure out if we should do this async or sync to extend + * it to all types of uris. + */ + if (directory->file == NULL || !g_file_is_native (directory->file)) { + return; + } + + child = g_file_get_child (directory->file, ".hidden"); + info = g_file_query_info (child, G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL); + + type = info ? g_file_info_get_file_type (info) : G_FILE_TYPE_UNKNOWN; + + if (info) + g_object_unref (info); + + if (type != G_FILE_TYPE_REGULAR) { + g_object_unref (child); + + return; + } + + if (!g_file_load_contents (child, NULL, &file_contents, &file_size, NULL, NULL)) { + g_object_unref (child); + return; + } + + g_object_unref (child); + + if (dir->hidden_file_hash == NULL) { + dir->hidden_file_hash = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + } + + /* Now parse the data */ + i = 0; + while (i < file_size) { + int start; + + start = i; + while (i < file_size && file_contents[i] != '\n') { + i++; + } + + if (i > start) { + char *hidden_filename; + + hidden_filename = g_strndup (file_contents + start, i - start); + g_hash_table_insert (dir->hidden_file_hash, + hidden_filename, hidden_filename); + } + + i++; + + } + + g_free (file_contents); +} + +static void +on_directory_monitor_event (GFileMonitor * monitor, + GFile * file, + GFile * other_file, + GFileMonitorEvent event_type, + FileBrowserNode * parent) +{ + FileBrowserNode *node; + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_DELETED: + node = node_list_contains_file (dir->children, file); + + if (node != NULL) { + model_remove_node (dir->model, node, NULL, TRUE); + } + break; + case G_FILE_MONITOR_EVENT_CREATED: + if (g_file_query_exists (file, NULL)) { + model_add_node_from_file (dir->model, parent, file, NULL); + } + + break; + default: + break; + } +} + +static void +async_node_free (AsyncNode *async) +{ + g_object_unref (async->cancellable); + g_slist_free (async->original_children); + g_free (async); +} + +static void +model_iterate_next_files_cb (GFileEnumerator * enumerator, + GAsyncResult * result, + AsyncNode * async) +{ + GList * files; + GError * error = NULL; + FileBrowserNodeDir * dir = async->dir; + FileBrowserNode * parent = (FileBrowserNode *)dir; + + files = g_file_enumerator_next_files_finish (enumerator, result, &error); + + if (files == NULL) { + g_file_enumerator_close (enumerator, NULL, NULL); + async_node_free (async); + + if (!error) + { + /* We're done loading */ + g_object_unref (dir->cancellable); + dir->cancellable = NULL; + +/* + * FIXME: This is temporarly, it is a bug in gio: + * http://bugzilla.mate.org/show_bug.cgi?id=565924 + */ +#ifndef G_OS_WIN32 + if (g_file_is_native (parent->file) && dir->monitor == NULL) { + dir->monitor = g_file_monitor_directory (parent->file, + G_FILE_MONITOR_NONE, + NULL, + NULL); + if (dir->monitor != NULL) + { + g_signal_connect (dir->monitor, + "changed", + G_CALLBACK (on_directory_monitor_event), + parent); + } + } +#endif + + model_check_dummy (dir->model, parent); + model_end_loading (dir->model, parent); + } else { + /* Simply return if we were cancelled */ + if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED) + return; + + /* Otherwise handle the error appropriately */ + g_signal_emit (dir->model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, + error->message); + + file_browser_node_unload (dir->model, (FileBrowserNode *)parent, TRUE); + g_error_free (error); + } + } else if (g_cancellable_is_cancelled (async->cancellable)) { + /* Check cancel state manually */ + g_file_enumerator_close (enumerator, NULL, NULL); + async_node_free (async); + } else { + model_add_nodes_from_files (dir->model, parent, async->original_children, files); + + g_list_free (files); + next_files_async (enumerator, async); + } +} + +static void +next_files_async (GFileEnumerator * enumerator, + AsyncNode * async) +{ + g_file_enumerator_next_files_async (enumerator, + DIRECTORY_LOAD_ITEMS_PER_CALLBACK, + G_PRIORITY_DEFAULT, + async->cancellable, + (GAsyncReadyCallback)model_iterate_next_files_cb, + async); +} + +static void +model_iterate_children_cb (GFile * file, + GAsyncResult * result, + AsyncNode * async) +{ + GError * error = NULL; + GFileEnumerator * enumerator; + + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_node_free (async); + return; + } + + enumerator = g_file_enumerate_children_finish (file, result, &error); + + if (enumerator == NULL) { + /* Simply return if we were cancelled or if the dir is not there */ + FileBrowserNodeDir *dir = async->dir; + + /* Otherwise handle the error appropriately */ + g_signal_emit (dir->model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, + error->message); + + file_browser_node_unload (dir->model, (FileBrowserNode *)dir, TRUE); + g_error_free (error); + async_node_free (async); + } else { + next_files_async (enumerator, async); + } +} + +static void +model_load_directory (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + FileBrowserNodeDir *dir; + AsyncNode *async; + + g_return_if_fail (NODE_IS_DIR (node)); + + dir = FILE_BROWSER_NODE_DIR (node); + + /* Cancel a previous load */ + if (dir->cancellable != NULL) { + file_browser_node_unload (dir->model, node, TRUE); + } + + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; + model_begin_loading (model, node); + + /* Read the '.hidden' file first (if any) */ + parse_dot_hidden_file (node); + + dir->cancellable = g_cancellable_new (); + + async = g_new (AsyncNode, 1); + async->dir = dir; + async->cancellable = g_object_ref (dir->cancellable); + async->original_children = g_slist_copy (dir->children); + + /* Start loading async */ + g_file_enumerate_children_async (node->file, + STANDARD_ATTRIBUTE_TYPES, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, + async->cancellable, + (GAsyncReadyCallback)model_iterate_children_cb, + async); +} + +static GList * +get_parent_files (GeditFileBrowserStore * model, GFile * file) +{ + GList * result = NULL; + + result = g_list_prepend (result, g_object_ref (file)); + + while ((file = g_file_get_parent (file))) { + if (g_file_equal (file, model->priv->root->file)) { + g_object_unref (file); + break; + } + + result = g_list_prepend (result, file); + } + + return result; +} + +static void +model_fill (GeditFileBrowserStore * model, FileBrowserNode * node, + GtkTreePath ** path) +{ + gboolean free_path = FALSE; + GtkTreeIter iter = {0,}; + GSList *item; + FileBrowserNode *child; + + if (node == NULL) { + node = model->priv->virtual_root; + *path = gtk_tree_path_new (); + free_path = TRUE; + } + + if (*path == NULL) { + *path = + gedit_file_browser_store_get_path_real (model, node); + free_path = TRUE; + } + + if (!model_node_visibility (model, node)) { + if (free_path) + gtk_tree_path_free (*path); + + return; + } + + if (node != model->priv->virtual_root) { + /* Insert node */ + iter.user_data = node; + + row_inserted(model, path, &iter); + } + + if (NODE_IS_DIR (node)) { + /* Go to the first child */ + gtk_tree_path_down (*path); + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) { + child = (FileBrowserNode *) (item->data); + + if (model_node_visibility (model, child)) { + model_fill (model, child, path); + + /* Increase path for next child */ + gtk_tree_path_next (*path); + } + } + + /* Move back up to node path */ + gtk_tree_path_up (*path); + } + + model_check_dummy (model, node); + + if (free_path) + gtk_tree_path_free (*path); +} + +static void +set_virtual_root_from_node (GeditFileBrowserStore * model, + FileBrowserNode * node) +{ + FileBrowserNode *next; + FileBrowserNode *prev; + FileBrowserNode *check; + FileBrowserNodeDir *dir; + GSList *item; + GSList *copy; + GtkTreePath *empty = NULL; + + prev = node; + next = prev->parent; + + /* Free all the nodes below that we don't need in cache */ + while (prev != model->priv->root) { + dir = FILE_BROWSER_NODE_DIR (next); + copy = g_slist_copy (dir->children); + + for (item = copy; item; item = item->next) { + check = (FileBrowserNode *) (item->data); + + if (prev == node) { + /* Only free the children, keeping this depth in cache */ + if (check != node) { + file_browser_node_free_children + (model, check); + file_browser_node_unload (model, + check, + FALSE); + } + } else if (check != prev) { + /* Only free when the node is not in the chain */ + dir->children = + g_slist_remove (dir->children, check); + file_browser_node_free (model, check); + } + } + + if (prev != node) + file_browser_node_unload (model, next, FALSE); + + g_slist_free (copy); + prev = next; + next = prev->parent; + } + + /* Free all the nodes up that we don't need in cache */ + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) { + check = (FileBrowserNode *) (item->data); + + if (NODE_IS_DIR (check)) { + for (copy = + FILE_BROWSER_NODE_DIR (check)->children; copy; + copy = copy->next) { + file_browser_node_free_children (model, + (FileBrowserNode + *) + (copy-> + data)); + file_browser_node_unload (model, + (FileBrowserNode + *) (copy->data), + FALSE); + } + } else if (NODE_IS_DUMMY (check)) { + check->flags |= + GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + } + } + + /* Now finally, set the virtual root, and load it up! */ + model->priv->virtual_root = node; + + /* Notify that the virtual-root has changed before loading up new nodes so that the + "root_changed" signal can be emitted before any "inserted" signals */ + g_object_notify (G_OBJECT (model), "virtual-root"); + + model_fill (model, NULL, &empty); + + if (!NODE_LOADED (node)) + model_load_directory (model, node); +} + +static void +set_virtual_root_from_file (GeditFileBrowserStore * model, + GFile * file) +{ + GList * files; + GList * item; + FileBrowserNode * parent; + GFile * check; + + /* Always clear the model before altering the nodes */ + model_clear (model, FALSE); + + /* Create the node path, get all the uri's */ + files = get_parent_files (model, file); + parent = model->priv->root; + + for (item = files; item; item = item->next) { + check = G_FILE (item->data); + + parent = model_add_node_from_dir (model, parent, check); + g_object_unref (check); + } + + g_list_free (files); + set_virtual_root_from_node (model, parent); +} + +static FileBrowserNode * +model_find_node_children (GeditFileBrowserStore * model, + FileBrowserNode * parent, + GFile * file) +{ + FileBrowserNodeDir *dir; + FileBrowserNode *child; + FileBrowserNode *result; + GSList *children; + + if (!NODE_IS_DIR (parent)) + return NULL; + + dir = FILE_BROWSER_NODE_DIR (parent); + + for (children = dir->children; children; children = children->next) { + child = (FileBrowserNode *)(children->data); + + result = model_find_node (model, child, file); + + if (result) + return result; + } + + return NULL; +} + +static FileBrowserNode * +model_find_node (GeditFileBrowserStore * model, + FileBrowserNode * node, + GFile * file) +{ + if (node == NULL) + node = model->priv->root; + + if (node->file && g_file_equal (node->file, file)) + return node; + + if (NODE_IS_DIR (node) && g_file_has_prefix (file, node->file)) + return model_find_node_children (model, node, file); + + return NULL; +} + +static GQuark +gedit_file_browser_store_error_quark (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) { + quark = g_quark_from_string ("gedit_file_browser_store_error"); + } + + return quark; +} + +static GFile * +unique_new_name (GFile * directory, gchar const * name) +{ + GFile * newuri = NULL; + guint num = 0; + gchar * newname; + + while (newuri == NULL || g_file_query_exists (newuri, NULL)) { + if (newuri != NULL) + g_object_unref (newuri); + + if (num == 0) + newname = g_strdup (name); + else + newname = g_strdup_printf ("%s(%d)", name, num); + + newuri = g_file_get_child (directory, newname); + g_free (newname); + + ++num; + } + + return newuri; +} + +static GeditFileBrowserStoreResult +model_root_mounted (GeditFileBrowserStore * model, gchar const * virtual_root) +{ + model_check_dummy (model, model->priv->root); + g_object_notify (G_OBJECT (model), "root"); + + if (virtual_root != NULL) + return + gedit_file_browser_store_set_virtual_root_from_string + (model, virtual_root); + else + set_virtual_root_from_node (model, + model->priv->root); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +static void +handle_root_error (GeditFileBrowserStore * model, GError *error) +{ + FileBrowserNode * root; + + g_signal_emit (model, + model_signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + error->message); + + /* Set the virtual root to the root */ + root = model->priv->root; + model->priv->virtual_root = root; + + /* Set the root to be loaded */ + root->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED; + + /* Check the dummy */ + model_check_dummy (model, root); + + g_object_notify (G_OBJECT (model), "root"); + g_object_notify (G_OBJECT (model), "virtual-root"); +} + +static void +mount_cb (GFile * file, + GAsyncResult * res, + MountInfo * mount_info) +{ + gboolean mounted; + GError * error = NULL; + GeditFileBrowserStore * model = mount_info->model; + + mounted = g_file_mount_enclosing_volume_finish (file, res, &error); + + if (mount_info->model) + { + model->priv->mount_info = NULL; + model_end_loading (model, model->priv->root); + } + + if (!mount_info->model || g_cancellable_is_cancelled (mount_info->cancellable)) + { + // Reset because it might be reused? + g_cancellable_reset (mount_info->cancellable); + } + else if (mounted) + { + model_root_mounted (model, mount_info->virtual_root); + } + else if (error->code != G_IO_ERROR_CANCELLED) + { + handle_root_error (model, error); + } + + if (error) + g_error_free (error); + + g_object_unref (mount_info->operation); + g_object_unref (mount_info->cancellable); + g_free (mount_info->virtual_root); + + g_free (mount_info); +} + +static GeditFileBrowserStoreResult +model_mount_root (GeditFileBrowserStore * model, gchar const * virtual_root) +{ + GFileInfo * info; + GError * error = NULL; + MountInfo * mount_info; + + info = g_file_query_info (model->priv->root->file, + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, + &error); + + if (!info) { + if (error->code == G_IO_ERROR_NOT_MOUNTED) { + /* Try to mount it */ + FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable = g_cancellable_new (); + + mount_info = g_new(MountInfo, 1); + mount_info->model = model; + mount_info->virtual_root = g_strdup (virtual_root); + + /* FIXME: we should be setting the correct window */ + mount_info->operation = gtk_mount_operation_new (NULL); + mount_info->cancellable = g_object_ref (FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable); + + model_begin_loading (model, model->priv->root); + g_file_mount_enclosing_volume (model->priv->root->file, + G_MOUNT_MOUNT_NONE, + mount_info->operation, + mount_info->cancellable, + (GAsyncReadyCallback)mount_cb, + mount_info); + + model->priv->mount_info = mount_info; + return GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING; + } + else + { + handle_root_error (model, error); + } + + g_error_free (error); + } else { + g_object_unref (info); + + return model_root_mounted (model, virtual_root); + } + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +/* Public */ +GeditFileBrowserStore * +gedit_file_browser_store_new (gchar const *root) +{ + GeditFileBrowserStore *obj = + GEDIT_FILE_BROWSER_STORE (g_object_new + (GEDIT_TYPE_FILE_BROWSER_STORE, + NULL)); + + gedit_file_browser_store_set_root (obj, root); + return obj; +} + +void +gedit_file_browser_store_set_value (GeditFileBrowserStore * tree_model, + GtkTreeIter * iter, gint column, + GValue * value) +{ + gpointer data; + FileBrowserNode *node; + GtkTreePath *path; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model)); + g_return_if_fail (column == + GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM); + g_return_if_fail (G_VALUE_HOLDS_OBJECT (value)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + data = g_value_get_object (value); + + if (data) + g_return_if_fail (GDK_IS_PIXBUF (data)); + + node = (FileBrowserNode *) (iter->user_data); + + if (node->emblem) + g_object_unref (node->emblem); + + if (data) + node->emblem = g_object_ref (GDK_PIXBUF (data)); + else + node->emblem = NULL; + + model_recomposite_icon (tree_model, iter); + + if (model_node_visibility (tree_model, node)) { + path = gedit_file_browser_store_get_path (GTK_TREE_MODEL (tree_model), + iter); + row_changed (tree_model, &path, iter); + gtk_tree_path_free (path); + } +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter != NULL, + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter->user_data != NULL, + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + model_clear (model, FALSE); + set_virtual_root_from_node (model, + (FileBrowserNode *) (iter->user_data)); + + return TRUE; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_from_string + (GeditFileBrowserStore * model, gchar const *root) { + GFile *file; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + file = g_file_new_for_uri (root); + if (file == NULL) { + g_warning ("Invalid uri (%s)", root); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + /* Check if uri is already the virtual root */ + if (model->priv->virtual_root && + g_file_equal (model->priv->virtual_root->file, file)) { + g_object_unref (file); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + /* Check if uri is the root itself */ + if (g_file_equal (model->priv->root->file, file)) { + g_object_unref (file); + + /* Always clear the model before altering the nodes */ + model_clear (model, FALSE); + set_virtual_root_from_node (model, model->priv->root); + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; + } + + if (!g_file_has_prefix (file, model->priv->root->file)) { + gchar *str, *str1; + + str = g_file_get_parse_name (model->priv->root->file); + str1 = g_file_get_parse_name (file); + + g_warning + ("Virtual root (%s) is not below actual root (%s)", + str1, str); + + g_free (str); + g_free (str1); + + g_object_unref (file); + return GEDIT_FILE_BROWSER_STORE_RESULT_ERROR; + } + + set_virtual_root_from_file (model, file); + g_object_unref (file); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore * + model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (model->priv->virtual_root == model->priv->root) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + model_clear (model, FALSE); + set_virtual_root_from_node (model, model->priv->root); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore * + model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (model->priv->virtual_root == model->priv->root) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + model_clear (model, FALSE); + set_virtual_root_from_node (model, + model->priv->virtual_root->parent); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +gboolean +gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore * + model, GtkTreeIter * iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + if (model->priv->virtual_root == NULL) + return FALSE; + + iter->user_data = model->priv->virtual_root; + return TRUE; +} + +gboolean +gedit_file_browser_store_get_iter_root (GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + if (model->priv->root == NULL) + return FALSE; + + iter->user_data = model->priv->root; + return TRUE; +} + +gboolean +gedit_file_browser_store_iter_equal (GeditFileBrowserStore * model, + GtkTreeIter * iter1, + GtkTreeIter * iter2) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter1 != NULL, FALSE); + g_return_val_if_fail (iter2 != NULL, FALSE); + g_return_val_if_fail (iter1->user_data != NULL, FALSE); + g_return_val_if_fail (iter2->user_data != NULL, FALSE); + + return (iter1->user_data == iter2->user_data); +} + +void +gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (store)); + + cancel_mount_operation (store); +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore * + model, + gchar const *root, + gchar const *virtual_root) +{ + GFile * file = NULL; + GFile * vfile = NULL; + FileBrowserNode * node; + gboolean equal = FALSE; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (root == NULL && model->priv->root == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + if (root != NULL) { + file = g_file_new_for_uri (root); + } + + if (root != NULL && model->priv->root != NULL) { + equal = g_file_equal (file, model->priv->root->file); + + if (equal && virtual_root == NULL) { + g_object_unref (file); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + } + + if (virtual_root) { + vfile = g_file_new_for_uri (virtual_root); + + if (equal && g_file_equal (vfile, model->priv->virtual_root->file)) { + if (file) + g_object_unref (file); + + g_object_unref (vfile); + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + g_object_unref (vfile); + } + + /* make sure to cancel any previous mount operations */ + cancel_mount_operation (model); + + /* Always clear the model before altering the nodes */ + model_clear (model, TRUE); + file_browser_node_free (model, model->priv->root); + + model->priv->root = NULL; + model->priv->virtual_root = NULL; + + if (file != NULL) { + /* Create the root node */ + node = file_browser_node_dir_new (model, file, NULL); + + g_object_unref (file); + + model->priv->root = node; + return model_mount_root (model, virtual_root); + } else { + g_object_notify (G_OBJECT (model), "root"); + g_object_notify (G_OBJECT (model), "virtual-root"); + } + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root (GeditFileBrowserStore * model, + gchar const *root) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + return gedit_file_browser_store_set_root_and_virtual_root (model, + root, + NULL); +} + +gchar * +gedit_file_browser_store_get_root (GeditFileBrowserStore * model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL); + + if (model->priv->root == NULL || model->priv->root->file == NULL) + return NULL; + else + return g_file_get_uri (model->priv->root->file); +} + +gchar * +gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore * model) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL); + + if (model->priv->virtual_root == NULL || model->priv->virtual_root->file == NULL) + return NULL; + else + return g_file_get_uri (model->priv->virtual_root->file); +} + +void +_gedit_file_browser_store_iter_expanded (GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + FileBrowserNode *node; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *) (iter->user_data); + + if (NODE_IS_DIR (node) && !NODE_LOADED (node)) { + /* Load it now */ + model_load_directory (model, node); + } +} + +void +_gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + FileBrowserNode *node; + GSList *item; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + g_return_if_fail (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *) (iter->user_data); + + if (NODE_IS_DIR (node) && NODE_LOADED (node)) { + /* Unload children of the children, keeping 1 depth in cache */ + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; + item = item->next) { + node = (FileBrowserNode *) (item->data); + + if (NODE_IS_DIR (node) && NODE_LOADED (node)) { + file_browser_node_unload (model, node, + TRUE); + model_check_dummy (model, node); + } + } + } +} + +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore * model) +{ + return model->priv->filter_mode; +} + +void +gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore * model, + GeditFileBrowserStoreFilterMode + mode) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->filter_mode == mode) + return; + + model->priv->filter_mode = mode; + model_refilter (model); + + g_object_notify (G_OBJECT (model), "filter-mode"); +} + +void +gedit_file_browser_store_set_filter_func (GeditFileBrowserStore * model, + GeditFileBrowserStoreFilterFunc + func, gpointer user_data) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + model->priv->filter_func = func; + model->priv->filter_user_data = user_data; + model_refilter (model); +} + +void +gedit_file_browser_store_refilter (GeditFileBrowserStore * model) +{ + model_refilter (model); +} + +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_filter_mode_get_default (void) +{ + return GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; +} + +void +gedit_file_browser_store_refresh (GeditFileBrowserStore * model) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->root == NULL || model->priv->virtual_root == NULL) + return; + + /* Clear the model */ + g_signal_emit (model, model_signals[BEGIN_REFRESH], 0); + file_browser_node_unload (model, model->priv->virtual_root, TRUE); + model_load_directory (model, model->priv->virtual_root); + g_signal_emit (model, model_signals[END_REFRESH], 0); +} + +static void +reparent_node (FileBrowserNode * node, gboolean reparent) +{ + FileBrowserNodeDir * dir; + GSList * child; + GFile * parent; + gchar * base; + + if (!node->file) { + return; + } + + if (reparent) { + parent = node->parent->file; + base = g_file_get_basename (node->file); + g_object_unref (node->file); + + node->file = g_file_get_child (parent, base); + g_free (base); + } + + if (NODE_IS_DIR (node)) { + dir = FILE_BROWSER_NODE_DIR (node); + + for (child = dir->children; child; child = child->next) { + reparent_node ((FileBrowserNode *)child->data, TRUE); + } + } +} + +gboolean +gedit_file_browser_store_rename (GeditFileBrowserStore * model, + GtkTreeIter * iter, + const gchar * new_name, + GError ** error) +{ + FileBrowserNode *node; + GFile * file; + GFile * parent; + GFile * previous; + GError * err = NULL; + gchar * olduri; + gchar * newuri; + GtkTreePath *path; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + g_return_val_if_fail (iter->user_data != NULL, FALSE); + + node = (FileBrowserNode *) (iter->user_data); + + parent = g_file_get_parent (node->file); + g_return_val_if_fail (parent != NULL, FALSE); + + file = g_file_get_child (parent, new_name); + g_object_unref (parent); + + if (g_file_equal (node->file, file)) { + g_object_unref (file); + return TRUE; + } + + if (g_file_move (node->file, file, G_FILE_COPY_NONE, NULL, NULL, NULL, &err)) { + previous = node->file; + node->file = file; + + /* This makes sure the actual info for the node is requeried */ + file_browser_node_set_name (node); + file_browser_node_set_from_info (model, node, NULL, TRUE); + + reparent_node (node, FALSE); + + if (model_node_visibility (model, node)) { + path = gedit_file_browser_store_get_path_real (model, node); + row_changed (model, &path, iter); + gtk_tree_path_free (path); + + /* Reorder this item */ + model_resort_node (model, node); + } else { + g_object_unref (previous); + + if (error != NULL) + *error = g_error_new_literal (gedit_file_browser_store_error_quark (), + GEDIT_FILE_BROWSER_ERROR_RENAME, + _("The renamed file is currently filtered out. You need to adjust your filter settings to make the file visible")); + return FALSE; + } + + olduri = g_file_get_uri (previous); + newuri = g_file_get_uri (node->file); + + g_signal_emit (model, model_signals[RENAME], 0, olduri, newuri); + + g_object_unref (previous); + g_free (olduri); + g_free (newuri); + + return TRUE; + } else { + g_object_unref (file); + + if (err) { + if (error != NULL) { + *error = + g_error_new_literal + (gedit_file_browser_store_error_quark (), + GEDIT_FILE_BROWSER_ERROR_RENAME, + err->message); + } + + g_error_free (err); + } + + return FALSE; + } +} + +static void +async_data_free (AsyncData * data) +{ + g_object_unref (data->cancellable); + + g_list_foreach (data->files, (GFunc)g_object_unref, NULL); + g_list_free (data->files); + + if (!data->removed) + data->model->priv->async_handles = g_slist_remove (data->model->priv->async_handles, data); + + g_free (data); +} + +static gboolean +emit_no_trash (AsyncData * data) +{ + /* Emit the no trash error */ + gboolean ret; + + g_signal_emit (data->model, model_signals[NO_TRASH], 0, data->files, &ret); + return ret; +} + +typedef struct { + GeditFileBrowserStore * model; + GFile * file; +} IdleDelete; + +static gboolean +file_deleted (IdleDelete * data) +{ + FileBrowserNode * node; + node = model_find_node (data->model, NULL, data->file); + + if (node != NULL) + model_remove_node (data->model, node, NULL, TRUE); + + return FALSE; +} + +static gboolean +delete_files (GIOSchedulerJob * job, + GCancellable * cancellable, + AsyncData * data) +{ + GFile * file; + GError * error = NULL; + gboolean ret; + gint code; + IdleDelete delete; + + /* Check if our job is done */ + if (!data->iter) + return FALSE; + + /* Move a file to the trash */ + file = G_FILE (data->iter->data); + + if (data->trash) + ret = g_file_trash (file, cancellable, &error); + else + ret = g_file_delete (file, cancellable, &error); + + if (ret) { + delete.model = data->model; + delete.file = file; + + /* Remove the file from the model in the main loop */ + g_io_scheduler_job_send_to_mainloop (job, (GSourceFunc)file_deleted, &delete, NULL); + } else if (!ret && error) { + code = error->code; + g_error_free (error); + + if (data->trash && code == G_IO_ERROR_NOT_SUPPORTED) { + /* Trash is not supported on this system ... */ + if (g_io_scheduler_job_send_to_mainloop (job, (GSourceFunc)emit_no_trash, data, NULL)) + { + /* Changes this into a delete job */ + data->trash = FALSE; + data->iter = data->files; + + return TRUE; + } + + /* End the job */ + return FALSE; + } else if (code == G_IO_ERROR_CANCELLED) { + /* Job has been cancelled, just let the job end */ + return FALSE; + } + } + + /* Process the next item */ + data->iter = data->iter->next; + return TRUE; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_delete_all (GeditFileBrowserStore *model, + GList *rows, gboolean trash) +{ + FileBrowserNode * node; + AsyncData * data; + GList * files = NULL; + GList * row; + GtkTreeIter iter; + GtkTreePath * prev = NULL; + GtkTreePath * path; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (rows == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + /* First we sort the paths so that we can later on remove any + files/directories that are actually subfiles/directories of + a directory that's also deleted */ + rows = g_list_sort (g_list_copy (rows), (GCompareFunc)gtk_tree_path_compare); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path)) + continue; + + /* Skip if the current path is actually a descendant of the + previous path */ + if (prev != NULL && gtk_tree_path_is_descendant (path, prev)) + continue; + + prev = path; + node = (FileBrowserNode *)(iter.user_data); + files = g_list_prepend (files, g_object_ref (node->file)); + } + + data = g_new (AsyncData, 1); + + data->model = model; + data->cancellable = g_cancellable_new (); + data->files = files; + data->trash = trash; + data->iter = files; + data->removed = FALSE; + + model->priv->async_handles = + g_slist_prepend (model->priv->async_handles, data); + + g_io_scheduler_push_job ((GIOSchedulerJobFunc)delete_files, + data, + (GDestroyNotify)async_data_free, + G_PRIORITY_DEFAULT, + data->cancellable); + g_list_free (rows); + + return GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +GeditFileBrowserStoreResult +gedit_file_browser_store_delete (GeditFileBrowserStore * model, + GtkTreeIter * iter, gboolean trash) +{ + FileBrowserNode *node; + GList *rows = NULL; + GeditFileBrowserStoreResult result; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + g_return_val_if_fail (iter->user_data != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + node = (FileBrowserNode *) (iter->user_data); + + if (NODE_IS_DUMMY (node)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + rows = g_list_append(NULL, gedit_file_browser_store_get_path_real (model, node)); + result = gedit_file_browser_store_delete_all (model, rows, trash); + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return result; +} + +gboolean +gedit_file_browser_store_new_file (GeditFileBrowserStore * model, + GtkTreeIter * parent, + GtkTreeIter * iter) +{ + GFile * file; + GFileOutputStream * stream; + FileBrowserNodeDir *parent_node; + gboolean result = FALSE; + FileBrowserNode *node; + GError * error = NULL; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (parent != NULL, FALSE); + g_return_val_if_fail (parent->user_data != NULL, FALSE); + g_return_val_if_fail (NODE_IS_DIR + ((FileBrowserNode *) (parent->user_data)), + FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + parent_node = FILE_BROWSER_NODE_DIR (parent->user_data); + /* Translators: This is the default name of new files created by the file browser pane. */ + file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("file")); + + stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, &error); + + if (!stream) + { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + error->message); + g_error_free (error); + } else { + g_object_unref (stream); + node = model_add_node_from_file (model, + (FileBrowserNode *)parent_node, + file, + NULL); + + if (model_node_visibility (model, node)) { + iter->user_data = node; + result = TRUE; + } else { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + _ + ("The new file is currently filtered out. You need to adjust your filter settings to make the file visible")); + } + } + + g_object_unref (file); + return result; +} + +gboolean +gedit_file_browser_store_new_directory (GeditFileBrowserStore * model, + GtkTreeIter * parent, + GtkTreeIter * iter) +{ + GFile * file; + FileBrowserNodeDir *parent_node; + GError * error = NULL; + FileBrowserNode *node; + gboolean result = FALSE; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE); + g_return_val_if_fail (parent != NULL, FALSE); + g_return_val_if_fail (parent->user_data != NULL, FALSE); + g_return_val_if_fail (NODE_IS_DIR + ((FileBrowserNode *) (parent->user_data)), + FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + parent_node = FILE_BROWSER_NODE_DIR (parent->user_data); + /* Translators: This is the default name of new directories created by the file browser pane. */ + file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("directory")); + + if (!g_file_make_directory (file, NULL, &error)) { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY, + error->message); + g_error_free (error); + } else { + node = model_add_node_from_file (model, + (FileBrowserNode *)parent_node, + file, + NULL); + + if (model_node_visibility (model, node)) { + iter->user_data = node; + result = TRUE; + } else { + g_signal_emit (model, model_signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_NEW_FILE, + _ + ("The new directory is currently filtered out. You need to adjust your filter settings to make the directory visible")); + } + } + + g_object_unref (file); + return result; +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-store.h b/plugins/filebrowser/gedit-file-browser-store.h new file mode 100755 index 00000000..f31da327 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.h @@ -0,0 +1,200 @@ +/* + * gedit-file-browser-store.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GEDIT_FILE_BROWSER_STORE_H__ +#define __GEDIT_FILE_BROWSER_STORE_H__ + +#include + +G_BEGIN_DECLS +#define GEDIT_TYPE_FILE_BROWSER_STORE (gedit_file_browser_store_get_type ()) +#define GEDIT_FILE_BROWSER_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStore)) +#define GEDIT_FILE_BROWSER_STORE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStore const)) +#define GEDIT_FILE_BROWSER_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStoreClass)) +#define GEDIT_IS_FILE_BROWSER_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_STORE)) +#define GEDIT_IS_FILE_BROWSER_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_STORE)) +#define GEDIT_FILE_BROWSER_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStoreClass)) + +typedef enum +{ + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON = 0, + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, + GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM, + GEDIT_FILE_BROWSER_STORE_COLUMN_NUM +} GeditFileBrowserStoreColumn; + +typedef enum +{ + GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY = 1 << 0, + GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN = 1 << 1, + GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT = 1 << 2, + GEDIT_FILE_BROWSER_STORE_FLAG_LOADED = 1 << 3, + GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED = 1 << 4, + GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY = 1 << 5 +} GeditFileBrowserStoreFlag; + +typedef enum +{ + GEDIT_FILE_BROWSER_STORE_RESULT_OK, + GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE, + GEDIT_FILE_BROWSER_STORE_RESULT_ERROR, + GEDIT_FILE_BROWSER_STORE_RESULT_NO_TRASH, + GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING, + GEDIT_FILE_BROWSER_STORE_RESULT_NUM +} GeditFileBrowserStoreResult; + +typedef enum +{ + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_NONE = 0, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN = 1 << 0, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY = 1 << 1 +} GeditFileBrowserStoreFilterMode; + +#define FILE_IS_DIR(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY) +#define FILE_IS_HIDDEN(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN) +#define FILE_IS_TEXT(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT) +#define FILE_LOADED(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_LOADED) +#define FILE_IS_FILTERED(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED) +#define FILE_IS_DUMMY(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY) + +typedef struct _GeditFileBrowserStore GeditFileBrowserStore; +typedef struct _GeditFileBrowserStoreClass GeditFileBrowserStoreClass; +typedef struct _GeditFileBrowserStorePrivate GeditFileBrowserStorePrivate; + +typedef gboolean (*GeditFileBrowserStoreFilterFunc) (GeditFileBrowserStore + * model, + GtkTreeIter * iter, + gpointer user_data); + +struct _GeditFileBrowserStore +{ + GObject parent; + + GeditFileBrowserStorePrivate *priv; +}; + +struct _GeditFileBrowserStoreClass { + GObjectClass parent_class; + + /* Signals */ + void (*begin_loading) (GeditFileBrowserStore * model, + GtkTreeIter * iter); + void (*end_loading) (GeditFileBrowserStore * model, + GtkTreeIter * iter); + void (*error) (GeditFileBrowserStore * model, + guint code, + gchar * message); + gboolean (*no_trash) (GeditFileBrowserStore * model, + GList * files); + void (*rename) (GeditFileBrowserStore * model, + const gchar * olduri, + const gchar * newuri); + void (*begin_refresh) (GeditFileBrowserStore * model); + void (*end_refresh) (GeditFileBrowserStore * model); + void (*unload) (GeditFileBrowserStore * model, + const gchar * uri); +}; + +GType gedit_file_browser_store_get_type (void) G_GNUC_CONST; +GType gedit_file_browser_store_register_type (GTypeModule * module); + +GeditFileBrowserStore *gedit_file_browser_store_new (gchar const *root); + +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore * model, + gchar const *root, + gchar const *virtual_root); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_root (GeditFileBrowserStore * model, + gchar const *root); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore * model, + GtkTreeIter * iter); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_from_string (GeditFileBrowserStore * model, + gchar const *root); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore * model); +GeditFileBrowserStoreResult +gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore * model); + +gboolean +gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore * model, + GtkTreeIter * iter); +gboolean gedit_file_browser_store_get_iter_root (GeditFileBrowserStore * model, + GtkTreeIter * iter); +gchar * gedit_file_browser_store_get_root (GeditFileBrowserStore * model); +gchar * gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore * model); + +gboolean gedit_file_browser_store_iter_equal (GeditFileBrowserStore * model, + GtkTreeIter * iter1, + GtkTreeIter * iter2); + +void gedit_file_browser_store_set_value (GeditFileBrowserStore * tree_model, + GtkTreeIter * iter, + gint column, + GValue * value); + +void _gedit_file_browser_store_iter_expanded (GeditFileBrowserStore * model, + GtkTreeIter * iter); +void _gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore * model, + GtkTreeIter * iter); + +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore * model); +void gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore * model, + GeditFileBrowserStoreFilterMode mode); +void gedit_file_browser_store_set_filter_func (GeditFileBrowserStore * model, + GeditFileBrowserStoreFilterFunc func, + gpointer user_data); +void gedit_file_browser_store_refilter (GeditFileBrowserStore * model); +GeditFileBrowserStoreFilterMode +gedit_file_browser_store_filter_mode_get_default (void); + +void gedit_file_browser_store_refresh (GeditFileBrowserStore * model); +gboolean gedit_file_browser_store_rename (GeditFileBrowserStore * model, + GtkTreeIter * iter, + gchar const *new_name, + GError ** error); +GeditFileBrowserStoreResult +gedit_file_browser_store_delete (GeditFileBrowserStore * model, + GtkTreeIter * iter, + gboolean trash); +GeditFileBrowserStoreResult +gedit_file_browser_store_delete_all (GeditFileBrowserStore * model, + GList *rows, + gboolean trash); + +gboolean gedit_file_browser_store_new_file (GeditFileBrowserStore * model, + GtkTreeIter * parent, + GtkTreeIter * iter); +gboolean gedit_file_browser_store_new_directory (GeditFileBrowserStore * model, + GtkTreeIter * parent, + GtkTreeIter * iter); + +void gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store); + +G_END_DECLS +#endif /* __GEDIT_FILE_BROWSER_STORE_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-utils.c b/plugins/filebrowser/gedit-file-browser-utils.c new file mode 100755 index 00000000..d8f4028a --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-utils.c @@ -0,0 +1,198 @@ +/* + * gedit-file-bookmarks-store.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gedit-file-browser-utils.h" +#include + +static GdkPixbuf * +process_icon_pixbuf (GdkPixbuf * pixbuf, + gchar const * name, + gint size, + GError * error) +{ + GdkPixbuf * scale; + + if (error != NULL) { + g_warning ("Could not load theme icon %s: %s", + name, + error->message); + g_error_free (error); + } + + if (pixbuf && gdk_pixbuf_get_width (pixbuf) > size) { + scale = gdk_pixbuf_scale_simple (pixbuf, + size, + size, + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + pixbuf = scale; + } + + return pixbuf; +} + +GdkPixbuf * +gedit_file_browser_utils_pixbuf_from_theme (gchar const * name, + GtkIconSize size) +{ + gint width; + GError *error = NULL; + GdkPixbuf *pixbuf; + + gtk_icon_size_lookup (size, &width, NULL); + + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + name, + width, + 0, + &error); + + pixbuf = process_icon_pixbuf (pixbuf, name, width, error); + + return pixbuf; +} + +GdkPixbuf * +gedit_file_browser_utils_pixbuf_from_icon (GIcon * icon, + GtkIconSize size) +{ + GdkPixbuf * ret = NULL; + GtkIconTheme *theme; + GtkIconInfo *info; + gint width; + + if (!icon) + return NULL; + + theme = gtk_icon_theme_get_default (); + gtk_icon_size_lookup (size, &width, NULL); + + info = gtk_icon_theme_lookup_by_gicon (theme, + icon, + width, + GTK_ICON_LOOKUP_USE_BUILTIN); + + if (!info) + return NULL; + + ret = gtk_icon_info_load_icon (info, NULL); + gtk_icon_info_free (info); + + return ret; +} + +GdkPixbuf * +gedit_file_browser_utils_pixbuf_from_file (GFile * file, + GtkIconSize size) +{ + GIcon * icon; + GFileInfo * info; + GdkPixbuf * ret = NULL; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_ICON, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (!info) + return NULL; + + icon = g_file_info_get_icon (info); + if (icon != NULL) + ret = gedit_file_browser_utils_pixbuf_from_icon (icon, size); + + g_object_unref (info); + + return ret; +} + +gchar * +gedit_file_browser_utils_file_basename (GFile * file) +{ + gchar *uri; + gchar *ret; + + uri = g_file_get_uri (file); + ret = gedit_file_browser_utils_uri_basename (uri); + g_free (uri); + + return ret; +} + +gchar * +gedit_file_browser_utils_uri_basename (gchar const * uri) +{ + return gedit_utils_basename_for_display (uri); +} + +gboolean +gedit_file_browser_utils_confirmation_dialog (GeditWindow * window, + GtkMessageType type, + gchar const *message, + gchar const *secondary, + gchar const * button_stock, + gchar const * button_label) +{ + GtkWidget *dlg; + gint ret; + GtkWidget *button; + + dlg = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + type, + GTK_BUTTONS_NONE, "%s", message); + + if (secondary) + gtk_message_dialog_format_secondary_text + (GTK_MESSAGE_DIALOG (dlg), "%s", secondary); + + /* Add a cancel button */ + button = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + gtk_widget_show (button); + + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_dialog_add_action_widget (GTK_DIALOG (dlg), + button, + GTK_RESPONSE_CANCEL); + + /* Add custom button */ + button = gtk_button_new_from_stock (button_stock); + + if (button_label) { + gtk_button_set_use_stock (GTK_BUTTON (button), FALSE); + gtk_button_set_label (GTK_BUTTON (button), button_label); + } + + gtk_widget_show (button); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_dialog_add_action_widget (GTK_DIALOG (dlg), + button, + GTK_RESPONSE_OK); + + ret = gtk_dialog_run (GTK_DIALOG (dlg)); + gtk_widget_destroy (dlg); + + return (ret == GTK_RESPONSE_OK); +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-utils.h b/plugins/filebrowser/gedit-file-browser-utils.h new file mode 100755 index 00000000..fc9acbce --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-utils.h @@ -0,0 +1,27 @@ +#ifndef __GEDIT_FILE_BROWSER_UTILS_H__ +#define __GEDIT_FILE_BROWSER_UTILS_H__ + +#include +#include + +GdkPixbuf *gedit_file_browser_utils_pixbuf_from_theme (gchar const *name, + GtkIconSize size); + +GdkPixbuf *gedit_file_browser_utils_pixbuf_from_icon (GIcon * icon, + GtkIconSize size); +GdkPixbuf *gedit_file_browser_utils_pixbuf_from_file (GFile * file, + GtkIconSize size); + +gchar * gedit_file_browser_utils_file_basename (GFile * file); +gchar * gedit_file_browser_utils_uri_basename (gchar const * uri); + +gboolean gedit_file_browser_utils_confirmation_dialog (GeditWindow * window, + GtkMessageType type, + gchar const *message, + gchar const *secondary, + gchar const * button_stock, + gchar const * button_label); + +#endif /* __GEDIT_FILE_BROWSER_UTILS_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-view.c b/plugins/filebrowser/gedit-file-browser-view.c new file mode 100755 index 00000000..05733da1 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-view.c @@ -0,0 +1,1256 @@ +/* + * gedit-file-browser-view.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include + +#include "gedit-file-browser-store.h" +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-view.h" +#include "gedit-file-browser-marshal.h" +#include "gedit-file-browser-enum-types.h" + +#define GEDIT_FILE_BROWSER_VIEW_GET_PRIVATE(object)( \ + G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewPrivate)) + +struct _GeditFileBrowserViewPrivate +{ + GtkTreeViewColumn *column; + GtkCellRenderer *pixbuf_renderer; + GtkCellRenderer *text_renderer; + + GtkTreeModel *model; + GtkTreeRowReference *editable; + + GdkCursor *busy_cursor; + + /* CLick policy */ + GeditFileBrowserViewClickPolicy click_policy; + GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */ + GtkTreePath *hover_path; + GdkCursor *hand_cursor; + gboolean ignore_release; + gboolean selected_on_button_down; + gint drag_button; + gboolean drag_started; + + gboolean restore_expand_state; + gboolean is_refresh; + GHashTable * expand_state; +}; + +/* Properties */ +enum +{ + PROP_0, + + PROP_CLICK_POLICY, + PROP_RESTORE_EXPAND_STATE +}; + +/* Signals */ +enum +{ + ERROR, + FILE_ACTIVATED, + DIRECTORY_ACTIVATED, + BOOKMARK_ACTIVATED, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS] = { 0 }; + +static const GtkTargetEntry drag_source_targets[] = { + { "text/uri-list", 0, 0 } +}; + +GEDIT_PLUGIN_DEFINE_TYPE (GeditFileBrowserView, gedit_file_browser_view, + GTK_TYPE_TREE_VIEW) + +static void on_cell_edited (GtkCellRendererText * cell, + gchar * path, + gchar * new_text, + GeditFileBrowserView * tree_view); + +static void on_begin_refresh (GeditFileBrowserStore * model, + GeditFileBrowserView * view); +static void on_end_refresh (GeditFileBrowserStore * model, + GeditFileBrowserView * view); + +static void on_unload (GeditFileBrowserStore * model, + gchar const * uri, + GeditFileBrowserView * view); + +static void on_row_inserted (GeditFileBrowserStore * model, + GtkTreePath * path, + GtkTreeIter * iter, + GeditFileBrowserView * view); + +static void +gedit_file_browser_view_finalize (GObject * object) +{ + GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW(object); + + if (obj->priv->hand_cursor) + gdk_cursor_unref(obj->priv->hand_cursor); + + if (obj->priv->hover_path) + gtk_tree_path_free (obj->priv->hover_path); + + if (obj->priv->expand_state) + { + g_hash_table_destroy (obj->priv->expand_state); + obj->priv->expand_state = NULL; + } + + gdk_cursor_unref (obj->priv->busy_cursor); + + G_OBJECT_CLASS (gedit_file_browser_view_parent_class)-> + finalize (object); +} + +static void +add_expand_state (GeditFileBrowserView * view, + gchar const * uri) +{ + GFile * file; + + if (!uri) + return; + + file = g_file_new_for_uri (uri); + + if (view->priv->expand_state) + g_hash_table_insert (view->priv->expand_state, file, file); + else + g_object_unref (file); +} + +static void +remove_expand_state (GeditFileBrowserView * view, + gchar const * uri) +{ + GFile * file; + + if (!uri) + return; + + file = g_file_new_for_uri (uri); + + if (view->priv->expand_state) + g_hash_table_remove (view->priv->expand_state, file); + + g_object_unref (file); +} + +static void +row_expanded (GtkTreeView * tree_view, + GtkTreeIter * iter, + GtkTreePath * path) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view); + gchar * uri; + + if (GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_expanded) + GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_expanded (tree_view, iter, path); + + if (!GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + return; + + if (view->priv->restore_expand_state) + { + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, + -1); + + add_expand_state (view, uri); + g_free (uri); + } + + _gedit_file_browser_store_iter_expanded (GEDIT_FILE_BROWSER_STORE (view->priv->model), + iter); +} + +static void +row_collapsed (GtkTreeView * tree_view, + GtkTreeIter * iter, + GtkTreePath * path) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view); + gchar * uri; + + if (GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_collapsed) + GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_collapsed (tree_view, iter, path); + + if (!GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + return; + + if (view->priv->restore_expand_state) + { + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, + -1); + + remove_expand_state (view, uri); + g_free (uri); + } + + _gedit_file_browser_store_iter_collapsed (GEDIT_FILE_BROWSER_STORE (view->priv->model), + iter); +} + +static gboolean +leave_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE && + view->priv->hover_path != NULL) { + gtk_tree_path_free (view->priv->hover_path); + view->priv->hover_path = NULL; + } + + // Chainup + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->leave_notify_event (widget, event); +} + +static gboolean +enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { + if (view->priv->hover_path != NULL) + gtk_tree_path_free (view->priv->hover_path); + + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &view->priv->hover_path, + NULL, NULL, NULL); + + if (view->priv->hover_path != NULL) + gdk_window_set_cursor (gtk_widget_get_window (widget), + view->priv->hand_cursor); + } + + // Chainup + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->enter_notify_event (widget, event); +} + +static gboolean +motion_notify_event (GtkWidget * widget, + GdkEventMotion * event) +{ + GtkTreePath *old_hover_path; + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { + old_hover_path = view->priv->hover_path; + gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, + &view->priv->hover_path, + NULL, NULL, NULL); + + if ((old_hover_path != NULL) != (view->priv->hover_path != NULL)) { + if (view->priv->hover_path != NULL) + gdk_window_set_cursor (gtk_widget_get_window (widget), + view->priv->hand_cursor); + else + gdk_window_set_cursor (gtk_widget_get_window (widget), + NULL); + } + + if (old_hover_path != NULL) + gtk_tree_path_free (old_hover_path); + } + + // Chainup + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->motion_notify_event (widget, event); +} + +static void +set_click_policy_property (GeditFileBrowserView *obj, + GeditFileBrowserViewClickPolicy click_policy) +{ + GtkTreeIter iter; + GdkDisplay *display; + GdkWindow *win; + + obj->priv->click_policy = click_policy; + + if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { + if (obj->priv->hand_cursor == NULL) + obj->priv->hand_cursor = gdk_cursor_new(GDK_HAND2); + } else if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE) { + if (obj->priv->hover_path != NULL) { + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (obj->priv->model), + &iter, obj->priv->hover_path)) + gtk_tree_model_row_changed (GTK_TREE_MODEL (obj->priv->model), + obj->priv->hover_path, &iter); + + gtk_tree_path_free (obj->priv->hover_path); + obj->priv->hover_path = NULL; + } + + if (GTK_WIDGET_REALIZED (GTK_WIDGET (obj))) { + win = gtk_widget_get_window (GTK_WIDGET (obj)); + gdk_window_set_cursor (win, NULL); + + display = gtk_widget_get_display (GTK_WIDGET (obj)); + + if (display != NULL) + gdk_display_flush (display); + } + + if (obj->priv->hand_cursor) { + gdk_cursor_unref (obj->priv->hand_cursor); + obj->priv->hand_cursor = NULL; + } + } +} + +static void +directory_activated (GeditFileBrowserView *view, + GtkTreeIter *iter) +{ + gedit_file_browser_store_set_virtual_root (GEDIT_FILE_BROWSER_STORE (view->priv->model), iter); +} + +static void +activate_selected_files (GeditFileBrowserView *view) { + GtkTreeView *tree_view = GTK_TREE_VIEW (view); + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + GList *rows, *row; + GtkTreePath *directory = NULL; + GtkTreePath *path; + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + + rows = gtk_tree_selection_get_selected_rows (selection, &view->priv->model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + /* Get iter from path */ + if (!gtk_tree_model_get_iter (view->priv->model, &iter, path)) + continue; + + gtk_tree_model_get (view->priv->model, &iter, GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1); + + if (FILE_IS_DIR (flags)) { + if (directory == NULL) + directory = path; + + } else if (!FILE_IS_DUMMY (flags)) { + g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter); + } + } + + if (directory != NULL) { + if (gtk_tree_model_get_iter (view->priv->model, &iter, directory)) + g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter); + } + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); +} + +static void +activate_selected_bookmark (GeditFileBrowserView *view) { + GtkTreeView *tree_view = GTK_TREE_VIEW (view); + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, &view->priv->model, &iter)) + g_signal_emit (view, signals[BOOKMARK_ACTIVATED], 0, &iter); +} + +static void +activate_selected_items (GeditFileBrowserView *view) +{ + if (GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + activate_selected_files (view); + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (view->priv->model)) + activate_selected_bookmark (view); +} + +static void +toggle_hidden_filter (GeditFileBrowserView *view) +{ + GeditFileBrowserStoreFilterMode mode; + + if (GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + { + mode = gedit_file_browser_store_get_filter_mode + (GEDIT_FILE_BROWSER_STORE (view->priv->model)); + mode ^= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN; + gedit_file_browser_store_set_filter_mode + (GEDIT_FILE_BROWSER_STORE (view->priv->model), mode); + } +} + +static gboolean +button_event_modifies_selection (GdkEventButton *event) +{ + return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0; +} + +static void +drag_begin (GtkWidget *widget, + GdkDragContext *context) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + view->priv->drag_button = 0; + view->priv->drag_started = TRUE; + + /* Chain up */ + GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->drag_begin (widget, context); +} + +static void +did_not_drag (GeditFileBrowserView *view, + GdkEventButton *event) +{ + GtkTreeView *tree_view; + GtkTreeSelection *selection; + GtkTreePath *path; + + tree_view = GTK_TREE_VIEW (view); + selection = gtk_tree_view_get_selection (tree_view); + + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) { + if ((view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) + && !button_event_modifies_selection(event) + && (event->button == 1 || event->button == 2)) { + /* Activate all selected items, and leave them selected */ + activate_selected_items (view); + } else if ((event->button == 1 || event->button == 2) + && ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0) + && view->priv->selected_on_button_down) { + if (!button_event_modifies_selection (event)) { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + } else { + gtk_tree_selection_unselect_path (selection, path); + } + } + + gtk_tree_path_free (path); + } +} + +static gboolean +button_release_event (GtkWidget *widget, + GdkEventButton *event) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + + if (event->button == view->priv->drag_button) { + view->priv->drag_button = 0; + + if (!view->priv->drag_started && + !view->priv->ignore_release) + did_not_drag (view, event); + } + + /* Chain up */ + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->button_release_event (widget, event); +} + +static gboolean +button_press_event (GtkWidget *widget, + GdkEventButton *event) +{ + int double_click_time; + static int click_count = 0; + static guint32 last_click_time = 0; + GeditFileBrowserView *view; + GtkTreeView *tree_view; + GtkTreeSelection *selection; + GtkTreePath *path; + int expander_size; + int horizontal_separator; + gboolean on_expander; + gboolean call_parent; + gboolean selected; + GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS(gedit_file_browser_view_parent_class); + + tree_view = GTK_TREE_VIEW (widget); + view = GEDIT_FILE_BROWSER_VIEW (widget); + selection = gtk_tree_view_get_selection (tree_view); + + /* Get double click time */ + g_object_get (G_OBJECT (gtk_widget_get_settings (widget)), + "gtk-double-click-time", &double_click_time, + NULL); + + /* Determine click count */ + if (event->time - last_click_time < double_click_time) + click_count++; + else + click_count = 0; + + last_click_time = event->time; + + /* Ignore double click if we are in single click mode */ + if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE && + click_count >= 2) { + return TRUE; + } + + view->priv->ignore_release = FALSE; + call_parent = TRUE; + + if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, + &path, NULL, NULL, NULL)) { + /* Keep track of path of last click so double clicks only happen + * on the same item */ + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) { + if (view->priv->double_click_path[1]) + gtk_tree_path_free (view->priv->double_click_path[1]); + + view->priv->double_click_path[1] = view->priv->double_click_path[0]; + view->priv->double_click_path[0] = gtk_tree_path_copy (path); + } + + if (event->type == GDK_2BUTTON_PRESS) { + if (view->priv->double_click_path[1] && + gtk_tree_path_compare (view->priv->double_click_path[0], view->priv->double_click_path[1]) == 0) + activate_selected_items (view); + + /* Chain up */ + widget_parent->button_press_event (widget, event); + } else { + /* We're going to filter out some situations where + * we can't let the default code run because all + * but one row would be would be deselected. We don't + * want that; we want the right click menu or single + * click to apply to everything that's currently selected. */ + selected = gtk_tree_selection_path_is_selected (selection, path); + + if (event->button == 3 && selected) + call_parent = FALSE; + + if ((event->button == 1 || event->button == 2) && + ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0)) { + gtk_widget_style_get (widget, + "expander-size", &expander_size, + "horizontal-separator", &horizontal_separator, + NULL); + on_expander = (event->x <= horizontal_separator / 2 + + gtk_tree_path_get_depth (path) * expander_size); + + view->priv->selected_on_button_down = selected; + + if (selected) { + call_parent = on_expander || gtk_tree_selection_count_selected_rows (selection) == 1; + view->priv->ignore_release = call_parent && view->priv->click_policy != GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE; + } else if ((event->state & GDK_CONTROL_MASK) != 0) { + call_parent = FALSE; + gtk_tree_selection_select_path (selection, path); + } else { + view->priv->ignore_release = on_expander; + } + } + + if (call_parent) { + /* Chain up */ + widget_parent->button_press_event (widget, event); + } else if (selected) { + gtk_widget_grab_focus (widget); + } + + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) { + view->priv->drag_started = FALSE; + view->priv->drag_button = event->button; + } + } + + gtk_tree_path_free (path); + } else { + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) { + if (view->priv->double_click_path[1]) + gtk_tree_path_free (view->priv->double_click_path[1]); + + view->priv->double_click_path[1] = view->priv->double_click_path[0]; + view->priv->double_click_path[0] = NULL; + } + + gtk_tree_selection_unselect_all (selection); + /* Chain up */ + widget_parent->button_press_event (widget, event); + } + + /* We already chained up if nescessary, so just return TRUE */ + return TRUE; +} + +static gboolean +key_press_event (GtkWidget *widget, + GdkEventKey *event) +{ + GeditFileBrowserView *view; + guint modifiers; + gboolean handled; + + view = GEDIT_FILE_BROWSER_VIEW (widget); + handled = FALSE; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + switch (event->keyval) { + case GDK_space: + if (event->state & GDK_CONTROL_MASK) { + handled = FALSE; + break; + } + if (!GTK_WIDGET_HAS_FOCUS (widget)) { + handled = FALSE; + break; + } + + activate_selected_items (view); + handled = TRUE; + break; + + case GDK_Return: + case GDK_KP_Enter: + activate_selected_items (view); + handled = TRUE; + break; + + case GDK_h: + if ((event->state & modifiers) == GDK_CONTROL_MASK) { + toggle_hidden_filter (view); + handled = TRUE; + break; + } + + default: + handled = FALSE; + } + + /* Chain up */ + if (!handled) + return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->key_press_event (widget, event); + + return TRUE; +} + +static void +fill_expand_state (GeditFileBrowserView * view, GtkTreeIter * iter) +{ + GtkTreePath * path; + GtkTreeIter child; + gchar * uri; + + if (!gtk_tree_model_iter_has_child (view->priv->model, iter)) + return; + + path = gtk_tree_model_get_path (view->priv->model, iter); + + if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (view), path)) + { + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, + -1); + + add_expand_state (view, uri); + g_free (uri); + } + + if (gtk_tree_model_iter_children (view->priv->model, &child, iter)) + { + do { + fill_expand_state (view, &child); + } while (gtk_tree_model_iter_next (view->priv->model, &child)); + } + + gtk_tree_path_free (path); +} + +static void +uninstall_restore_signals (GeditFileBrowserView * tree_view, + GtkTreeModel * model) +{ + g_signal_handlers_disconnect_by_func (model, + on_begin_refresh, + tree_view); + + g_signal_handlers_disconnect_by_func (model, + on_end_refresh, + tree_view); + + g_signal_handlers_disconnect_by_func (model, + on_unload, + tree_view); + + g_signal_handlers_disconnect_by_func (model, + on_row_inserted, + tree_view); +} + +static void +install_restore_signals (GeditFileBrowserView * tree_view, + GtkTreeModel * model) +{ + g_signal_connect (model, + "begin-refresh", + G_CALLBACK (on_begin_refresh), + tree_view); + + g_signal_connect (model, + "end-refresh", + G_CALLBACK (on_end_refresh), + tree_view); + + g_signal_connect (model, + "unload", + G_CALLBACK (on_unload), + tree_view); + + g_signal_connect_after (model, + "row-inserted", + G_CALLBACK (on_row_inserted), + tree_view); +} + +static void +set_restore_expand_state (GeditFileBrowserView * view, + gboolean state) +{ + if (state == view->priv->restore_expand_state) + return; + + if (view->priv->expand_state) + { + g_hash_table_destroy (view->priv->expand_state); + view->priv->expand_state = NULL; + } + + if (state) + { + view->priv->expand_state = g_hash_table_new_full (g_file_hash, + (GEqualFunc)g_file_equal, + g_object_unref, + NULL); + + if (view->priv->model && GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + { + fill_expand_state (view, NULL); + + install_restore_signals (view, view->priv->model); + } + } + else if (view->priv->model && GEDIT_IS_FILE_BROWSER_STORE (view->priv->model)) + { + uninstall_restore_signals (view, view->priv->model); + } + + view->priv->restore_expand_state = state; +} + +static void +get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object); + + switch (prop_id) + { + case PROP_CLICK_POLICY: + g_value_set_enum (value, obj->priv->click_policy); + break; + case PROP_RESTORE_EXPAND_STATE: + g_value_set_boolean (value, obj->priv->restore_expand_state); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object); + + switch (prop_id) + { + case PROP_CLICK_POLICY: + set_click_policy_property (obj, g_value_get_enum (value)); + break; + case PROP_RESTORE_EXPAND_STATE: + set_restore_expand_state (obj, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_view_class_init (GeditFileBrowserViewClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gedit_file_browser_view_finalize; + object_class->get_property = get_property; + object_class->set_property = set_property; + + /* Event handlers */ + widget_class->motion_notify_event = motion_notify_event; + widget_class->enter_notify_event = enter_notify_event; + widget_class->leave_notify_event = leave_notify_event; + widget_class->button_press_event = button_press_event; + widget_class->button_release_event = button_release_event; + widget_class->drag_begin = drag_begin; + widget_class->key_press_event = key_press_event; + + /* Tree view handlers */ + tree_view_class->row_expanded = row_expanded; + tree_view_class->row_collapsed = row_collapsed; + + /* Default handlers */ + klass->directory_activated = directory_activated; + + g_object_class_install_property (object_class, PROP_CLICK_POLICY, + g_param_spec_enum ("click-policy", + "Click Policy", + "The click policy", + GEDIT_TYPE_FILE_BROWSER_VIEW_CLICK_POLICY, + GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_RESTORE_EXPAND_STATE, + g_param_spec_boolean ("restore-expand-state", + "Restore Expand State", + "Restore expanded state of loaded directories", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + signals[ERROR] = + g_signal_new ("error", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserViewClass, + error), NULL, NULL, + gedit_file_browser_marshal_VOID__UINT_STRING, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + signals[FILE_ACTIVATED] = + g_signal_new ("file-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserViewClass, + file_activated), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); + signals[DIRECTORY_ACTIVATED] = + g_signal_new ("directory-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserViewClass, + directory_activated), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); + signals[BOOKMARK_ACTIVATED] = + g_signal_new ("bookmark-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserViewClass, + bookmark_activated), NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); + + g_type_class_add_private (object_class, + sizeof (GeditFileBrowserViewPrivate)); +} + +static void +cell_data_cb (GtkTreeViewColumn * tree_column, GtkCellRenderer * cell, + GtkTreeModel * tree_model, GtkTreeIter * iter, + GeditFileBrowserView * obj) +{ + GtkTreePath *path; + PangoUnderline underline = PANGO_UNDERLINE_NONE; + gboolean editable = FALSE; + + path = gtk_tree_model_get_path (tree_model, iter); + + if (obj->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) { + if (obj->priv->hover_path != NULL && + gtk_tree_path_compare (path, obj->priv->hover_path) == 0) + underline = PANGO_UNDERLINE_SINGLE; + } + + if (GEDIT_IS_FILE_BROWSER_STORE (tree_model)) + { + if (obj->priv->editable != NULL && + gtk_tree_row_reference_valid (obj->priv->editable)) + { + GtkTreePath *edpath = gtk_tree_row_reference_get_path (obj->priv->editable); + + editable = edpath && gtk_tree_path_compare (path, edpath) == 0; + } + } + + gtk_tree_path_free (path); + g_object_set (cell, "editable", editable, "underline", underline, NULL); +} + +static void +gedit_file_browser_view_init (GeditFileBrowserView * obj) +{ + obj->priv = GEDIT_FILE_BROWSER_VIEW_GET_PRIVATE (obj); + + obj->priv->column = gtk_tree_view_column_new (); + + obj->priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (obj->priv->column, + obj->priv->pixbuf_renderer, + FALSE); + gtk_tree_view_column_add_attribute (obj->priv->column, + obj->priv->pixbuf_renderer, + "pixbuf", + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON); + + obj->priv->text_renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (obj->priv->column, + obj->priv->text_renderer, TRUE); + gtk_tree_view_column_add_attribute (obj->priv->column, + obj->priv->text_renderer, + "text", + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME); + + g_signal_connect (obj->priv->text_renderer, "edited", + G_CALLBACK (on_cell_edited), obj); + + gtk_tree_view_append_column (GTK_TREE_VIEW (obj), + obj->priv->column); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (obj), FALSE); + + gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (obj), + GDK_BUTTON1_MASK, + drag_source_targets, + G_N_ELEMENTS (drag_source_targets), + GDK_ACTION_COPY); + + obj->priv->busy_cursor = gdk_cursor_new (GDK_WATCH); +} + +static gboolean +bookmarks_separator_func (GtkTreeModel * model, GtkTreeIter * iter, + gpointer user_data) +{ + guint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + &flags, -1); + + return (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR); +} + +/* Public */ +GtkWidget * +gedit_file_browser_view_new (void) +{ + GeditFileBrowserView *obj = + GEDIT_FILE_BROWSER_VIEW (g_object_new + (GEDIT_TYPE_FILE_BROWSER_VIEW, NULL)); + + return GTK_WIDGET (obj); +} + +void +gedit_file_browser_view_set_model (GeditFileBrowserView * tree_view, + GtkTreeModel * model) +{ + GtkTreeSelection *selection; + + if (tree_view->priv->model == model) + return; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + + if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) { + gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW + (tree_view), + bookmarks_separator_func, + NULL, NULL); + gtk_tree_view_column_set_cell_data_func (tree_view->priv-> + column, + tree_view->priv-> + text_renderer, + (GtkTreeCellDataFunc) + cell_data_cb, + tree_view, NULL); + } else { + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW + (tree_view), NULL, + NULL, NULL); + gtk_tree_view_column_set_cell_data_func (tree_view->priv-> + column, + tree_view->priv-> + text_renderer, + (GtkTreeCellDataFunc) + cell_data_cb, + tree_view, NULL); + + if (tree_view->priv->restore_expand_state) + install_restore_signals (tree_view, model); + + } + + if (tree_view->priv->hover_path != NULL) { + gtk_tree_path_free (tree_view->priv->hover_path); + tree_view->priv->hover_path = NULL; + } + + if (GEDIT_IS_FILE_BROWSER_STORE (tree_view->priv->model)) { + if (tree_view->priv->restore_expand_state) + uninstall_restore_signals (tree_view, + tree_view->priv->model); + } + + tree_view->priv->model = model; + gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model); +} + +void +gedit_file_browser_view_start_rename (GeditFileBrowserView * tree_view, + GtkTreeIter * iter) +{ + guint flags; + GtkTreeRowReference *rowref; + GtkTreePath *path; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view)); + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE + (tree_view->priv->model)); + g_return_if_fail (iter != NULL); + + gtk_tree_model_get (tree_view->priv->model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags))) + return; + + path = gtk_tree_model_get_path (tree_view->priv->model, iter); + rowref = gtk_tree_row_reference_new (tree_view->priv->model, path); + + /* Start editing */ + gtk_widget_grab_focus (GTK_WIDGET (tree_view)); + + if (gtk_tree_path_up (path)) + gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree_view), + path); + + gtk_tree_path_free (path); + tree_view->priv->editable = rowref; + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), + gtk_tree_row_reference_get_path (tree_view->priv->editable), + tree_view->priv->column, TRUE); + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), + gtk_tree_row_reference_get_path (tree_view->priv->editable), + tree_view->priv->column, + FALSE, 0.0, 0.0); +} + +void +gedit_file_browser_view_set_click_policy (GeditFileBrowserView *tree_view, + GeditFileBrowserViewClickPolicy policy) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view)); + + set_click_policy_property (tree_view, policy); + + g_object_notify (G_OBJECT (tree_view), "click-policy"); +} + +void +gedit_file_browser_view_set_restore_expand_state (GeditFileBrowserView * tree_view, + gboolean restore_expand_state) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view)); + + set_restore_expand_state (tree_view, restore_expand_state); + g_object_notify (G_OBJECT (tree_view), "restore-expand-state"); +} + +/* Signal handlers */ +static void +on_cell_edited (GtkCellRendererText * cell, gchar * path, gchar * new_text, + GeditFileBrowserView * tree_view) +{ + GtkTreePath * treepath; + GtkTreeIter iter; + gboolean ret; + GError * error = NULL; + + gtk_tree_row_reference_free (tree_view->priv->editable); + tree_view->priv->editable = NULL; + + if (new_text == NULL || *new_text == '\0') + return; + + treepath = gtk_tree_path_new_from_string (path); + ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath); + gtk_tree_path_free (treepath); + + if (ret) { + if (gedit_file_browser_store_rename (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model), + &iter, new_text, &error)) { + treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (tree_view->priv->model), &iter); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), + treepath, NULL, + FALSE, 0.0, 0.0); + gtk_tree_path_free (treepath); + } + else { + if (error) { + g_signal_emit (tree_view, signals[ERROR], 0, + error->code, error->message); + g_error_free (error); + } + } + } +} + +static void +on_begin_refresh (GeditFileBrowserStore * model, + GeditFileBrowserView * view) +{ + /* Store the refresh state, so we can handle unloading of nodes while + refreshing properly */ + view->priv->is_refresh = TRUE; +} + +static void +on_end_refresh (GeditFileBrowserStore * model, + GeditFileBrowserView * view) +{ + /* Store the refresh state, so we can handle unloading of nodes while + refreshing properly */ + view->priv->is_refresh = FALSE; +} + +static void +on_unload (GeditFileBrowserStore * model, + gchar const * uri, + GeditFileBrowserView * view) +{ + /* Don't remove the expand state if we are refreshing */ + if (!view->priv->restore_expand_state || view->priv->is_refresh) + return; + + remove_expand_state (view, uri); +} + +static void +restore_expand_state (GeditFileBrowserView * view, + GeditFileBrowserStore * model, + GtkTreeIter * iter) +{ + gchar * uri; + GFile * file; + GtkTreePath * path; + + gtk_tree_model_get (GTK_TREE_MODEL (model), + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, + -1); + + if (!uri) + return; + + file = g_file_new_for_uri (uri); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + + if (g_hash_table_lookup (view->priv->expand_state, file)) + { + gtk_tree_view_expand_row (GTK_TREE_VIEW (view), + path, + FALSE); + } + + gtk_tree_path_free (path); + + g_object_unref (file); + g_free (uri); +} + +static void +on_row_inserted (GeditFileBrowserStore * model, + GtkTreePath * path, + GtkTreeIter * iter, + GeditFileBrowserView * view) +{ + GtkTreeIter parent; + GtkTreePath * copy; + + if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (model), iter)) + restore_expand_state (view, model, iter); + + copy = gtk_tree_path_copy (path); + + if (gtk_tree_path_up (copy) && + (gtk_tree_path_get_depth (copy) != 0) && + gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &parent, copy)) + { + restore_expand_state (view, model, &parent); + } + + gtk_tree_path_free (copy); +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-view.h b/plugins/filebrowser/gedit-file-browser-view.h new file mode 100755 index 00000000..a5ada255 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-view.h @@ -0,0 +1,84 @@ +/* + * gedit-file-browser-view.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GEDIT_FILE_BROWSER_VIEW_H__ +#define __GEDIT_FILE_BROWSER_VIEW_H__ + +#include + +G_BEGIN_DECLS +#define GEDIT_TYPE_FILE_BROWSER_VIEW (gedit_file_browser_view_get_type ()) +#define GEDIT_FILE_BROWSER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserView)) +#define GEDIT_FILE_BROWSER_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserView const)) +#define GEDIT_FILE_BROWSER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewClass)) +#define GEDIT_IS_FILE_BROWSER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW)) +#define GEDIT_IS_FILE_BROWSER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_VIEW)) +#define GEDIT_FILE_BROWSER_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewClass)) + +typedef struct _GeditFileBrowserView GeditFileBrowserView; +typedef struct _GeditFileBrowserViewClass GeditFileBrowserViewClass; +typedef struct _GeditFileBrowserViewPrivate GeditFileBrowserViewPrivate; + +typedef enum { + GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE, + GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE +} GeditFileBrowserViewClickPolicy; + +struct _GeditFileBrowserView +{ + GtkTreeView parent; + + GeditFileBrowserViewPrivate *priv; +}; + +struct _GeditFileBrowserViewClass +{ + GtkTreeViewClass parent_class; + + /* Signals */ + void (*error) (GeditFileBrowserView * filetree, + guint code, + gchar const *message); + void (*file_activated) (GeditFileBrowserView * filetree, + GtkTreeIter *iter); + void (*directory_activated) (GeditFileBrowserView * filetree, + GtkTreeIter *iter); + void (*bookmark_activated) (GeditFileBrowserView * filetree, + GtkTreeIter *iter); +}; + +GType gedit_file_browser_view_get_type (void) G_GNUC_CONST; +GType gedit_file_browser_view_register_type (GTypeModule * module); + +GtkWidget *gedit_file_browser_view_new (void); +void gedit_file_browser_view_set_model (GeditFileBrowserView * tree_view, + GtkTreeModel * model); +void gedit_file_browser_view_start_rename (GeditFileBrowserView * tree_view, + GtkTreeIter * iter); +void gedit_file_browser_view_set_click_policy (GeditFileBrowserView * tree_view, + GeditFileBrowserViewClickPolicy policy); +void gedit_file_browser_view_set_restore_expand_state (GeditFileBrowserView * tree_view, + gboolean restore_expand_state); + +G_END_DECLS +#endif /* __GEDIT_FILE_BROWSER_VIEW_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-widget-ui.xml b/plugins/filebrowser/gedit-file-browser-widget-ui.xml new file mode 100755 index 00000000..472fd185 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget-ui.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/filebrowser/gedit-file-browser-widget.c b/plugins/filebrowser/gedit-file-browser-widget.c new file mode 100755 index 00000000..e8a73cce --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget.c @@ -0,0 +1,3143 @@ +/* + * gedit-file-browser-widget.c - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gedit-file-browser-utils.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-widget.h" +#include "gedit-file-browser-view.h" +#include "gedit-file-browser-store.h" +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-marshal.h" +#include "gedit-file-browser-enum-types.h" + +#define GEDIT_FILE_BROWSER_WIDGET_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GEDIT_TYPE_FILE_BROWSER_WIDGET, \ + GeditFileBrowserWidgetPrivate)) + +#define XML_UI_FILE "gedit-file-browser-widget-ui.xml" +#define LOCATION_DATA_KEY "gedit-file-browser-widget-location" + +enum +{ + BOOKMARKS_ID, + SEPARATOR_CUSTOM_ID, + SEPARATOR_ID, + PATH_ID, + NUM_DEFAULT_IDS +}; + +enum +{ + COLUMN_INDENT, + COLUMN_ICON, + COLUMN_NAME, + COLUMN_FILE, + COLUMN_ID, + N_COLUMNS +}; + +/* Properties */ +enum +{ + PROP_0, + + PROP_FILTER_PATTERN, + PROP_ENABLE_DELETE +}; + +/* Signals */ +enum +{ + URI_ACTIVATED, + ERROR, + CONFIRM_DELETE, + CONFIRM_NO_TRASH, + NUM_SIGNALS +}; + +static guint signals[NUM_SIGNALS] = { 0 }; + +typedef struct _SignalNode +{ + GObject *object; + gulong id; +} SignalNode; + +typedef struct +{ + gulong id; + GeditFileBrowserWidgetFilterFunc func; + gpointer user_data; + GDestroyNotify destroy_notify; +} FilterFunc; + +typedef struct +{ + GFile *root; + GFile *virtual_root; +} Location; + +typedef struct +{ + gchar *name; + GdkPixbuf *icon; +} NameIcon; + +struct _GeditFileBrowserWidgetPrivate +{ + GeditFileBrowserView *treeview; + GeditFileBrowserStore *file_store; + GeditFileBookmarksStore *bookmarks_store; + + GHashTable *bookmarks_hash; + + GtkWidget *combo; + GtkTreeStore *combo_model; + + GtkWidget *filter_expander; + GtkWidget *filter_entry; + + GtkUIManager *manager; + GtkActionGroup *action_group; + GtkActionGroup *action_group_selection; + GtkActionGroup *action_group_file_selection; + GtkActionGroup *action_group_single_selection; + GtkActionGroup *action_group_single_most_selection; + GtkActionGroup *action_group_sensitive; + GtkActionGroup *bookmark_action_group; + + GSList *signal_pool; + + GSList *filter_funcs; + gulong filter_id; + gulong glob_filter_id; + GPatternSpec *filter_pattern; + gchar *filter_pattern_str; + + GList *locations; + GList *current_location; + gboolean changing_location; + GtkWidget *location_previous_menu; + GtkWidget *location_next_menu; + GtkWidget *current_location_menu_item; + + gboolean enable_delete; + + GCancellable *cancellable; + + GdkCursor *busy_cursor; +}; + +static void set_enable_delete (GeditFileBrowserWidget *obj, + gboolean enable); +static void on_model_set (GObject * gobject, + GParamSpec * arg1, + GeditFileBrowserWidget * obj); +static void on_treeview_error (GeditFileBrowserView * tree_view, + guint code, + gchar * message, + GeditFileBrowserWidget * obj); +static void on_file_store_error (GeditFileBrowserStore * store, + guint code, + gchar * message, + GeditFileBrowserWidget * obj); +static gboolean on_file_store_no_trash (GeditFileBrowserStore * store, + GList * files, + GeditFileBrowserWidget * obj); +static void on_combo_changed (GtkComboBox * combo, + GeditFileBrowserWidget * obj); +static gboolean on_treeview_popup_menu (GeditFileBrowserView * treeview, + GeditFileBrowserWidget * obj); +static gboolean on_treeview_button_press_event (GeditFileBrowserView * treeview, + GdkEventButton * event, + GeditFileBrowserWidget * obj); +static gboolean on_treeview_key_press_event (GeditFileBrowserView * treeview, + GdkEventKey * event, + GeditFileBrowserWidget * obj); +static void on_selection_changed (GtkTreeSelection * selection, + GeditFileBrowserWidget * obj); + +static void on_virtual_root_changed (GeditFileBrowserStore * model, + GParamSpec *param, + GeditFileBrowserWidget * obj); + +static gboolean on_entry_filter_activate (GeditFileBrowserWidget * obj); +static void on_location_jump_activate (GtkMenuItem * item, + GeditFileBrowserWidget * obj); +static void on_bookmarks_row_changed (GtkTreeModel * model, + GtkTreePath * path, + GtkTreeIter * iter, + GeditFileBrowserWidget * obj); +static void on_bookmarks_row_deleted (GtkTreeModel * model, + GtkTreePath * path, + GeditFileBrowserWidget * obj); +static void on_filter_mode_changed (GeditFileBrowserStore * model, + GParamSpec * param, + GeditFileBrowserWidget * obj); +static void on_action_directory_previous (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_next (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_up (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_new (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_open (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_new (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_rename (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_delete (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_file_move_to_trash (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_refresh (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_directory_open (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_filter_hidden (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_filter_binary (GtkAction * action, + GeditFileBrowserWidget * obj); +static void on_action_bookmark_open (GtkAction * action, + GeditFileBrowserWidget * obj); + +GEDIT_PLUGIN_DEFINE_TYPE (GeditFileBrowserWidget, gedit_file_browser_widget, + GTK_TYPE_VBOX) + +static void +free_name_icon (gpointer data) +{ + NameIcon * item; + + if (data == NULL) + return; + + item = (NameIcon *)(data); + + g_free (item->name); + + if (item->icon) + g_object_unref (item->icon); + + g_free (item); +} + +static FilterFunc * +filter_func_new (GeditFileBrowserWidget * obj, + GeditFileBrowserWidgetFilterFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + FilterFunc *result; + + result = g_new (FilterFunc, 1); + + result->id = ++obj->priv->filter_id; + result->func = func; + result->user_data = user_data; + result->destroy_notify = notify; + return result; +} + +static void +location_free (Location * loc) +{ + if (loc->root) + g_object_unref (loc->root); + + if (loc->virtual_root) + g_object_unref (loc->virtual_root); + + g_free (loc); +} + +static gboolean +combo_find_by_id (GeditFileBrowserWidget * obj, guint id, + GtkTreeIter * iter) +{ + guint checkid; + GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->combo_model); + + if (iter == NULL) + return FALSE; + + if (gtk_tree_model_get_iter_first (model, iter)) { + do { + gtk_tree_model_get (model, iter, COLUMN_ID, + &checkid, -1); + + if (checkid == id) + return TRUE; + } while (gtk_tree_model_iter_next (model, iter)); + } + + return FALSE; +} + +static void +remove_path_items (GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + + while (combo_find_by_id (obj, PATH_ID, &iter)) + gtk_tree_store_remove (obj->priv->combo_model, &iter); +} + +static void +cancel_async_operation (GeditFileBrowserWidget *widget) +{ + if (!widget->priv->cancellable) + return; + + g_cancellable_cancel (widget->priv->cancellable); + g_object_unref (widget->priv->cancellable); + + widget->priv->cancellable = NULL; +} + +static void +gedit_file_browser_widget_finalize (GObject * object) +{ + GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object); + GList *loc; + + remove_path_items (obj); + gedit_file_browser_store_set_filter_func (obj->priv->file_store, + NULL, NULL); + + g_object_unref (obj->priv->manager); + g_object_unref (obj->priv->file_store); + g_object_unref (obj->priv->bookmarks_store); + g_object_unref (obj->priv->combo_model); + + g_slist_foreach (obj->priv->filter_funcs, (GFunc) g_free, NULL); + g_slist_free (obj->priv->filter_funcs); + + for (loc = obj->priv->locations; loc; loc = loc->next) + location_free ((Location *) (loc->data)); + + if (obj->priv->current_location_menu_item) + g_object_unref (obj->priv->current_location_menu_item); + + g_list_free (obj->priv->locations); + + g_hash_table_destroy (obj->priv->bookmarks_hash); + + cancel_async_operation (obj); + + gdk_cursor_unref (obj->priv->busy_cursor); + + G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->finalize (object); +} + +static void +gedit_file_browser_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object); + + switch (prop_id) + { + case PROP_FILTER_PATTERN: + g_value_set_string (value, obj->priv->filter_pattern_str); + break; + case PROP_ENABLE_DELETE: + g_value_set_boolean (value, obj->priv->enable_delete); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object); + + switch (prop_id) + { + case PROP_FILTER_PATTERN: + gedit_file_browser_widget_set_filter_pattern (obj, + g_value_get_string (value)); + break; + case PROP_ENABLE_DELETE: + set_enable_delete (obj, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_widget_class_init (GeditFileBrowserWidgetClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gedit_file_browser_widget_finalize; + + object_class->get_property = gedit_file_browser_widget_get_property; + object_class->set_property = gedit_file_browser_widget_set_property; + + g_object_class_install_property (object_class, PROP_FILTER_PATTERN, + g_param_spec_string ("filter-pattern", + "Filter Pattern", + "The filter pattern", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, PROP_ENABLE_DELETE, + g_param_spec_boolean ("enable-delete", + "Enable delete", + "Enable permanently deleting items", + TRUE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + signals[URI_ACTIVATED] = + g_signal_new ("uri-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, + uri_activated), NULL, NULL, + g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, + G_TYPE_STRING); + signals[ERROR] = + g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, + error), NULL, NULL, + gedit_file_browser_marshal_VOID__UINT_STRING, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING); + + signals[CONFIRM_DELETE] = + g_signal_new ("confirm-delete", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, + confirm_delete), + g_signal_accumulator_true_handled, + NULL, + gedit_file_browser_marshal_BOOL__OBJECT_POINTER, + G_TYPE_BOOLEAN, + 2, + G_TYPE_OBJECT, + G_TYPE_POINTER); + + signals[CONFIRM_NO_TRASH] = + g_signal_new ("confirm-no-trash", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, + confirm_no_trash), + g_signal_accumulator_true_handled, + NULL, + gedit_file_browser_marshal_BOOL__POINTER, + G_TYPE_BOOLEAN, + 1, + G_TYPE_POINTER); + + g_type_class_add_private (object_class, + sizeof (GeditFileBrowserWidgetPrivate)); +} + +static void +add_signal (GeditFileBrowserWidget * obj, gpointer object, gulong id) +{ + SignalNode *node = g_new (SignalNode, 1); + + node->object = G_OBJECT (object); + node->id = id; + + obj->priv->signal_pool = + g_slist_prepend (obj->priv->signal_pool, node); +} + +static void +clear_signals (GeditFileBrowserWidget * obj) +{ + GSList *item; + SignalNode *node; + + for (item = obj->priv->signal_pool; item; item = item->next) { + node = (SignalNode *) (item->data); + + g_signal_handler_disconnect (node->object, node->id); + g_free (item->data); + } + + g_slist_free (obj->priv->signal_pool); + obj->priv->signal_pool = NULL; +} + +static gboolean +separator_func (GtkTreeModel * model, GtkTreeIter * iter, gpointer data) +{ + guint id; + + gtk_tree_model_get (model, iter, COLUMN_ID, &id, -1); + + return (id == SEPARATOR_ID); +} + +static gboolean +get_from_bookmark_file (GeditFileBrowserWidget * obj, GFile * file, + gchar ** name, GdkPixbuf ** icon) +{ + gpointer data; + NameIcon * item; + + data = g_hash_table_lookup (obj->priv->bookmarks_hash, file); + + if (data == NULL) + return FALSE; + + item = (NameIcon *)data; + + *name = g_strdup (item->name); + *icon = item->icon; + + if (item->icon != NULL) + { + g_object_ref (item->icon); + } + + return TRUE; +} + +static void +insert_path_item (GeditFileBrowserWidget * obj, + GFile * file, + GtkTreeIter * after, + GtkTreeIter * iter, + guint indent) +{ + gchar * unescape; + GdkPixbuf * icon = NULL; + + /* Try to get the icon and name from the bookmarks hash */ + if (!get_from_bookmark_file (obj, file, &unescape, &icon)) { + /* It's not a bookmark, fetch the name and the icon ourselves */ + unescape = gedit_file_browser_utils_file_basename (file); + + /* Get the icon */ + icon = gedit_file_browser_utils_pixbuf_from_file (file, GTK_ICON_SIZE_MENU); + } + + gtk_tree_store_insert_after (obj->priv->combo_model, iter, NULL, + after); + + gtk_tree_store_set (obj->priv->combo_model, + iter, + COLUMN_INDENT, indent, + COLUMN_ICON, icon, + COLUMN_NAME, unescape, + COLUMN_FILE, file, + COLUMN_ID, PATH_ID, + -1); + + if (icon) + g_object_unref (icon); + + g_free (unescape); +} + +static void +insert_separator_item (GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + + gtk_tree_store_insert (obj->priv->combo_model, &iter, NULL, 1); + gtk_tree_store_set (obj->priv->combo_model, &iter, + COLUMN_ICON, NULL, + COLUMN_NAME, NULL, + COLUMN_ID, SEPARATOR_ID, -1); +} + +static void +combo_set_active_by_id (GeditFileBrowserWidget * obj, guint id) +{ + GtkTreeIter iter; + + if (combo_find_by_id (obj, id, &iter)) + gtk_combo_box_set_active_iter (GTK_COMBO_BOX + (obj->priv->combo), &iter); +} + +static guint +uri_num_parents (GFile * from, GFile * to) +{ + /* Determine the number of 'levels' to get from #from to #to. */ + guint parents = 0; + GFile * parent; + + if (from == NULL) + return 0; + + g_object_ref (from); + + while ((parent = g_file_get_parent (from)) && !(to && g_file_equal (from, to))) { + g_object_unref (from); + from = parent; + + ++parents; + } + + g_object_unref (from); + return parents; +} + +static void +insert_location_path (GeditFileBrowserWidget * obj) +{ + Location *loc; + GFile *current = NULL; + GFile * tmp; + GtkTreeIter separator; + GtkTreeIter iter; + guint indent; + + if (!obj->priv->current_location) { + g_message ("insert_location_path: no current location"); + return; + } + + loc = (Location *) (obj->priv->current_location->data); + + current = loc->virtual_root; + combo_find_by_id (obj, SEPARATOR_ID, &separator); + + indent = uri_num_parents (loc->virtual_root, loc->root); + + while (current != NULL) { + insert_path_item (obj, current, &separator, &iter, indent--); + + if (current == loc->virtual_root) { + g_signal_handlers_block_by_func (obj->priv->combo, + on_combo_changed, + obj); + gtk_combo_box_set_active_iter (GTK_COMBO_BOX + (obj->priv->combo), + &iter); + g_signal_handlers_unblock_by_func (obj->priv-> + combo, + on_combo_changed, + obj); + } + + if (g_file_equal (current, loc->root) || !gedit_utils_file_has_parent (current)) { + if (current != loc->virtual_root) + g_object_unref (current); + break; + } + + tmp = g_file_get_parent (current); + + if (current != loc->virtual_root) + g_object_unref (current); + + current = tmp; + } +} + +static void +check_current_item (GeditFileBrowserWidget * obj, gboolean show_path) +{ + GtkTreeIter separator; + gboolean has_sep; + + remove_path_items (obj); + has_sep = combo_find_by_id (obj, SEPARATOR_ID, &separator); + + if (show_path) { + if (!has_sep) + insert_separator_item (obj); + + insert_location_path (obj); + } else if (has_sep) + gtk_tree_store_remove (obj->priv->combo_model, &separator); +} + +static void +fill_combo_model (GeditFileBrowserWidget * obj) +{ + GtkTreeStore *store = obj->priv->combo_model; + GtkTreeIter iter; + GdkPixbuf *icon; + + icon = gedit_file_browser_utils_pixbuf_from_theme (GTK_STOCK_HOME, GTK_ICON_SIZE_MENU); + + gtk_tree_store_append (store, &iter, NULL); + gtk_tree_store_set (store, &iter, + COLUMN_ICON, icon, + COLUMN_NAME, _("Bookmarks"), + COLUMN_ID, BOOKMARKS_ID, -1); + g_object_unref (icon); + + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (obj->priv->combo), + separator_func, obj, NULL); + gtk_combo_box_set_active (GTK_COMBO_BOX (obj->priv->combo), 0); +} + +static void +indent_cell_data_func (GtkCellLayout * cell_layout, + GtkCellRenderer * cell, + GtkTreeModel * model, + GtkTreeIter * iter, + gpointer data) +{ + gchar * indent; + guint num; + + gtk_tree_model_get (model, iter, COLUMN_INDENT, &num, -1); + + if (num == 0) + g_object_set (cell, "text", "", NULL); + else { + indent = g_strnfill (num * 2, ' '); + + g_object_set (cell, "text", indent, NULL); + g_free (indent); + } +} + +static void +create_combo (GeditFileBrowserWidget * obj) +{ + GtkCellRenderer *renderer; + + obj->priv->combo_model = gtk_tree_store_new (N_COLUMNS, + G_TYPE_UINT, + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_FILE, + G_TYPE_UINT); + obj->priv->combo = + gtk_combo_box_new_with_model (GTK_TREE_MODEL + (obj->priv->combo_model)); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, FALSE); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT + (obj->priv->combo), renderer, + indent_cell_data_func, obj, NULL); + + + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, FALSE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, "pixbuf", COLUMN_ICON); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (obj->priv->combo), + renderer, "text", COLUMN_NAME); + + g_object_set (renderer, "ellipsize-set", TRUE, + "ellipsize", PANGO_ELLIPSIZE_END, NULL); + + gtk_box_pack_start (GTK_BOX (obj), GTK_WIDGET (obj->priv->combo), + FALSE, FALSE, 0); + + fill_combo_model (obj); + g_signal_connect (obj->priv->combo, "changed", + G_CALLBACK (on_combo_changed), obj); + + gtk_widget_show (obj->priv->combo); +} + +static GtkActionEntry toplevel_actions[] = +{ + {"FilterMenuAction", NULL, N_("_Filter")} +}; + +static const GtkActionEntry tree_actions_selection[] = +{ + {"FileMoveToTrash", "mate-stock-trash", N_("_Move to Trash"), NULL, + N_("Move selected file or folder to trash"), + G_CALLBACK (on_action_file_move_to_trash)}, + {"FileDelete", GTK_STOCK_DELETE, N_("_Delete"), NULL, + N_("Delete selected file or folder"), + G_CALLBACK (on_action_file_delete)} +}; + +static const GtkActionEntry tree_actions_file_selection[] = +{ + {"FileOpen", GTK_STOCK_OPEN, NULL, NULL, + N_("Open selected file"), + G_CALLBACK (on_action_file_open)} +}; + +static const GtkActionEntry tree_actions[] = +{ + {"DirectoryUp", GTK_STOCK_GO_UP, N_("Up"), NULL, + N_("Open the parent folder"), G_CALLBACK (on_action_directory_up)} +}; + +static const GtkActionEntry tree_actions_single_most_selection[] = +{ + {"DirectoryNew", GTK_STOCK_ADD, N_("_New Folder"), NULL, + N_("Add new empty folder"), + G_CALLBACK (on_action_directory_new)}, + {"FileNew", GTK_STOCK_NEW, N_("New F_ile"), NULL, + N_("Add new empty file"), G_CALLBACK (on_action_file_new)} +}; + +static const GtkActionEntry tree_actions_single_selection[] = +{ + {"FileRename", NULL, N_("_Rename"), NULL, + N_("Rename selected file or folder"), + G_CALLBACK (on_action_file_rename)} +}; + +static const GtkActionEntry tree_actions_sensitive[] = +{ + {"DirectoryPrevious", GTK_STOCK_GO_BACK, N_("_Previous Location"), + NULL, + N_("Go to the previous visited location"), + G_CALLBACK (on_action_directory_previous)}, + {"DirectoryNext", GTK_STOCK_GO_FORWARD, N_("_Next Location"), NULL, + N_("Go to the next visited location"), G_CALLBACK (on_action_directory_next)}, + {"DirectoryRefresh", GTK_STOCK_REFRESH, N_("Re_fresh View"), NULL, + N_("Refresh the view"), G_CALLBACK (on_action_directory_refresh)}, + {"DirectoryOpen", GTK_STOCK_OPEN, N_("_View Folder"), NULL, + N_("View folder in file manager"), + G_CALLBACK (on_action_directory_open)} +}; + +static const GtkToggleActionEntry tree_actions_toggle[] = +{ + {"FilterHidden", GTK_STOCK_DIALOG_AUTHENTICATION, + N_("Show _Hidden"), NULL, + N_("Show hidden files and folders"), + G_CALLBACK (on_action_filter_hidden), FALSE}, + {"FilterBinary", NULL, N_("Show _Binary"), NULL, + N_("Show binary files"), G_CALLBACK (on_action_filter_binary), + FALSE} +}; + +static const GtkActionEntry bookmark_actions[] = +{ + {"BookmarkOpen", GTK_STOCK_OPEN, N_("_View Folder"), NULL, + N_("View folder in file manager"), G_CALLBACK (on_action_bookmark_open)} +}; + +static void +create_toolbar (GeditFileBrowserWidget * obj, + const gchar *data_dir) +{ + GtkUIManager *manager; + GError *error = NULL; + GtkActionGroup *action_group; + GtkWidget *toolbar; + GtkWidget *widget; + GtkAction *action; + gchar *ui_file; + + manager = gtk_ui_manager_new (); + obj->priv->manager = manager; + + ui_file = g_build_filename (data_dir, XML_UI_FILE, NULL); + gtk_ui_manager_add_ui_from_file (manager, ui_file, &error); + + g_free (ui_file); + + if (error != NULL) { + g_warning ("Error in adding ui from file %s: %s", + XML_UI_FILE, error->message); + g_error_free (error); + return; + } + + action_group = gtk_action_group_new ("FileBrowserWidgetActionGroupToplevel"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + toplevel_actions, + G_N_ELEMENTS (toplevel_actions), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + + action_group = gtk_action_group_new ("FileBrowserWidgetActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions, + G_N_ELEMENTS (tree_actions), + obj); + gtk_action_group_add_toggle_actions (action_group, + tree_actions_toggle, + G_N_ELEMENTS (tree_actions_toggle), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetSelectionActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_selection, + G_N_ELEMENTS (tree_actions_selection), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_selection = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetFileSelectionActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_file_selection, + G_N_ELEMENTS (tree_actions_file_selection), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_file_selection = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetSingleSelectionActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_single_selection, + G_N_ELEMENTS (tree_actions_single_selection), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_single_selection = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetSingleMostSelectionActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_single_most_selection, + G_N_ELEMENTS (tree_actions_single_most_selection), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_single_most_selection = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetSensitiveActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + tree_actions_sensitive, + G_N_ELEMENTS (tree_actions_sensitive), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->action_group_sensitive = action_group; + + action_group = gtk_action_group_new ("FileBrowserWidgetBookmarkActionGroup"); + gtk_action_group_set_translation_domain (action_group, NULL); + gtk_action_group_add_actions (action_group, + bookmark_actions, + G_N_ELEMENTS (bookmark_actions), + obj); + gtk_ui_manager_insert_action_group (manager, action_group, 0); + obj->priv->bookmark_action_group = action_group; + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryPrevious"); + gtk_action_set_sensitive (action, FALSE); + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryNext"); + gtk_action_set_sensitive (action, FALSE); + + toolbar = gtk_ui_manager_get_widget (manager, "/ToolBar"); + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS); + gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_MENU); + + /* Previous directory menu tool item */ + obj->priv->location_previous_menu = gtk_menu_new (); + gtk_widget_show (obj->priv->location_previous_menu); + + widget = GTK_WIDGET (gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_BACK)); + gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (widget), + obj->priv->location_previous_menu); + + g_object_set (widget, "label", _("Previous location"), NULL); + gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (widget), + _("Go to previous location")); + gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (widget), + _("Go to a previously opened location")); + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryPrevious"); + g_object_set (action, "is_important", TRUE, "short_label", + _("Previous location"), NULL); + gtk_action_connect_proxy (action, widget); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (widget), 0); + + /* Next directory menu tool item */ + obj->priv->location_next_menu = gtk_menu_new (); + gtk_widget_show (obj->priv->location_next_menu); + + widget = GTK_WIDGET (gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_FORWARD)); + gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (widget), + obj->priv->location_next_menu); + + g_object_set (widget, "label", _("Next location"), NULL); + gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (widget), + _("Go to next location")); + gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (widget), + _("Go to a previously opened location")); + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryNext"); + g_object_set (action, "is_important", TRUE, "short_label", + _("Previous location"), NULL); + gtk_action_connect_proxy (action, widget); + gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (widget), 1); + + gtk_box_pack_start (GTK_BOX (obj), toolbar, FALSE, FALSE, 0); + gtk_widget_show (toolbar); + + set_enable_delete (obj, obj->priv->enable_delete); +} + +static void +set_enable_delete (GeditFileBrowserWidget *obj, + gboolean enable) +{ + GtkAction *action; + obj->priv->enable_delete = enable; + + if (obj->priv->action_group_selection == NULL) + return; + + action = + gtk_action_group_get_action (obj->priv->action_group_selection, + "FileDelete"); + + g_object_set (action, "visible", enable, "sensitive", enable, NULL); +} + +static gboolean +filter_real (GeditFileBrowserStore * model, GtkTreeIter * iter, + GeditFileBrowserWidget * obj) +{ + GSList *item; + FilterFunc *func; + + for (item = obj->priv->filter_funcs; item; item = item->next) { + func = (FilterFunc *) (item->data); + + if (!func->func (obj, model, iter, func->user_data)) + return FALSE; + } + + return TRUE; +} + +static void +add_bookmark_hash (GeditFileBrowserWidget * obj, + GtkTreeIter * iter) +{ + GtkTreeModel *model; + GdkPixbuf * pixbuf; + gchar * name; + gchar * uri; + GFile * file; + NameIcon * item; + + model = GTK_TREE_MODEL (obj->priv->bookmarks_store); + + uri = gedit_file_bookmarks_store_get_uri (obj->priv-> + bookmarks_store, + iter); + + if (uri == NULL) + return; + + file = g_file_new_for_uri (uri); + g_free (uri); + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, + &pixbuf, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, + &name, -1); + + item = g_new (NameIcon, 1); + item->name = name; + item->icon = pixbuf; + + g_hash_table_insert (obj->priv->bookmarks_hash, + file, + item); +} + +static void +init_bookmarks_hash (GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + GtkTreeModel *model; + + model = GTK_TREE_MODEL (obj->priv->bookmarks_store); + + if (!gtk_tree_model_get_iter_first (model, &iter)) + return; + + do { + add_bookmark_hash (obj, &iter); + } while (gtk_tree_model_iter_next (model, &iter)); + + g_signal_connect (obj->priv->bookmarks_store, + "row-changed", + G_CALLBACK (on_bookmarks_row_changed), + obj); + + g_signal_connect (obj->priv->bookmarks_store, + "row-deleted", + G_CALLBACK (on_bookmarks_row_deleted), + obj); +} + +static void +on_begin_loading (GeditFileBrowserStore *model, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + if (!GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview)))) + return; + + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (obj)), + obj->priv->busy_cursor); +} + +static void +on_end_loading (GeditFileBrowserStore *model, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + if (!GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview)))) + return; + + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (obj)), NULL); +} + +static void +create_tree (GeditFileBrowserWidget * obj) +{ + GtkWidget *sw; + + obj->priv->file_store = gedit_file_browser_store_new (NULL); + obj->priv->bookmarks_store = gedit_file_bookmarks_store_new (); + obj->priv->treeview = + GEDIT_FILE_BROWSER_VIEW (gedit_file_browser_view_new ()); + + gedit_file_browser_view_set_restore_expand_state (obj->priv->treeview, TRUE); + + gedit_file_browser_store_set_filter_mode (obj->priv->file_store, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN + | + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); + gedit_file_browser_store_set_filter_func (obj->priv->file_store, + (GeditFileBrowserStoreFilterFunc) + filter_real, obj); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_container_add (GTK_CONTAINER (sw), + GTK_WIDGET (obj->priv->treeview)); + gtk_box_pack_start (GTK_BOX (obj), sw, TRUE, TRUE, 0); + + g_signal_connect (obj->priv->treeview, "notify::model", + G_CALLBACK (on_model_set), obj); + g_signal_connect (obj->priv->treeview, "error", + G_CALLBACK (on_treeview_error), obj); + g_signal_connect (obj->priv->treeview, "popup-menu", + G_CALLBACK (on_treeview_popup_menu), obj); + g_signal_connect (obj->priv->treeview, "button-press-event", + G_CALLBACK (on_treeview_button_press_event), + obj); + g_signal_connect (obj->priv->treeview, "key-press-event", + G_CALLBACK (on_treeview_key_press_event), obj); + + g_signal_connect (gtk_tree_view_get_selection + (GTK_TREE_VIEW (obj->priv->treeview)), "changed", + G_CALLBACK (on_selection_changed), obj); + g_signal_connect (obj->priv->file_store, "notify::filter-mode", + G_CALLBACK (on_filter_mode_changed), obj); + + g_signal_connect (obj->priv->file_store, "notify::virtual-root", + G_CALLBACK (on_virtual_root_changed), obj); + + g_signal_connect (obj->priv->file_store, "begin-loading", + G_CALLBACK (on_begin_loading), obj); + + g_signal_connect (obj->priv->file_store, "end-loading", + G_CALLBACK (on_end_loading), obj); + + g_signal_connect (obj->priv->file_store, "error", + G_CALLBACK (on_file_store_error), obj); + + init_bookmarks_hash (obj); + + gtk_widget_show (sw); + gtk_widget_show (GTK_WIDGET (obj->priv->treeview)); +} + +static void +create_filter (GeditFileBrowserWidget * obj) +{ + GtkWidget *expander; + GtkWidget *vbox; + GtkWidget *entry; + + expander = gtk_expander_new_with_mnemonic (_("_Match Filename")); + gtk_widget_show (expander); + gtk_box_pack_start (GTK_BOX (obj), expander, FALSE, FALSE, 0); + + vbox = gtk_vbox_new (FALSE, 3); + gtk_widget_show (vbox); + + obj->priv->filter_expander = expander; + + entry = gtk_entry_new (); + gtk_widget_show (entry); + + obj->priv->filter_entry = entry; + + g_signal_connect_swapped (entry, "activate", + G_CALLBACK (on_entry_filter_activate), + obj); + g_signal_connect_swapped (entry, "focus_out_event", + G_CALLBACK (on_entry_filter_activate), + obj); + + gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER (expander), vbox); +} + +static void +gedit_file_browser_widget_init (GeditFileBrowserWidget * obj) +{ + obj->priv = GEDIT_FILE_BROWSER_WIDGET_GET_PRIVATE (obj); + + obj->priv->bookmarks_hash = g_hash_table_new_full (g_file_hash, + (GEqualFunc)g_file_equal, + g_object_unref, + free_name_icon); + + gtk_box_set_spacing (GTK_BOX (obj), 3); + + obj->priv->busy_cursor = gdk_cursor_new (GDK_WATCH); +} + +/* Private */ + +static void +update_sensitivity (GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GtkAction *action; + gint mode; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) { + gtk_action_group_set_sensitive (obj->priv->action_group, + TRUE); + gtk_action_group_set_sensitive (obj->priv->bookmark_action_group, + FALSE); + + mode = + gedit_file_browser_store_get_filter_mode + (GEDIT_FILE_BROWSER_STORE (model)); + + action = + gtk_action_group_get_action (obj->priv->action_group, + "FilterHidden"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + !(mode & + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN)); + } else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) { + gtk_action_group_set_sensitive (obj->priv->action_group, + FALSE); + gtk_action_group_set_sensitive (obj->priv->bookmark_action_group, + TRUE); + + /* Set the filter toggle to normal up state, just for visual pleasure */ + action = + gtk_action_group_get_action (obj->priv->action_group, + "FilterHidden"); + gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), + FALSE); + } + + on_selection_changed (gtk_tree_view_get_selection + (GTK_TREE_VIEW (obj->priv->treeview)), obj); +} + +static gboolean +gedit_file_browser_widget_get_first_selected (GeditFileBrowserWidget *obj, + GtkTreeIter *iter) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeModel *model; + GList *rows = gtk_tree_selection_get_selected_rows (selection, &model); + gboolean result; + + if (!rows) + return FALSE; + + result = gtk_tree_model_get_iter(model, iter, (GtkTreePath *)(rows->data)); + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return result; +} + +static gboolean +popup_menu (GeditFileBrowserWidget * obj, GdkEventButton * event, GtkTreeModel * model) +{ + GtkWidget *menu; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + menu = gtk_ui_manager_get_widget (obj->priv->manager, "/FilePopup"); + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + menu = gtk_ui_manager_get_widget (obj->priv->manager, "/BookmarkPopup"); + else + return FALSE; + + g_return_val_if_fail (menu != NULL, FALSE); + + if (event != NULL) { + GtkTreeSelection *selection; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + + if (gtk_tree_selection_count_selected_rows (selection) <= 1) { + GtkTreePath *path; + + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (obj->priv->treeview), + (gint)event->x, (gint)event->y, + &path, NULL, NULL, NULL)) + { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + gtk_tree_path_free (path); + } + } + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, + event->button, event->time); + } else { + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, + gedit_utils_menu_position_under_tree_view, + obj->priv->treeview, 0, + gtk_get_current_event_time ()); + gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); + } + + return TRUE; +} + +static gboolean +filter_glob (GeditFileBrowserWidget * obj, GeditFileBrowserStore * store, + GtkTreeIter * iter, gpointer user_data) +{ + gchar *name; + gboolean result; + guint flags; + + if (obj->priv->filter_pattern == NULL) + return TRUE; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (FILE_IS_DIR (flags) || FILE_IS_DUMMY (flags)) + result = TRUE; + else + result = + g_pattern_match_string (obj->priv->filter_pattern, + name); + + g_free (name); + + return result; +} + +static void +rename_selected_file (GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (gedit_file_browser_widget_get_first_selected (obj, &iter)) + gedit_file_browser_view_start_rename (obj->priv->treeview, + &iter); +} + +static GList * +get_deletable_files (GeditFileBrowserWidget *obj) { + GtkTreeSelection *selection; + GtkTreeModel *model; + GList *rows; + GList *row; + GList *paths = NULL; + guint flags; + GtkTreeIter iter; + GtkTreePath *path; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + /* Get all selected files */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + if (!gtk_tree_model_get_iter (model, &iter, path)) + continue; + + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, + &flags, -1); + + if (FILE_IS_DUMMY (flags)) + continue; + + paths = g_list_append (paths, gtk_tree_path_copy (path)); + } + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return paths; +} + +static gboolean +delete_selected_files (GeditFileBrowserWidget * obj, gboolean trash) +{ + GtkTreeModel *model; + gboolean confirm; + GeditFileBrowserStoreResult result; + GList *rows; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return FALSE; + + rows = get_deletable_files (obj); + + if (!rows) + return FALSE; + + if (!trash) { + g_signal_emit (obj, signals[CONFIRM_DELETE], 0, model, rows, &confirm); + + if (!confirm) + return FALSE; + } + + result = gedit_file_browser_store_delete_all (GEDIT_FILE_BROWSER_STORE (model), + rows, trash); + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return result == GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +static gboolean +on_file_store_no_trash (GeditFileBrowserStore * store, + GList * files, + GeditFileBrowserWidget * obj) +{ + gboolean confirm = FALSE; + + g_signal_emit (obj, signals[CONFIRM_NO_TRASH], 0, files, &confirm); + + return confirm; +} + +static GFile * +get_topmost_file (GFile * file) +{ + GFile * tmp; + GFile * current; + + current = g_object_ref (file); + + while ((tmp = g_file_get_parent (current)) != NULL) { + g_object_unref (current); + current = tmp; + } + + return current; +} + +static GtkWidget * +create_goto_menu_item (GeditFileBrowserWidget * obj, GList * item, + GdkPixbuf * icon) +{ + GtkWidget *result; + GtkWidget *image; + gchar *unescape; + GdkPixbuf *pixbuf = NULL; + Location *loc; + + loc = (Location *) (item->data); + + if (!get_from_bookmark_file (obj, loc->virtual_root, &unescape, &pixbuf)) { + unescape = gedit_file_browser_utils_file_basename (loc->virtual_root); + + if (icon) + pixbuf = g_object_ref (icon); + } + + if (pixbuf) { + image = gtk_image_new_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + + gtk_widget_show (image); + + result = gtk_image_menu_item_new_with_label (unescape); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (result), + image); + } else { + result = gtk_menu_item_new_with_label (unescape); + } + + g_object_set_data (G_OBJECT (result), LOCATION_DATA_KEY, item); + g_signal_connect (result, "activate", + G_CALLBACK (on_location_jump_activate), obj); + + gtk_widget_show (result); + + g_free (unescape); + + return result; +} + +static GList * +list_next_iterator (GList * list) +{ + if (!list) + return NULL; + + return list->next; +} + +static GList * +list_prev_iterator (GList * list) +{ + if (!list) + return NULL; + + return list->prev; +} + +static void +jump_to_location (GeditFileBrowserWidget * obj, GList * item, + gboolean previous) +{ + Location *loc; + GtkWidget *widget; + GList *children; + GList *child; + GList *(*iter_func) (GList *); + GtkWidget *menu_from; + GtkWidget *menu_to; + gchar *root; + gchar *virtual_root; + + if (!obj->priv->locations) + return; + + if (previous) { + iter_func = list_next_iterator; + menu_from = obj->priv->location_previous_menu; + menu_to = obj->priv->location_next_menu; + } else { + iter_func = list_prev_iterator; + menu_from = obj->priv->location_next_menu; + menu_to = obj->priv->location_previous_menu; + } + + children = gtk_container_get_children (GTK_CONTAINER (menu_from)); + child = children; + + /* This is the menuitem for the current location, which is the first + to be added to the menu */ + widget = obj->priv->current_location_menu_item; + + while (obj->priv->current_location != item) { + if (widget) { + /* Prepend the menu item to the menu */ + gtk_menu_shell_prepend (GTK_MENU_SHELL (menu_to), + widget); + + g_object_unref (widget); + } + + widget = GTK_WIDGET (child->data); + + /* Make sure the widget isn't destroyed when removed */ + g_object_ref (widget); + gtk_container_remove (GTK_CONTAINER (menu_from), widget); + + obj->priv->current_location_menu_item = widget; + + if (obj->priv->current_location == NULL) { + obj->priv->current_location = obj->priv->locations; + + if (obj->priv->current_location == item) + break; + } else { + obj->priv->current_location = + iter_func (obj->priv->current_location); + } + + child = child->next; + } + + g_list_free (children); + + obj->priv->changing_location = TRUE; + + loc = (Location *) (obj->priv->current_location->data); + + /* Set the new root + virtual root */ + root = g_file_get_uri (loc->root); + virtual_root = g_file_get_uri (loc->virtual_root); + + gedit_file_browser_widget_set_root_and_virtual_root (obj, + root, + virtual_root); + + g_free (root); + g_free (virtual_root); + + obj->priv->changing_location = FALSE; +} + +static void +clear_next_locations (GeditFileBrowserWidget * obj) +{ + GList *children; + GList *item; + + if (obj->priv->current_location == NULL) + return; + + while (obj->priv->current_location->prev) { + location_free ((Location *) (obj->priv->current_location-> + prev->data)); + obj->priv->locations = + g_list_remove_link (obj->priv->locations, + obj->priv->current_location->prev); + } + + children = + gtk_container_get_children (GTK_CONTAINER + (obj->priv->location_next_menu)); + + for (item = children; item; item = item->next) { + gtk_container_remove (GTK_CONTAINER + (obj->priv->location_next_menu), + GTK_WIDGET (item->data)); + } + + g_list_free (children); + + gtk_action_set_sensitive (gtk_action_group_get_action + (obj->priv->action_group_sensitive, + "DirectoryNext"), FALSE); +} + +static void +update_filter_mode (GeditFileBrowserWidget * obj, + GtkAction * action, + GeditFileBrowserStoreFilterMode mode) +{ + gboolean active = + gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)); + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + gint now; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) { + now = + gedit_file_browser_store_get_filter_mode + (GEDIT_FILE_BROWSER_STORE (model)); + + if (active) + now &= ~mode; + else + now |= mode; + + gedit_file_browser_store_set_filter_mode + (GEDIT_FILE_BROWSER_STORE (model), now); + } +} + +static void +set_filter_pattern_real (GeditFileBrowserWidget * obj, + gchar const * pattern, + gboolean update_entry) +{ + GtkTreeModel *model; + + model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (pattern != NULL && *pattern == '\0') + pattern = NULL; + + if (pattern == NULL && obj->priv->filter_pattern_str == NULL) + return; + + if (pattern != NULL && obj->priv->filter_pattern_str != NULL && + strcmp (pattern, obj->priv->filter_pattern_str) == 0) + return; + + /* Free the old pattern */ + g_free (obj->priv->filter_pattern_str); + obj->priv->filter_pattern_str = g_strdup (pattern); + + if (obj->priv->filter_pattern) { + g_pattern_spec_free (obj->priv->filter_pattern); + obj->priv->filter_pattern = NULL; + } + + if (pattern == NULL) { + if (obj->priv->glob_filter_id != 0) { + gedit_file_browser_widget_remove_filter (obj, + obj-> + priv-> + glob_filter_id); + obj->priv->glob_filter_id = 0; + } + } else { + obj->priv->filter_pattern = g_pattern_spec_new (pattern); + + if (obj->priv->glob_filter_id == 0) + obj->priv->glob_filter_id = + gedit_file_browser_widget_add_filter (obj, + filter_glob, + NULL, + NULL); + } + + if (update_entry) { + if (obj->priv->filter_pattern_str == NULL) + gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry), + ""); + else { + gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry), + obj->priv->filter_pattern_str); + + gtk_expander_set_expanded (GTK_EXPANDER (obj->priv->filter_expander), + TRUE); + } + } + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + gedit_file_browser_store_refilter (GEDIT_FILE_BROWSER_STORE + (model)); + + g_object_notify (G_OBJECT (obj), "filter-pattern"); +} + + +/* Public */ + +GtkWidget * +gedit_file_browser_widget_new (const gchar *data_dir) +{ + GeditFileBrowserWidget *obj = + g_object_new (GEDIT_TYPE_FILE_BROWSER_WIDGET, NULL); + + create_toolbar (obj, data_dir); + create_combo (obj); + create_tree (obj); + create_filter (obj); + + gedit_file_browser_widget_show_bookmarks (obj); + + return GTK_WIDGET (obj); +} + +void +gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget * obj) +{ + /* Select bookmarks in the combo box */ + g_signal_handlers_block_by_func (obj->priv->combo, + on_combo_changed, obj); + combo_set_active_by_id (obj, BOOKMARKS_ID); + g_signal_handlers_unblock_by_func (obj->priv->combo, + on_combo_changed, obj); + + check_current_item (obj, FALSE); + + gedit_file_browser_view_set_model (obj->priv->treeview, + GTK_TREE_MODEL (obj->priv-> + bookmarks_store)); +} + +static void +show_files_real (GeditFileBrowserWidget *obj, + gboolean do_root_changed) +{ + gedit_file_browser_view_set_model (obj->priv->treeview, + GTK_TREE_MODEL (obj->priv-> + file_store)); + + if (do_root_changed) + on_virtual_root_changed (obj->priv->file_store, NULL, obj); +} + +void +gedit_file_browser_widget_show_files (GeditFileBrowserWidget * obj) +{ + show_files_real (obj, TRUE); +} + +void +gedit_file_browser_widget_set_root_and_virtual_root (GeditFileBrowserWidget *obj, + gchar const *root, + gchar const *virtual_root) +{ + GeditFileBrowserStoreResult result; + + if (!virtual_root) + result = + gedit_file_browser_store_set_root_and_virtual_root + (obj->priv->file_store, root, root); + else + result = + gedit_file_browser_store_set_root_and_virtual_root + (obj->priv->file_store, root, virtual_root); + + if (result == GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE) + show_files_real (obj, TRUE); +} + +void +gedit_file_browser_widget_set_root (GeditFileBrowserWidget * obj, + gchar const *root, + gboolean virtual_root) +{ + GFile *file; + GFile *parent; + gchar *str; + + if (!virtual_root) { + gedit_file_browser_widget_set_root_and_virtual_root (obj, + root, + NULL); + return; + } + + if (!root) + return; + + file = g_file_new_for_uri (root); + parent = get_topmost_file (file); + str = g_file_get_uri (parent); + + gedit_file_browser_widget_set_root_and_virtual_root + (obj, str, root); + + g_free (str); + + g_object_unref (file); + g_object_unref (parent); +} + +GeditFileBrowserStore * +gedit_file_browser_widget_get_browser_store (GeditFileBrowserWidget * obj) +{ + return obj->priv->file_store; +} + +GeditFileBookmarksStore * +gedit_file_browser_widget_get_bookmarks_store (GeditFileBrowserWidget * obj) +{ + return obj->priv->bookmarks_store; +} + +GeditFileBrowserView * +gedit_file_browser_widget_get_browser_view (GeditFileBrowserWidget * obj) +{ + return obj->priv->treeview; +} + +GtkUIManager * +gedit_file_browser_widget_get_ui_manager (GeditFileBrowserWidget * obj) +{ + return obj->priv->manager; +} + +GtkWidget * +gedit_file_browser_widget_get_filter_entry (GeditFileBrowserWidget * obj) +{ + return obj->priv->filter_entry; +} + +gulong +gedit_file_browser_widget_add_filter (GeditFileBrowserWidget * obj, + GeditFileBrowserWidgetFilterFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + FilterFunc *f; + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + f = filter_func_new (obj, func, user_data, notify); + obj->priv->filter_funcs = + g_slist_append (obj->priv->filter_funcs, f); + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + gedit_file_browser_store_refilter (GEDIT_FILE_BROWSER_STORE + (model)); + + return f->id; +} + +void +gedit_file_browser_widget_remove_filter (GeditFileBrowserWidget * obj, + gulong id) +{ + GSList *item; + FilterFunc *func; + + for (item = obj->priv->filter_funcs; item; item = item->next) + { + func = (FilterFunc *) (item->data); + + if (func->id == id) + { + if (func->destroy_notify) + func->destroy_notify (func->user_data); + + obj->priv->filter_funcs = + g_slist_remove_link (obj->priv->filter_funcs, + item); + g_free (func); + break; + } + } +} + +void +gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget * obj, + gchar const *pattern) +{ + set_filter_pattern_real (obj, pattern, TRUE); +} + +gboolean +gedit_file_browser_widget_get_selected_directory (GeditFileBrowserWidget * obj, + GtkTreeIter * iter) +{ + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeIter parent; + guint flags; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return FALSE; + + if (!gedit_file_browser_widget_get_first_selected (obj, iter)) { + if (!gedit_file_browser_store_get_iter_virtual_root + (GEDIT_FILE_BROWSER_STORE (model), iter)) + return FALSE; + } + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!FILE_IS_DIR (flags)) { + /* Get the parent, because the selection is a file */ + gtk_tree_model_iter_parent (model, &parent, iter); + *iter = parent; + } + + return TRUE; +} + +static guint +gedit_file_browser_widget_get_num_selected_files_or_directories (GeditFileBrowserWidget *obj, + guint *files, + guint *dirs) +{ + GList *rows, *row; + GtkTreePath *path; + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + guint result = 0; + GtkTreeSelection *selection; + GtkTreeModel *model; + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + return 0; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + /* Get iter from path */ + if (!gtk_tree_model_get_iter (model, &iter, path)) + continue; + + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!FILE_IS_DUMMY (flags)) { + if (!FILE_IS_DIR (flags)) + ++(*files); + else + ++(*dirs); + + ++result; + } + } + + g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL); + g_list_free (rows); + + return result; +} + +typedef struct +{ + GeditFileBrowserWidget *widget; + GCancellable *cancellable; +} AsyncData; + +static AsyncData * +async_data_new (GeditFileBrowserWidget *widget) +{ + AsyncData *ret; + + ret = g_new (AsyncData, 1); + ret->widget = widget; + + cancel_async_operation (widget); + widget->priv->cancellable = g_cancellable_new (); + + ret->cancellable = g_object_ref (widget->priv->cancellable); + + return ret; +} + +static void +async_free (AsyncData *async) +{ + g_object_unref (async->cancellable); + g_free (async); +} + +static void +set_busy (GeditFileBrowserWidget *obj, gboolean busy) +{ + GdkCursor *cursor; + GdkWindow *window; + + window = gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview)); + + if (!GDK_IS_WINDOW (window)) + return; + + if (busy) + { + cursor = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor (window, cursor); + gdk_cursor_unref (cursor); + } + else + { + gdk_window_set_cursor (window, NULL); + } +} + +static void try_mount_volume (GeditFileBrowserWidget *widget, GVolume *volume); + +static void +activate_mount (GeditFileBrowserWidget *widget, + GVolume *volume, + GMount *mount) +{ + GFile *root; + gchar *uri; + + if (!mount) + { + gchar *message; + gchar *name; + + name = g_volume_get_name (volume); + message = g_strdup_printf (_("No mount object for mounted volume: %s"), name); + + g_signal_emit (widget, + signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + message); + + g_free (name); + g_free (message); + return; + } + + root = g_mount_get_root (mount); + uri = g_file_get_uri (root); + + gedit_file_browser_widget_set_root (widget, uri, FALSE); + + g_free (uri); + g_object_unref (root); +} + +static void +try_activate_drive (GeditFileBrowserWidget *widget, + GDrive *drive) +{ + GList *volumes; + GVolume *volume; + GMount *mount; + + volumes = g_drive_get_volumes (drive); + + volume = G_VOLUME (volumes->data); + mount = g_volume_get_mount (volume); + + if (mount) + { + /* try set the root of the mount */ + activate_mount (widget, volume, mount); + g_object_unref (mount); + } + else + { + /* try to mount it then? */ + try_mount_volume (widget, volume); + } + + g_list_foreach (volumes, (GFunc)g_object_unref, NULL); + g_list_free (volumes); +} + +static void +poll_for_media_cb (GDrive *drive, + GAsyncResult *res, + AsyncData *async) +{ + GError *error = NULL; + + /* check for cancelled state */ + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_free (async); + return; + } + + /* finish poll operation */ + set_busy (async->widget, FALSE); + + if (g_drive_poll_for_media_finish (drive, res, &error) && + g_drive_has_media (drive) && + g_drive_has_volumes (drive)) + { + try_activate_drive (async->widget, drive); + } + else + { + gchar *message; + gchar *name; + + name = g_drive_get_name (drive); + message = g_strdup_printf (_("Could not open media: %s"), name); + + g_signal_emit (async->widget, + signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + message); + + g_free (name); + g_free (message); + + g_error_free (error); + } + + async_free (async); +} + +static void +mount_volume_cb (GVolume *volume, + GAsyncResult *res, + AsyncData *async) +{ + GError *error = NULL; + + /* check for cancelled state */ + if (g_cancellable_is_cancelled (async->cancellable)) + { + async_free (async); + return; + } + + if (g_volume_mount_finish (volume, res, &error)) + { + GMount *mount; + + mount = g_volume_get_mount (volume); + activate_mount (async->widget, volume, mount); + + if (mount) + g_object_unref (mount); + } + else + { + gchar *message; + gchar *name; + + name = g_volume_get_name (volume); + message = g_strdup_printf (_("Could not mount volume: %s"), name); + + g_signal_emit (async->widget, + signals[ERROR], + 0, + GEDIT_FILE_BROWSER_ERROR_SET_ROOT, + message); + + g_free (name); + g_free (message); + + g_error_free (error); + } + + set_busy (async->widget, FALSE); + async_free (async); +} + +static void +activate_drive (GeditFileBrowserWidget *obj, + GtkTreeIter *iter) +{ + GDrive *drive; + AsyncData *async; + + gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->bookmarks_store), iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + &drive, -1); + + /* most common use case is a floppy drive, we'll poll for media and + go from there */ + async = async_data_new (obj); + g_drive_poll_for_media (drive, + async->cancellable, + (GAsyncReadyCallback)poll_for_media_cb, + async); + + g_object_unref (drive); + set_busy (obj, TRUE); +} + +static void +try_mount_volume (GeditFileBrowserWidget *widget, + GVolume *volume) +{ + GMountOperation *operation; + AsyncData *async; + + operation = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (widget)))); + async = async_data_new (widget); + + g_volume_mount (volume, + G_MOUNT_MOUNT_NONE, + operation, + async->cancellable, + (GAsyncReadyCallback)mount_volume_cb, + async); + + g_object_unref (operation); + set_busy (widget, TRUE); +} + +static void +activate_volume (GeditFileBrowserWidget *obj, + GtkTreeIter *iter) +{ + GVolume *volume; + + gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->bookmarks_store), iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, + &volume, -1); + + /* see if we can mount the volume */ + try_mount_volume (obj, volume); + g_object_unref (volume); +} + +void +gedit_file_browser_widget_refresh (GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = + gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + gedit_file_browser_store_refresh (GEDIT_FILE_BROWSER_STORE + (model)); + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) { + g_hash_table_ref (obj->priv->bookmarks_hash); + g_hash_table_destroy (obj->priv->bookmarks_hash); + + gedit_file_bookmarks_store_refresh + (GEDIT_FILE_BOOKMARKS_STORE (model)); + } +} + +void +gedit_file_browser_widget_history_back (GeditFileBrowserWidget *obj) +{ + if (obj->priv->locations) { + if (obj->priv->current_location) + jump_to_location (obj, + obj->priv->current_location-> + next, TRUE); + else { + jump_to_location (obj, obj->priv->locations, TRUE); + } + } +} + +void +gedit_file_browser_widget_history_forward (GeditFileBrowserWidget *obj) +{ + if (obj->priv->locations) + jump_to_location (obj, obj->priv->current_location->prev, + FALSE); +} + +static void +bookmark_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gchar *uri; + gint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, + &flags, -1); + + if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE) + { + /* handle a drive node */ + gedit_file_browser_store_cancel_mount_operation (obj->priv->file_store); + activate_drive (obj, iter); + return; + } + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME) + { + /* handle a volume node */ + gedit_file_browser_store_cancel_mount_operation (obj->priv->file_store); + activate_volume (obj, iter); + return; + } + + uri = + gedit_file_bookmarks_store_get_uri + (GEDIT_FILE_BOOKMARKS_STORE (model), iter); + + if (uri) { + /* here we check if the bookmark is a mount point, or if it + is a remote bookmark. If that's the case, we will set the + root to the uri of the bookmark and not try to set the + topmost parent as root (since that may as well not be the + mount point anymore) */ + if ((flags & GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT) || + (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK)) { + gedit_file_browser_widget_set_root (obj, + uri, + FALSE); + } else { + gedit_file_browser_widget_set_root (obj, + uri, + TRUE); + } + } else { + g_warning ("No uri!"); + } + + g_free (uri); +} + +static void +file_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gchar *uri; + gint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + -1); + + if (!FILE_IS_DIR (flags) && !FILE_IS_DUMMY (flags)) { + g_signal_emit (obj, signals[URI_ACTIVATED], 0, uri); + } + + g_free (uri); +} + +static gboolean +directory_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gboolean result = FALSE; + GError *error = NULL; + gchar *uri = NULL; + GeditFileBrowserStoreFlag flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri, + -1); + + if (FILE_IS_DIR (flags)) { + result = TRUE; + + if (!gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (obj)), uri, GDK_CURRENT_TIME, &error)) { + g_signal_emit (obj, signals[ERROR], 0, + GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY, + error->message); + + g_error_free (error); + error = NULL; + } + } + + g_free (uri); + + return result; +} + +static void +on_bookmark_activated (GeditFileBrowserView *tree_view, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); + + bookmark_open (obj, model, iter); +} + +static void +on_file_activated (GeditFileBrowserView *tree_view, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)); + + file_open (obj, model, iter); +} + +static gboolean +virtual_root_is_root (GeditFileBrowserWidget * obj, + GeditFileBrowserStore * model) +{ + GtkTreeIter root; + GtkTreeIter virtual_root; + + if (!gedit_file_browser_store_get_iter_root (model, &root)) + return TRUE; + + if (!gedit_file_browser_store_get_iter_virtual_root (model, &virtual_root)) + return TRUE; + + return gedit_file_browser_store_iter_equal (model, &root, &virtual_root); +} + +static void +on_virtual_root_changed (GeditFileBrowserStore * model, + GParamSpec * param, + GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + gchar *uri; + gchar *root_uri; + GtkTreeIter root; + GtkAction *action; + Location *loc; + GdkPixbuf *pixbuf; + + if (gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)) != + GTK_TREE_MODEL (obj->priv->file_store)) + { + show_files_real (obj, FALSE); + } + + if (gedit_file_browser_store_get_iter_virtual_root (model, &iter)) { + gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_URI, + &uri, -1); + + if (gedit_file_browser_store_get_iter_root (model, &root)) { + if (!obj->priv->changing_location) { + /* Remove all items from obj->priv->current_location on */ + if (obj->priv->current_location) + clear_next_locations (obj); + + root_uri = + gedit_file_browser_store_get_root + (model); + + loc = g_new (Location, 1); + loc->root = g_file_new_for_uri (root_uri); + loc->virtual_root = g_file_new_for_uri (uri); + g_free (root_uri); + + if (obj->priv->current_location) { + /* Add current location to the menu so we can go back + to it later */ + gtk_menu_shell_prepend + (GTK_MENU_SHELL + (obj->priv-> + location_previous_menu), + obj->priv-> + current_location_menu_item); + } + + obj->priv->locations = + g_list_prepend (obj->priv->locations, + loc); + + gtk_tree_model_get (GTK_TREE_MODEL (model), + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON, + &pixbuf, -1); + + obj->priv->current_location = + obj->priv->locations; + obj->priv->current_location_menu_item = + create_goto_menu_item (obj, + obj->priv-> + current_location, + pixbuf); + + g_object_ref_sink (obj->priv-> + current_location_menu_item); + + if (pixbuf) + g_object_unref (pixbuf); + + } + + action = + gtk_action_group_get_action (obj->priv-> + action_group, + "DirectoryUp"); + gtk_action_set_sensitive (action, + !virtual_root_is_root (obj, model)); + + action = + gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryPrevious"); + gtk_action_set_sensitive (action, + obj->priv-> + current_location != NULL + && obj->priv-> + current_location->next != + NULL); + + action = + gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryNext"); + gtk_action_set_sensitive (action, + obj->priv-> + current_location != NULL + && obj->priv-> + current_location->prev != + NULL); + } + + check_current_item (obj, TRUE); + g_free (uri); + } else { + g_message ("NO!"); + } +} + +static void +on_model_set (GObject * gobject, GParamSpec * arg1, + GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (gobject)); + + clear_signals (obj); + + if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) { + clear_next_locations (obj); + + /* Add the current location to the back menu */ + if (obj->priv->current_location) { + GtkAction *action; + + gtk_menu_shell_prepend (GTK_MENU_SHELL (obj->priv->location_previous_menu), + obj->priv->current_location_menu_item); + + g_object_unref (obj->priv->current_location_menu_item); + obj->priv->current_location = NULL; + obj->priv->current_location_menu_item = NULL; + + action = gtk_action_group_get_action (obj->priv->action_group_sensitive, + "DirectoryPrevious"); + gtk_action_set_sensitive (action, TRUE); + } + + gtk_widget_set_sensitive (obj->priv->filter_expander, FALSE); + + add_signal (obj, gobject, + g_signal_connect (gobject, "bookmark-activated", + G_CALLBACK + (on_bookmark_activated), obj)); + } else if (GEDIT_IS_FILE_BROWSER_STORE (model)) { + /* make sure any async operation is cancelled */ + cancel_async_operation (obj); + + add_signal (obj, gobject, + g_signal_connect (gobject, "file-activated", + G_CALLBACK + (on_file_activated), obj)); + + add_signal (obj, model, + g_signal_connect (model, "no-trash", + G_CALLBACK + (on_file_store_no_trash), obj)); + + gtk_widget_set_sensitive (obj->priv->filter_expander, TRUE); + } + + update_sensitivity (obj); +} + +static void +on_file_store_error (GeditFileBrowserStore * store, guint code, + gchar * message, GeditFileBrowserWidget * obj) +{ + g_signal_emit (obj, signals[ERROR], 0, code, message); +} + +static void +on_treeview_error (GeditFileBrowserView * tree_view, guint code, + gchar * message, GeditFileBrowserWidget * obj) +{ + g_signal_emit (obj, signals[ERROR], 0, code, message); +} + +static void +on_combo_changed (GtkComboBox * combo, GeditFileBrowserWidget * obj) +{ + GtkTreeIter iter; + guint id; + gchar * uri; + GFile * file; + + if (!gtk_combo_box_get_active_iter (combo, &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->combo_model), &iter, + COLUMN_ID, &id, -1); + + switch (id) { + case BOOKMARKS_ID: + gedit_file_browser_widget_show_bookmarks (obj); + break; + + case PATH_ID: + gtk_tree_model_get (GTK_TREE_MODEL + (obj->priv->combo_model), &iter, + COLUMN_FILE, &file, -1); + + uri = g_file_get_uri (file); + gedit_file_browser_store_set_virtual_root_from_string + (obj->priv->file_store, uri); + + g_free (uri); + g_object_unref (file); + break; + } +} + +static gboolean +on_treeview_popup_menu (GeditFileBrowserView * treeview, + GeditFileBrowserWidget * obj) +{ + return popup_menu (obj, NULL, gtk_tree_view_get_model (GTK_TREE_VIEW (treeview))); +} + +static gboolean +on_treeview_button_press_event (GeditFileBrowserView * treeview, + GdkEventButton * event, + GeditFileBrowserWidget * obj) +{ + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + return popup_menu (obj, event, + gtk_tree_view_get_model (GTK_TREE_VIEW (treeview))); + } + + return FALSE; +} + +static gboolean +do_change_directory (GeditFileBrowserWidget * obj, + GdkEventKey * event) +{ + GtkAction * action = NULL; + + if ((event->state & + (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK & ~GDK_MOD1_MASK)) == + event->state && event->keyval == GDK_BackSpace) + action = gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryPrevious"); + else if (!((event->state & GDK_MOD1_MASK) && + (event->state & (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK)) == event->state)) + return FALSE; + + switch (event->keyval) { + case GDK_Left: + action = gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryPrevious"); + break; + case GDK_Right: + action = gtk_action_group_get_action (obj->priv-> + action_group_sensitive, + "DirectoryNext"); + break; + case GDK_Up: + action = gtk_action_group_get_action (obj->priv-> + action_group, + "DirectoryUp"); + break; + default: + break; + } + + if (action != NULL) { + gtk_action_activate (action); + return TRUE; + } + + return FALSE; +} + +static gboolean +on_treeview_key_press_event (GeditFileBrowserView * treeview, + GdkEventKey * event, + GeditFileBrowserWidget * obj) +{ + guint modifiers; + + if (do_change_directory (obj, event)) + return TRUE; + + if (!GEDIT_IS_FILE_BROWSER_STORE + (gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)))) + return FALSE; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + if (event->keyval == GDK_Delete + || event->keyval == GDK_KP_Delete) { + + if ((event->state & modifiers) == GDK_SHIFT_MASK) { + if (obj->priv->enable_delete) { + delete_selected_files (obj, FALSE); + return TRUE; + } + } else if ((event->state & modifiers) == 0) { + delete_selected_files (obj, TRUE); + return TRUE; + } + } + + if ((event->keyval == GDK_F2) + && (event->state & modifiers) == 0) { + rename_selected_file (obj); + + return TRUE; + } + + return FALSE; +} + +static void +on_selection_changed (GtkTreeSelection * selection, + GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + guint selected = 0; + guint files = 0; + guint dirs = 0; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + { + selected = gedit_file_browser_widget_get_num_selected_files_or_directories (obj, + &files, + &dirs); + } + + gtk_action_group_set_sensitive (obj->priv->action_group_selection, + selected > 0); + gtk_action_group_set_sensitive (obj->priv->action_group_file_selection, + (selected > 0) && (selected == files)); + gtk_action_group_set_sensitive (obj->priv->action_group_single_selection, + selected == 1); + gtk_action_group_set_sensitive (obj->priv->action_group_single_most_selection, + selected <= 1); +} + +static gboolean +on_entry_filter_activate (GeditFileBrowserWidget * obj) +{ + gchar const *text; + + text = gtk_entry_get_text (GTK_ENTRY (obj->priv->filter_entry)); + set_filter_pattern_real (obj, text, FALSE); + + return FALSE; +} + +static void +on_location_jump_activate (GtkMenuItem * item, + GeditFileBrowserWidget * obj) +{ + GList *location; + + location = g_object_get_data (G_OBJECT (item), LOCATION_DATA_KEY); + + if (obj->priv->current_location) { + jump_to_location (obj, location, + g_list_position (obj->priv->locations, + location) > + g_list_position (obj->priv->locations, + obj->priv-> + current_location)); + } else { + jump_to_location (obj, location, TRUE); + } + +} + +static void +on_bookmarks_row_changed (GtkTreeModel * model, + GtkTreePath * path, + GtkTreeIter * iter, + GeditFileBrowserWidget *obj) +{ + add_bookmark_hash (obj, iter); +} + +static void +on_bookmarks_row_deleted (GtkTreeModel * model, + GtkTreePath * path, + GeditFileBrowserWidget *obj) +{ + GtkTreeIter iter; + gchar * uri; + GFile * file; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + + uri = gedit_file_bookmarks_store_get_uri (obj->priv->bookmarks_store, &iter); + + if (!uri) + return; + + file = g_file_new_for_uri (uri); + g_hash_table_remove (obj->priv->bookmarks_hash, file); + + g_object_unref (file); + g_free (uri); +} + +static void +on_filter_mode_changed (GeditFileBrowserStore * model, + GParamSpec * param, + GeditFileBrowserWidget * obj) +{ + gint mode; + GtkToggleAction * action; + gboolean active; + + mode = gedit_file_browser_store_get_filter_mode (model); + + action = GTK_TOGGLE_ACTION (gtk_action_group_get_action (obj->priv->action_group, + "FilterHidden")); + active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN); + + if (active != gtk_toggle_action_get_active (action)) + gtk_toggle_action_set_active (action, active); + + action = GTK_TOGGLE_ACTION (gtk_action_group_get_action (obj->priv->action_group, + "FilterBinary")); + active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); + + if (active != gtk_toggle_action_get_active (action)) + gtk_toggle_action_set_active (action, active); +} + +static void +on_action_directory_next (GtkAction * action, GeditFileBrowserWidget * obj) +{ + gedit_file_browser_widget_history_forward (obj); +} + +static void +on_action_directory_previous (GtkAction * action, + GeditFileBrowserWidget * obj) +{ + gedit_file_browser_widget_history_back (obj); +} + +static void +on_action_directory_up (GtkAction * action, + GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + gedit_file_browser_store_set_virtual_root_up (GEDIT_FILE_BROWSER_STORE (model)); +} + +static void +on_action_directory_new (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeIter parent; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (!gedit_file_browser_widget_get_selected_directory (obj, &parent)) + return; + + if (gedit_file_browser_store_new_directory + (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) { + gedit_file_browser_view_start_rename (obj->priv->treeview, + &iter); + } +} + +static void +on_action_file_open (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GList *rows; + GList *row; + GtkTreeIter iter; + GtkTreePath *path; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + if (gtk_tree_model_get_iter (model, &iter, path)) + file_open (obj, model, &iter); + + gtk_tree_path_free (path); + } + + g_list_free (rows); +} + +static void +on_action_file_new (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeIter parent; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (!gedit_file_browser_widget_get_selected_directory (obj, &parent)) + return; + + if (gedit_file_browser_store_new_file + (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) { + gedit_file_browser_view_start_rename (obj->priv->treeview, + &iter); + } +} + +static void +on_action_file_rename (GtkAction * action, GeditFileBrowserWidget * obj) +{ + rename_selected_file (obj); +} + +static void +on_action_file_delete (GtkAction * action, GeditFileBrowserWidget * obj) +{ + delete_selected_files (obj, FALSE); +} + +static void +on_action_file_move_to_trash (GtkAction * action, GeditFileBrowserWidget * obj) +{ + delete_selected_files (obj, TRUE); +} + +static void +on_action_directory_refresh (GtkAction * action, + GeditFileBrowserWidget * obj) +{ + gedit_file_browser_widget_refresh (obj); +} + +static void +on_action_directory_open (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GList *rows; + GList *row; + gboolean directory_opened = FALSE; + GtkTreeIter iter; + GtkTreePath *path; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + + for (row = rows; row; row = row->next) { + path = (GtkTreePath *)(row->data); + + if (gtk_tree_model_get_iter (model, &iter, path)) + directory_opened |= directory_open (obj, model, &iter); + + gtk_tree_path_free (path); + } + + if (!directory_opened) { + if (gedit_file_browser_widget_get_selected_directory (obj, &iter)) + directory_open (obj, model, &iter); + } + + g_list_free (rows); +} + +static void +on_action_filter_hidden (GtkAction * action, GeditFileBrowserWidget * obj) +{ + update_filter_mode (obj, + action, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN); +} + +static void +on_action_filter_binary (GtkAction * action, GeditFileBrowserWidget * obj) +{ + update_filter_mode (obj, + action, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); +} + +static void +on_action_bookmark_open (GtkAction * action, GeditFileBrowserWidget * obj) +{ + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + + if (!GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + return; + + if (gtk_tree_selection_get_selected (selection, NULL, &iter)) + bookmark_open (obj, model, &iter); +} + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser-widget.h b/plugins/filebrowser/gedit-file-browser-widget.h new file mode 100755 index 00000000..e9cc2a0e --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget.h @@ -0,0 +1,121 @@ +/* + * gedit-file-browser-widget.h - Gedit plugin providing easy file access + * from the sidepanel + * + * Copyright (C) 2006 - Jesse van den Kieboom + * + * 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 2, 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __GEDIT_FILE_BROWSER_WIDGET_H__ +#define __GEDIT_FILE_BROWSER_WIDGET_H__ + +#include +#include "gedit-file-browser-store.h" +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-view.h" + +G_BEGIN_DECLS +#define GEDIT_TYPE_FILE_BROWSER_WIDGET (gedit_file_browser_widget_get_type ()) +#define GEDIT_FILE_BROWSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidget)) +#define GEDIT_FILE_BROWSER_WIDGET_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidget const)) +#define GEDIT_FILE_BROWSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidgetClass)) +#define GEDIT_IS_FILE_BROWSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET)) +#define GEDIT_IS_FILE_BROWSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_WIDGET)) +#define GEDIT_FILE_BROWSER_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidgetClass)) + +typedef struct _GeditFileBrowserWidget GeditFileBrowserWidget; +typedef struct _GeditFileBrowserWidgetClass GeditFileBrowserWidgetClass; +typedef struct _GeditFileBrowserWidgetPrivate GeditFileBrowserWidgetPrivate; + +typedef +gboolean (*GeditFileBrowserWidgetFilterFunc) (GeditFileBrowserWidget * obj, + GeditFileBrowserStore * + model, GtkTreeIter * iter, + gpointer user_data); + +struct _GeditFileBrowserWidget +{ + GtkVBox parent; + + GeditFileBrowserWidgetPrivate *priv; +}; + +struct _GeditFileBrowserWidgetClass +{ + GtkVBoxClass parent_class; + + /* Signals */ + void (*uri_activated) (GeditFileBrowserWidget * widget, + gchar const *uri); + void (*error) (GeditFileBrowserWidget * widget, + guint code, + gchar const *message); + gboolean (*confirm_delete) (GeditFileBrowserWidget * widget, + GeditFileBrowserStore * model, + GList *list); + gboolean (*confirm_no_trash) (GeditFileBrowserWidget * widget, + GList *list); +}; + +GType gedit_file_browser_widget_get_type (void) G_GNUC_CONST; +GType gedit_file_browser_widget_register_type (GTypeModule * module); + +GtkWidget *gedit_file_browser_widget_new (const gchar *data_dir); + +void gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget * obj); +void gedit_file_browser_widget_show_files (GeditFileBrowserWidget * obj); + +void gedit_file_browser_widget_set_root (GeditFileBrowserWidget * obj, + gchar const *root, + gboolean virtual_root); +void +gedit_file_browser_widget_set_root_and_virtual_root (GeditFileBrowserWidget * obj, + gchar const *root, + gchar const *virtual_root); + +gboolean +gedit_file_browser_widget_get_selected_directory (GeditFileBrowserWidget * obj, + GtkTreeIter * iter); + +GeditFileBrowserStore * +gedit_file_browser_widget_get_browser_store (GeditFileBrowserWidget * obj); +GeditFileBookmarksStore * +gedit_file_browser_widget_get_bookmarks_store (GeditFileBrowserWidget * obj); +GeditFileBrowserView * +gedit_file_browser_widget_get_browser_view (GeditFileBrowserWidget * obj); +GtkWidget * +gedit_file_browser_widget_get_filter_entry (GeditFileBrowserWidget * obj); + +GtkUIManager * +gedit_file_browser_widget_get_ui_manager (GeditFileBrowserWidget * obj); + +gulong gedit_file_browser_widget_add_filter (GeditFileBrowserWidget * obj, + GeditFileBrowserWidgetFilterFunc func, + gpointer user_data, + GDestroyNotify notify); +void gedit_file_browser_widget_remove_filter (GeditFileBrowserWidget * obj, + gulong id); +void gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget * obj, + gchar const *pattern); + +void gedit_file_browser_widget_refresh (GeditFileBrowserWidget * obj); +void gedit_file_browser_widget_history_back (GeditFileBrowserWidget * obj); +void gedit_file_browser_widget_history_forward (GeditFileBrowserWidget * obj); + +G_END_DECLS +#endif /* __GEDIT_FILE_BROWSER_WIDGET_H__ */ + +// ex:ts=8:noet: diff --git a/plugins/filebrowser/gedit-file-browser.schemas.in b/plugins/filebrowser/gedit-file-browser.schemas.in new file mode 100755 index 00000000..c80c8eec --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser.schemas.in @@ -0,0 +1,97 @@ + + + + /schemas/apps/gedit-2/plugins/filebrowser/on_load/tree_view + /apps/gedit-2/plugins/filebrowser/on_load/tree_view + gedit + bool + TRUE + + Open With Tree View + Open the tree view when the file browser plugin gets loaded instead of the bookmarks view + + + + + /schemas/apps/gedit-2/plugins/filebrowser/on_load/root + /apps/gedit-2/plugins/filebrowser/on_load/root + gedit + string + + + File Browser Root Directory + The file browser root directory to use when loading the file + browser plugin and onload/tree_view is TRUE. + + + + + /schemas/apps/gedit-2/plugins/filebrowser/on_load/virtual_root + /apps/gedit-2/plugins/filebrowser/on_load/virtual_root + gedit + string + + + File Browser Virtual Root Directory + The file browser virtual root directory to use when loading the + file browser plugin when onload/tree_view is TRUE. The virtual root + must always be below the actual root. + + + + + /schemas/apps/gedit-2/plugins/filebrowser/on_load/enable_remote + /apps/gedit-2/plugins/filebrowser/on_load/enable_remote + gedit + bool + FALSE + + Enable Restore of Remote Locations + Sets whether to enable restoring of remote locations. + + + + + /schemas/apps/gedit-2/plugins/filebrowser/open_at_first_doc + /apps/gedit-2/plugins/filebrowser/open_at_first_doc + gedit + bool + TRUE + + Set Location to First Document + If TRUE the file browser plugin will view the directory of + the first opened document given that the file browser hasn't been + used yet. (Thus this generally applies to opening a document from + the command line or opening it with Caja, etc.) + + + + + /schemas/apps/gedit-2/plugins/filebrowser/filter_mode + /apps/gedit-2/plugins/filebrowser/filter_mode + gedit + string + hidden_and_binary + + File Browser Filter Mode + This value determines what files get filtered from the file + browser. Valid values are: none (filter nothing), + hidden (filter hidden files), binary (filter binary files) and + hidden_and_binary (filter both hidden and binary files). + + + + + /schemas/apps/gedit-2/plugins/filebrowser/filter_pattern + /apps/gedit-2/plugins/filebrowser/filter_pattern + gedit + string + + + File Browser Filter Pattern + The filter pattern to filter the file browser with. This filter + works on top of the filter_mode. + + + + -- cgit v1.2.1