From 528c1e5ff51e213936e800fc5a9a25da99c0bdf2 Mon Sep 17 00:00:00 2001 From: Perberos Date: Mon, 7 Nov 2011 16:46:58 -0300 Subject: initial --- plugins/Makefile.am | 42 + plugins/changecase/Makefile.am | 34 + .../changecase/changecase.gedit-plugin.desktop.in | 8 + plugins/changecase/gedit-changecase-plugin.c | 395 +++ plugins/changecase/gedit-changecase-plugin.h | 72 + plugins/checkupdate/Makefile.am | 49 + .../checkupdate.gedit-plugin.desktop.in | 9 + plugins/checkupdate/gedit-check-update-plugin.c | 697 ++++ plugins/checkupdate/gedit-check-update-plugin.h | 74 + plugins/checkupdate/gedit-check-update.schemas.in | 13 + plugins/docinfo/Makefile.am | 34 + plugins/docinfo/docinfo.gedit-plugin.desktop.in | 7 + plugins/docinfo/docinfo.ui | 621 ++++ plugins/docinfo/gedit-docinfo-plugin.c | 580 ++++ plugins/docinfo/gedit-docinfo-plugin.h | 75 + plugins/externaltools/Makefile.am | 15 + plugins/externaltools/data/Makefile.am | 65 + plugins/externaltools/data/build.desktop.in | 9 + plugins/externaltools/data/build.tool.in | 15 + .../data/open-terminal-here-osx.desktop.in | 8 + .../data/open-terminal-here-osx.tool.in | 16 + .../data/open-terminal-here.desktop.in | 8 + .../externaltools/data/open-terminal-here.tool.in | 4 + .../data/remove-trailing-spaces.desktop.in | 9 + .../data/remove-trailing-spaces.tool.in | 3 + plugins/externaltools/data/run-command.desktop.in | 8 + plugins/externaltools/data/run-command.tool.in | 4 + .../externaltools.gedit-plugin.desktop.in | 9 + plugins/externaltools/scripts/Makefile.am | 4 + plugins/externaltools/scripts/gedit-tool-merge.pl | 78 + plugins/externaltools/tools/Makefile.am | 23 + plugins/externaltools/tools/__init__.py | 281 ++ plugins/externaltools/tools/capture.py | 214 ++ plugins/externaltools/tools/filelookup.py | 145 + plugins/externaltools/tools/functions.py | 303 ++ plugins/externaltools/tools/library.py | 493 +++ plugins/externaltools/tools/linkparsing.py | 231 ++ plugins/externaltools/tools/manager.py | 948 +++++ plugins/externaltools/tools/outputpanel.py | 224 ++ plugins/externaltools/tools/outputpanel.ui | 53 + plugins/externaltools/tools/tools.ui | 606 ++++ 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 + plugins/modelines/Makefile.am | 38 + plugins/modelines/gedit-modeline-plugin.c | 248 ++ plugins/modelines/gedit-modeline-plugin.h | 48 + plugins/modelines/language-mappings | 14 + plugins/modelines/modeline-parser.c | 852 +++++ plugins/modelines/modeline-parser.h | 37 + .../modelines/modelines.gedit-plugin.desktop.in | 8 + plugins/pythonconsole/Makefile.am | 15 + .../pythonconsole.gedit-plugin.desktop.in | 10 + plugins/pythonconsole/pythonconsole/Makefile.am | 17 + plugins/pythonconsole/pythonconsole/__init__.py | 78 + plugins/pythonconsole/pythonconsole/config.py | 134 + plugins/pythonconsole/pythonconsole/config.ui | 107 + plugins/pythonconsole/pythonconsole/console.py | 370 ++ plugins/quickopen/Makefile.am | 15 + .../quickopen/quickopen.gedit-plugin.desktop.in | 10 + plugins/quickopen/quickopen/Makefile.am | 13 + plugins/quickopen/quickopen/__init__.py | 46 + plugins/quickopen/quickopen/popup.py | 534 +++ plugins/quickopen/quickopen/virtualdirs.py | 87 + plugins/quickopen/quickopen/windowhelper.py | 198 ++ plugins/snippets/Makefile.am | 15 + plugins/snippets/data/Makefile.am | 33 + plugins/snippets/data/c.xml | 283 ++ plugins/snippets/data/chdr.xml | 241 ++ plugins/snippets/data/cpp.xml | 183 + plugins/snippets/data/css.xml | 557 +++ plugins/snippets/data/docbook.xml | 118 + plugins/snippets/data/fortran.xml | 164 + plugins/snippets/data/global.xml | 2 + plugins/snippets/data/haskell.xml | 14 + plugins/snippets/data/html.xml | 246 ++ plugins/snippets/data/idl.xml | 49 + plugins/snippets/data/java.xml | 91 + plugins/snippets/data/javascript.xml | 11 + plugins/snippets/data/lang/Makefile.am | 9 + plugins/snippets/data/lang/snippets.lang | 162 + plugins/snippets/data/latex.xml | 38 + plugins/snippets/data/mallard.xml | 207 ++ plugins/snippets/data/perl.xml | 126 + plugins/snippets/data/php.xml | 224 ++ plugins/snippets/data/python.xml | 112 + plugins/snippets/data/ruby.xml | 166 + plugins/snippets/data/sh.xml | 47 + plugins/snippets/data/snippets.xml | 98 + plugins/snippets/data/tcl.xml | 55 + plugins/snippets/data/xml.xml | 25 + plugins/snippets/data/xslt.xml | 143 + plugins/snippets/snippets.gedit-plugin.desktop.in | 9 + plugins/snippets/snippets/Completion.py | 165 + plugins/snippets/snippets/Document.py | 1089 ++++++ plugins/snippets/snippets/Exporter.py | 98 + plugins/snippets/snippets/Helper.py | 182 + plugins/snippets/snippets/Importer.py | 100 + plugins/snippets/snippets/LanguageManager.py | 21 + plugins/snippets/snippets/Library.py | 993 ++++++ plugins/snippets/snippets/Makefile.am | 28 + plugins/snippets/snippets/Manager.py | 1157 +++++++ plugins/snippets/snippets/Parser.py | 259 ++ plugins/snippets/snippets/Placeholder.py | 700 ++++ plugins/snippets/snippets/Snippet.py | 355 ++ plugins/snippets/snippets/SubstitutionParser.py | 202 ++ plugins/snippets/snippets/WindowHelper.py | 209 ++ plugins/snippets/snippets/__init__.py | 101 + plugins/snippets/snippets/snippets.ui | 647 ++++ plugins/sort/Makefile.am | 34 + plugins/sort/gedit-sort-plugin.c | 588 ++++ plugins/sort/gedit-sort-plugin.h | 73 + plugins/sort/sort.gedit-plugin.desktop.in | 9 + plugins/sort/sort.ui | 275 ++ plugins/spell/Makefile.am | 63 + plugins/spell/gedit-automatic-spell-checker.c | 1015 ++++++ plugins/spell/gedit-automatic-spell-checker.h | 67 + plugins/spell/gedit-spell-checker-dialog.c | 722 ++++ plugins/spell/gedit-spell-checker-dialog.h | 92 + plugins/spell/gedit-spell-checker-language.c | 439 +++ plugins/spell/gedit-spell-checker-language.h | 51 + plugins/spell/gedit-spell-checker.c | 520 +++ plugins/spell/gedit-spell-checker.h | 109 + plugins/spell/gedit-spell-language-dialog.c | 309 ++ plugins/spell/gedit-spell-language-dialog.h | 67 + plugins/spell/gedit-spell-marshal.list | 6 + plugins/spell/gedit-spell-plugin.c | 1217 +++++++ plugins/spell/gedit-spell-plugin.h | 75 + plugins/spell/gedit-spell-utils.c | 94 + plugins/spell/gedit-spell-utils.h | 37 + plugins/spell/languages-dialog.ui | 145 + plugins/spell/spell-checker.ui | 482 +++ plugins/spell/spell.gedit-plugin.desktop.in | 9 + plugins/taglist/HTML.tags.xml.in | 2672 +++++++++++++++ plugins/taglist/Latex.tags.xml.in | 344 ++ plugins/taglist/Makefile.am | 60 + plugins/taglist/XSLT.tags.xml.in | 337 ++ plugins/taglist/XUL.tags.xml.in | 536 +++ plugins/taglist/gedit-taglist-plugin-panel.c | 776 +++++ plugins/taglist/gedit-taglist-plugin-panel.h | 89 + plugins/taglist/gedit-taglist-plugin-parser.c | 655 ++++ plugins/taglist/gedit-taglist-plugin-parser.h | 68 + plugins/taglist/gedit-taglist-plugin.c | 160 + plugins/taglist/gedit-taglist-plugin.h | 85 + plugins/taglist/taglist.gedit-plugin.desktop.in | 8 + plugins/time/Makefile.am | 36 + plugins/time/gedit-time-dialog.ui | 297 ++ plugins/time/gedit-time-plugin.c | 1272 +++++++ plugins/time/gedit-time-plugin.h | 78 + plugins/time/gedit-time-setup-dialog.ui | 330 ++ plugins/time/time.gedit-plugin.desktop.in | 8 + 171 files changed, 45829 insertions(+) create mode 100755 plugins/Makefile.am create mode 100755 plugins/changecase/Makefile.am create mode 100755 plugins/changecase/changecase.gedit-plugin.desktop.in create mode 100755 plugins/changecase/gedit-changecase-plugin.c create mode 100755 plugins/changecase/gedit-changecase-plugin.h create mode 100755 plugins/checkupdate/Makefile.am create mode 100755 plugins/checkupdate/checkupdate.gedit-plugin.desktop.in create mode 100755 plugins/checkupdate/gedit-check-update-plugin.c create mode 100755 plugins/checkupdate/gedit-check-update-plugin.h create mode 100755 plugins/checkupdate/gedit-check-update.schemas.in create mode 100755 plugins/docinfo/Makefile.am create mode 100755 plugins/docinfo/docinfo.gedit-plugin.desktop.in create mode 100755 plugins/docinfo/docinfo.ui create mode 100755 plugins/docinfo/gedit-docinfo-plugin.c create mode 100755 plugins/docinfo/gedit-docinfo-plugin.h create mode 100755 plugins/externaltools/Makefile.am create mode 100755 plugins/externaltools/data/Makefile.am create mode 100755 plugins/externaltools/data/build.desktop.in create mode 100755 plugins/externaltools/data/build.tool.in create mode 100755 plugins/externaltools/data/open-terminal-here-osx.desktop.in create mode 100755 plugins/externaltools/data/open-terminal-here-osx.tool.in create mode 100755 plugins/externaltools/data/open-terminal-here.desktop.in create mode 100755 plugins/externaltools/data/open-terminal-here.tool.in create mode 100755 plugins/externaltools/data/remove-trailing-spaces.desktop.in create mode 100755 plugins/externaltools/data/remove-trailing-spaces.tool.in create mode 100755 plugins/externaltools/data/run-command.desktop.in create mode 100755 plugins/externaltools/data/run-command.tool.in create mode 100755 plugins/externaltools/externaltools.gedit-plugin.desktop.in create mode 100755 plugins/externaltools/scripts/Makefile.am create mode 100755 plugins/externaltools/scripts/gedit-tool-merge.pl create mode 100755 plugins/externaltools/tools/Makefile.am create mode 100755 plugins/externaltools/tools/__init__.py create mode 100755 plugins/externaltools/tools/capture.py create mode 100755 plugins/externaltools/tools/filelookup.py create mode 100755 plugins/externaltools/tools/functions.py create mode 100755 plugins/externaltools/tools/library.py create mode 100755 plugins/externaltools/tools/linkparsing.py create mode 100755 plugins/externaltools/tools/manager.py create mode 100755 plugins/externaltools/tools/outputpanel.py create mode 100755 plugins/externaltools/tools/outputpanel.ui create mode 100755 plugins/externaltools/tools/tools.ui 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 create mode 100755 plugins/modelines/Makefile.am create mode 100755 plugins/modelines/gedit-modeline-plugin.c create mode 100755 plugins/modelines/gedit-modeline-plugin.h create mode 100755 plugins/modelines/language-mappings create mode 100755 plugins/modelines/modeline-parser.c create mode 100755 plugins/modelines/modeline-parser.h create mode 100755 plugins/modelines/modelines.gedit-plugin.desktop.in create mode 100755 plugins/pythonconsole/Makefile.am create mode 100755 plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in create mode 100755 plugins/pythonconsole/pythonconsole/Makefile.am create mode 100755 plugins/pythonconsole/pythonconsole/__init__.py create mode 100755 plugins/pythonconsole/pythonconsole/config.py create mode 100755 plugins/pythonconsole/pythonconsole/config.ui create mode 100755 plugins/pythonconsole/pythonconsole/console.py create mode 100755 plugins/quickopen/Makefile.am create mode 100755 plugins/quickopen/quickopen.gedit-plugin.desktop.in create mode 100755 plugins/quickopen/quickopen/Makefile.am create mode 100755 plugins/quickopen/quickopen/__init__.py create mode 100755 plugins/quickopen/quickopen/popup.py create mode 100755 plugins/quickopen/quickopen/virtualdirs.py create mode 100755 plugins/quickopen/quickopen/windowhelper.py create mode 100755 plugins/snippets/Makefile.am create mode 100755 plugins/snippets/data/Makefile.am create mode 100755 plugins/snippets/data/c.xml create mode 100755 plugins/snippets/data/chdr.xml create mode 100755 plugins/snippets/data/cpp.xml create mode 100755 plugins/snippets/data/css.xml create mode 100755 plugins/snippets/data/docbook.xml create mode 100755 plugins/snippets/data/fortran.xml create mode 100755 plugins/snippets/data/global.xml create mode 100755 plugins/snippets/data/haskell.xml create mode 100755 plugins/snippets/data/html.xml create mode 100755 plugins/snippets/data/idl.xml create mode 100755 plugins/snippets/data/java.xml create mode 100755 plugins/snippets/data/javascript.xml create mode 100755 plugins/snippets/data/lang/Makefile.am create mode 100755 plugins/snippets/data/lang/snippets.lang create mode 100755 plugins/snippets/data/latex.xml create mode 100755 plugins/snippets/data/mallard.xml create mode 100755 plugins/snippets/data/perl.xml create mode 100755 plugins/snippets/data/php.xml create mode 100755 plugins/snippets/data/python.xml create mode 100755 plugins/snippets/data/ruby.xml create mode 100755 plugins/snippets/data/sh.xml create mode 100755 plugins/snippets/data/snippets.xml create mode 100755 plugins/snippets/data/tcl.xml create mode 100755 plugins/snippets/data/xml.xml create mode 100755 plugins/snippets/data/xslt.xml create mode 100755 plugins/snippets/snippets.gedit-plugin.desktop.in create mode 100755 plugins/snippets/snippets/Completion.py create mode 100755 plugins/snippets/snippets/Document.py create mode 100755 plugins/snippets/snippets/Exporter.py create mode 100755 plugins/snippets/snippets/Helper.py create mode 100755 plugins/snippets/snippets/Importer.py create mode 100755 plugins/snippets/snippets/LanguageManager.py create mode 100755 plugins/snippets/snippets/Library.py create mode 100755 plugins/snippets/snippets/Makefile.am create mode 100755 plugins/snippets/snippets/Manager.py create mode 100755 plugins/snippets/snippets/Parser.py create mode 100755 plugins/snippets/snippets/Placeholder.py create mode 100755 plugins/snippets/snippets/Snippet.py create mode 100755 plugins/snippets/snippets/SubstitutionParser.py create mode 100755 plugins/snippets/snippets/WindowHelper.py create mode 100755 plugins/snippets/snippets/__init__.py create mode 100755 plugins/snippets/snippets/snippets.ui create mode 100755 plugins/sort/Makefile.am create mode 100755 plugins/sort/gedit-sort-plugin.c create mode 100755 plugins/sort/gedit-sort-plugin.h create mode 100755 plugins/sort/sort.gedit-plugin.desktop.in create mode 100755 plugins/sort/sort.ui create mode 100755 plugins/spell/Makefile.am create mode 100755 plugins/spell/gedit-automatic-spell-checker.c create mode 100755 plugins/spell/gedit-automatic-spell-checker.h create mode 100755 plugins/spell/gedit-spell-checker-dialog.c create mode 100755 plugins/spell/gedit-spell-checker-dialog.h create mode 100755 plugins/spell/gedit-spell-checker-language.c create mode 100755 plugins/spell/gedit-spell-checker-language.h create mode 100755 plugins/spell/gedit-spell-checker.c create mode 100755 plugins/spell/gedit-spell-checker.h create mode 100755 plugins/spell/gedit-spell-language-dialog.c create mode 100755 plugins/spell/gedit-spell-language-dialog.h create mode 100755 plugins/spell/gedit-spell-marshal.list create mode 100755 plugins/spell/gedit-spell-plugin.c create mode 100755 plugins/spell/gedit-spell-plugin.h create mode 100755 plugins/spell/gedit-spell-utils.c create mode 100755 plugins/spell/gedit-spell-utils.h create mode 100755 plugins/spell/languages-dialog.ui create mode 100755 plugins/spell/spell-checker.ui create mode 100755 plugins/spell/spell.gedit-plugin.desktop.in create mode 100755 plugins/taglist/HTML.tags.xml.in create mode 100755 plugins/taglist/Latex.tags.xml.in create mode 100755 plugins/taglist/Makefile.am create mode 100755 plugins/taglist/XSLT.tags.xml.in create mode 100755 plugins/taglist/XUL.tags.xml.in create mode 100755 plugins/taglist/gedit-taglist-plugin-panel.c create mode 100755 plugins/taglist/gedit-taglist-plugin-panel.h create mode 100755 plugins/taglist/gedit-taglist-plugin-parser.c create mode 100755 plugins/taglist/gedit-taglist-plugin-parser.h create mode 100755 plugins/taglist/gedit-taglist-plugin.c create mode 100755 plugins/taglist/gedit-taglist-plugin.h create mode 100755 plugins/taglist/taglist.gedit-plugin.desktop.in create mode 100755 plugins/time/Makefile.am create mode 100755 plugins/time/gedit-time-dialog.ui create mode 100755 plugins/time/gedit-time-plugin.c create mode 100755 plugins/time/gedit-time-plugin.h create mode 100755 plugins/time/gedit-time-setup-dialog.ui create mode 100755 plugins/time/time.gedit-plugin.desktop.in (limited to 'plugins') diff --git a/plugins/Makefile.am b/plugins/Makefile.am new file mode 100755 index 00000000..ba80e090 --- /dev/null +++ b/plugins/Makefile.am @@ -0,0 +1,42 @@ +DIST_SUBDIRS = \ + changecase \ + checkupdate \ + docinfo \ + externaltools \ + filebrowser \ + modelines \ + pythonconsole \ + quickopen \ + snippets \ + sort \ + spell \ + taglist \ + time + +SUBDIRS = \ + changecase \ + docinfo \ + filebrowser \ + modelines \ + sort \ + taglist \ + time + +if ENABLE_PYTHON +SUBDIRS += pythonconsole snippets quickopen + +if !OS_WIN32 +SUBDIRS += externaltools +endif + +endif + +if ENABLE_ENCHANT +SUBDIRS += spell +endif + +if ENABLE_UPDATER +SUBDIRS += checkupdate +endif + +-include $(top_srcdir)/git.mk diff --git a/plugins/changecase/Makefile.am b/plugins/changecase/Makefile.am new file mode 100755 index 00000000..1f165f9e --- /dev/null +++ b/plugins/changecase/Makefile.am @@ -0,0 +1,34 @@ +# changecase plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libchangecase.la + +libchangecase_la_SOURCES = \ + gedit-changecase-plugin.h \ + gedit-changecase-plugin.c + +libchangecase_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libchangecase_la_LIBADD = $(GEDIT_LIBS) + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/changecase +ui_DATA = + +plugin_in_files = changecase.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 + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(ui_DATA) $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/changecase/changecase.gedit-plugin.desktop.in b/plugins/changecase/changecase.gedit-plugin.desktop.in new file mode 100755 index 00000000..52a226f4 --- /dev/null +++ b/plugins/changecase/changecase.gedit-plugin.desktop.in @@ -0,0 +1,8 @@ +[Gedit Plugin] +Module=changecase +IAge=2 +_Name=Change Case +_Description=Changes the case of selected text. +Authors=Paolo Borelli +Copyright=Copyright © 2004-2005 Paolo Borelli +Website=http://www.gedit.org diff --git a/plugins/changecase/gedit-changecase-plugin.c b/plugins/changecase/gedit-changecase-plugin.c new file mode 100755 index 00000000..8544aeb0 --- /dev/null +++ b/plugins/changecase/gedit-changecase-plugin.c @@ -0,0 +1,395 @@ +/* + * gedit-changecase-plugin.c + * + * Copyright (C) 2004-2005 - Paolo Borelli + * + * 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. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gedit-changecase-plugin.h" + +#include +#include + +#include + +#define WINDOW_DATA_KEY "GeditChangecasePluginWindowData" + +GEDIT_PLUGIN_REGISTER_TYPE(GeditChangecasePlugin, gedit_changecase_plugin) + +typedef enum { + TO_UPPER_CASE, + TO_LOWER_CASE, + INVERT_CASE, + TO_TITLE_CASE, +} ChangeCaseChoice; + +static void +do_upper_case (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GString *s = g_string_new (NULL); + + while (!gtk_text_iter_is_end (start) && + !gtk_text_iter_equal (start, end)) + { + gunichar c, nc; + + c = gtk_text_iter_get_char (start); + nc = g_unichar_toupper (c); + g_string_append_unichar (s, nc); + + gtk_text_iter_forward_char (start); + } + + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len); + + g_string_free (s, TRUE); +} + +static void +do_lower_case (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GString *s = g_string_new (NULL); + + while (!gtk_text_iter_is_end (start) && + !gtk_text_iter_equal (start, end)) + { + gunichar c, nc; + + c = gtk_text_iter_get_char (start); + nc = g_unichar_tolower (c); + g_string_append_unichar (s, nc); + + gtk_text_iter_forward_char (start); + } + + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len); + + g_string_free (s, TRUE); +} + +static void +do_invert_case (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GString *s = g_string_new (NULL); + + while (!gtk_text_iter_is_end (start) && + !gtk_text_iter_equal (start, end)) + { + gunichar c, nc; + + c = gtk_text_iter_get_char (start); + if (g_unichar_islower (c)) + nc = g_unichar_toupper (c); + else + nc = g_unichar_tolower (c); + g_string_append_unichar (s, nc); + + gtk_text_iter_forward_char (start); + } + + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len); + + g_string_free (s, TRUE); +} + +static void +do_title_case (GtkTextBuffer *buffer, + GtkTextIter *start, + GtkTextIter *end) +{ + GString *s = g_string_new (NULL); + + while (!gtk_text_iter_is_end (start) && + !gtk_text_iter_equal (start, end)) + { + gunichar c, nc; + + c = gtk_text_iter_get_char (start); + if (gtk_text_iter_starts_word (start)) + nc = g_unichar_totitle (c); + else + nc = g_unichar_tolower (c); + g_string_append_unichar (s, nc); + + gtk_text_iter_forward_char (start); + } + + gtk_text_buffer_delete_selection (buffer, TRUE, TRUE); + gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len); + + g_string_free (s, TRUE); +} + +static void +change_case (GeditWindow *window, + ChangeCaseChoice choice) +{ + GeditDocument *doc; + GtkTextIter start, end; + + gedit_debug (DEBUG_PLUGINS); + + doc = gedit_window_get_active_document (window); + g_return_if_fail (doc != NULL); + + if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), + &start, &end)) + { + return; + } + + gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (doc)); + + switch (choice) + { + case TO_UPPER_CASE: + do_upper_case (GTK_TEXT_BUFFER (doc), &start, &end); + break; + case TO_LOWER_CASE: + do_lower_case (GTK_TEXT_BUFFER (doc), &start, &end); + break; + case INVERT_CASE: + do_invert_case (GTK_TEXT_BUFFER (doc), &start, &end); + break; + case TO_TITLE_CASE: + do_title_case (GTK_TEXT_BUFFER (doc), &start, &end); + break; + default: + g_return_if_reached (); + } + + gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (doc)); +} + +static void +upper_case_cb (GtkAction *action, + GeditWindow *window) +{ + change_case (window, TO_UPPER_CASE); +} + +static void +lower_case_cb (GtkAction *action, + GeditWindow *window) +{ + change_case (window, TO_LOWER_CASE); +} + +static void +invert_case_cb (GtkAction *action, + GeditWindow *window) +{ + change_case (window, INVERT_CASE); +} + +static void +title_case_cb (GtkAction *action, + GeditWindow *window) +{ + change_case (window, TO_TITLE_CASE); +} + +static const GtkActionEntry action_entries[] = +{ + { "ChangeCase", NULL, N_("C_hange Case") }, + { "UpperCase", NULL, N_("All _Upper Case"), NULL, + N_("Change selected text to upper case"), + G_CALLBACK (upper_case_cb) }, + { "LowerCase", NULL, N_("All _Lower Case"), NULL, + N_("Change selected text to lower case"), + G_CALLBACK (lower_case_cb) }, + { "InvertCase", NULL, N_("_Invert Case"), NULL, + N_("Invert the case of selected text"), + G_CALLBACK (invert_case_cb) }, + { "TitleCase", NULL, N_("_Title Case"), NULL, + N_("Capitalize the first letter of each selected word"), + G_CALLBACK (title_case_cb) } +}; + +const gchar submenu[] = +"" +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +" " +""; + +static void +gedit_changecase_plugin_init (GeditChangecasePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditChangecasePlugin initializing"); +} + +static void +gedit_changecase_plugin_finalize (GObject *object) +{ + G_OBJECT_CLASS (gedit_changecase_plugin_parent_class)->finalize (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditChangecasePlugin finalizing"); +} + +typedef struct +{ + GtkActionGroup *action_group; + guint ui_id; +} WindowData; + +static void +free_window_data (WindowData *data) +{ + g_return_if_fail (data != NULL); + + g_slice_free (WindowData, data); +} + +static void +update_ui_real (GeditWindow *window, + WindowData *data) +{ + GtkTextView *view; + GtkAction *action; + gboolean sensitive = FALSE; + + gedit_debug (DEBUG_PLUGINS); + + view = GTK_TEXT_VIEW (gedit_window_get_active_view (window)); + + if (view != NULL) + { + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer (view); + sensitive = (gtk_text_view_get_editable (view) && + gtk_text_buffer_get_has_selection (buffer)); + } + + action = gtk_action_group_get_action (data->action_group, + "ChangeCase"); + gtk_action_set_sensitive (action, sensitive); +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + GError *error = NULL; + + gedit_debug (DEBUG_PLUGINS); + + data = g_slice_new (WindowData); + + manager = gedit_window_get_ui_manager (window); + + data->action_group = gtk_action_group_new ("GeditChangecasePluginActions"); + gtk_action_group_set_translation_domain (data->action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions (data->action_group, + action_entries, + G_N_ELEMENTS (action_entries), + window); + + gtk_ui_manager_insert_action_group (manager, data->action_group, -1); + + data->ui_id = gtk_ui_manager_add_ui_from_string (manager, + submenu, + -1, + &error); + if (data->ui_id == 0) + { + g_warning ("%s", error->message); + free_window_data (data); + return; + } + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + (GDestroyNotify) free_window_data); + + update_ui_real (window, data); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + manager = gedit_window_get_ui_manager (window); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + gtk_ui_manager_remove_ui (manager, data->ui_id); + gtk_ui_manager_remove_action_group (manager, data->action_group); + + g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL); +} + +static void +impl_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + update_ui_real (window, data); +} + +static void +gedit_changecase_plugin_class_init (GeditChangecasePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = gedit_changecase_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; +} diff --git a/plugins/changecase/gedit-changecase-plugin.h b/plugins/changecase/gedit-changecase-plugin.h new file mode 100755 index 00000000..9587928c --- /dev/null +++ b/plugins/changecase/gedit-changecase-plugin.h @@ -0,0 +1,72 @@ +/* + * gedit-changecase-plugin.h + * + * Copyright (C) 2004-2005 - Paolo Borelli + * + * 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. + * + * $Id$ + */ + +#ifndef __GEDIT_CHANGECASE_PLUGIN_H__ +#define __GEDIT_CHANGECASE_PLUGIN_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_CHANGECASE_PLUGIN (gedit_changecase_plugin_get_type ()) +#define GEDIT_CHANGECASE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePlugin)) +#define GEDIT_CHANGECASE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePluginClass)) +#define GEDIT_IS_CHANGECASE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_CHANGECASE_PLUGIN)) +#define GEDIT_IS_CHANGECASE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_CHANGECASE_PLUGIN)) +#define GEDIT_CHANGECASE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePluginClass)) + +/* + * Main object structure + */ +typedef struct _GeditChangecasePlugin GeditChangecasePlugin; + +struct _GeditChangecasePlugin +{ + GeditPlugin parent_instance; +}; + +/* + * Class definition + */ +typedef struct _GeditChangecasePluginClass GeditChangecasePluginClass; + +struct _GeditChangecasePluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_changecase_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_CHANGECASE_PLUGIN_H__ */ diff --git a/plugins/checkupdate/Makefile.am b/plugins/checkupdate/Makefile.am new file mode 100755 index 00000000..551b7eee --- /dev/null +++ b/plugins/checkupdate/Makefile.am @@ -0,0 +1,49 @@ +# gedit win32 updater + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(LIBSOUP_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libcheckupdate.la + +libcheckupdate_la_SOURCES = \ + gedit-check-update-plugin.h \ + gedit-check-update-plugin.c + +libcheckupdate_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libcheckupdate_la_LIBADD = $(GEDIT_LIBS) $(LIBSOUP_LIBS) + +plugin_in_files = checkupdate.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 + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +schemasdir = $(MATECONF_SCHEMA_FILE_DIR) +schemas_in_files = gedit-check-update.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/checkupdate/$$p ; \ + done \ + fi +else +install-data-local: +endif + +EXTRA_DIST = $(plugin_in_files) $(schemas_in_files) + +CLEANFILES = $(plugin_DATA) $(schemas_DATA) + +DISTCLEANFILES = $(plugin_DATA) $(schemas_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in b/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in new file mode 100755 index 00000000..4699b6a2 --- /dev/null +++ b/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in @@ -0,0 +1,9 @@ +[Gedit Plugin] +Module=checkupdate +IAge=2 +_Name=Check update +_Description=Check for latest version of gedit +Icon=gedit-plugin +Authors=Ignacio Casal Quinteiro +Copyright=Copyright © 2009 Ignacio Casal Quinteiro +Website=http://www.gedit.org diff --git a/plugins/checkupdate/gedit-check-update-plugin.c b/plugins/checkupdate/gedit-check-update-plugin.c new file mode 100755 index 00000000..aa9f7a5e --- /dev/null +++ b/plugins/checkupdate/gedit-check-update-plugin.c @@ -0,0 +1,697 @@ +/* + * Copyright (C) 2009 - Ignacio Casal Quinteiro + * + * 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 "gedit-check-update-plugin.h" + +#include +#include +#include +#include +#include +#include + +#include + +#if !GTK_CHECK_VERSION(2, 17, 1) +#include +#endif + +#define MATECONF_KEY_BASE "/apps/gedit-2/plugins/checkupdate" +#define MATECONF_KEY_IGNORE_VERSION MATECONF_KEY_BASE "/ignore_version" + +#define WINDOW_DATA_KEY "GeditCheckUpdatePluginWindowData" + +#define VERSION_PLACE "" + +#ifdef G_OS_WIN32 +#define GEDIT_URL "http://ftp.acc.umu.se/pub/mate/binaries/win32/gedit/" +#define FILE_REGEX "gedit\\-setup\\-[0-9]+\\.[0-9]+\\.[0-9]+(\\-[0-9]+)?\\.exe" +#else +#define GEDIT_URL "http://ftp.acc.umu.se/pub/mate/binaries/mac/gedit/" +#define FILE_REGEX "gedit\\-[0-9]+\\.[0-9]+\\.[0-9]+(\\-[0-9]+)?\\.dmg" +#endif + +#ifdef OS_OSX +#include "gedit/osx/gedit-osx.h" +#endif + +#define GEDIT_CHECK_UPDATE_PLUGIN_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), \ + GEDIT_TYPE_CHECK_UPDATE_PLUGIN, \ + GeditCheckUpdatePluginPrivate)) + +GEDIT_PLUGIN_REGISTER_TYPE (GeditCheckUpdatePlugin, gedit_check_update_plugin) + +struct _GeditCheckUpdatePluginPrivate +{ + SoupSession *session; + + MateConfClient *mateconf_client; +}; + +typedef struct +{ + GeditCheckUpdatePlugin *plugin; + + gchar *url; + gchar *version; +} WindowData; + +static void +free_window_data (gpointer data) +{ + WindowData *window_data; + + if (data == NULL) + return; + + window_data = (WindowData *)data; + + g_free (window_data->url); + g_free (window_data->version); + g_slice_free (WindowData, data); +} + +static void +gedit_check_update_plugin_init (GeditCheckUpdatePlugin *plugin) +{ + plugin->priv = GEDIT_CHECK_UPDATE_PLUGIN_GET_PRIVATE (plugin); + + gedit_debug_message (DEBUG_PLUGINS, + "GeditCheckUpdatePlugin initializing"); + + plugin->priv->session = soup_session_async_new (); + + plugin->priv->mateconf_client = mateconf_client_get_default (); + + mateconf_client_add_dir (plugin->priv->mateconf_client, + MATECONF_KEY_BASE, + MATECONF_CLIENT_PRELOAD_ONELEVEL, + NULL); +} + +static void +gedit_check_update_plugin_dispose (GObject *object) +{ + GeditCheckUpdatePlugin *plugin = GEDIT_CHECK_UPDATE_PLUGIN (object); + + if (plugin->priv->session != NULL) + { + g_object_unref (plugin->priv->session); + plugin->priv->session = NULL; + } + + if (plugin->priv->mateconf_client != NULL) + { + mateconf_client_suggest_sync (plugin->priv->mateconf_client, NULL); + + g_object_unref (G_OBJECT (plugin->priv->mateconf_client)); + + plugin->priv->mateconf_client = NULL; + } + + gedit_debug_message (DEBUG_PLUGINS, + "GeditCheckUpdatePlugin disposing"); + + G_OBJECT_CLASS (gedit_check_update_plugin_parent_class)->dispose (object); +} + +static void +gedit_check_update_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, + "GeditCheckUpdatePlugin finalizing"); + + G_OBJECT_CLASS (gedit_check_update_plugin_parent_class)->finalize (object); +} + +static void +set_contents (GtkWidget *infobar, + GtkWidget *contents) +{ +#if !GTK_CHECK_VERSION (2, 17, 1) + gedit_message_area_set_contents (GEDIT_MESSAGE_AREA (infobar), + contents); +#else + GtkWidget *content_area; + + content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar)); + gtk_container_add (GTK_CONTAINER (content_area), contents); +#endif +} + +static void +set_message_area_text_and_icon (GtkWidget *message_area, + const gchar *icon_stock_id, + const gchar *primary_text, + const gchar *secondary_text) +{ + GtkWidget *hbox_content; + GtkWidget *image; + GtkWidget *vbox; + gchar *primary_markup; + gchar *secondary_markup; + GtkWidget *primary_label; + GtkWidget *secondary_label; + + hbox_content = gtk_hbox_new (FALSE, 8); + gtk_widget_show (hbox_content); + + image = gtk_image_new_from_stock (icon_stock_id, GTK_ICON_SIZE_DIALOG); + gtk_widget_show (image); + gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0); + + primary_markup = g_strdup_printf ("%s", primary_text); + primary_label = gtk_label_new (primary_markup); + g_free (primary_markup); + gtk_widget_show (primary_label); + gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0); + gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE); + gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5); + GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS); + gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE); + + if (secondary_text != NULL) + { + secondary_markup = g_strdup_printf ("%s", + secondary_text); + secondary_label = gtk_label_new (secondary_markup); + g_free (secondary_markup); + gtk_widget_show (secondary_label); + gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0); + GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS); + gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE); + gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE); + gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE); + gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5); + } + + set_contents (message_area, hbox_content); +} + +static void +on_response_cb (GtkWidget *infobar, + gint response_id, + GeditWindow *window) +{ + if (response_id == GTK_RESPONSE_YES) + { + GError *error = NULL; + WindowData *data; + + data = g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + +#ifdef OS_OSX + gedit_osx_show_url (data->url); +#else + gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (window)), + data->url, + GDK_CURRENT_TIME, + &error); +#endif + if (error != NULL) + { + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("There was an error displaying the URI.")); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", error->message); + + g_signal_connect (G_OBJECT (dialog), + "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + + gtk_widget_show (dialog); + + g_error_free (error); + } + } + else if (response_id == GTK_RESPONSE_NO) + { + WindowData *data; + + data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + + mateconf_client_set_string (data->plugin->priv->mateconf_client, + MATECONF_KEY_IGNORE_VERSION, + data->version, + NULL); + } + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); + + gtk_widget_destroy (infobar); +} + +static GtkWidget * +create_infobar (GeditWindow *window, + const gchar *version) +{ + GtkWidget *infobar; + gchar *message; + +#if !GTK_CHECK_VERSION (2, 17, 1) + infobar = gedit_message_area_new (); + + gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (infobar), + _("_Download"), + GTK_STOCK_SAVE, + GTK_RESPONSE_YES); + gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (infobar), + _("_Ignore Version"), + GTK_STOCK_DISCARD, + GTK_RESPONSE_NO); + gedit_message_area_add_button (GEDIT_MESSAGE_AREA (infobar), + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL); +#else + GtkWidget *button; + + infobar = gtk_info_bar_new (); + + button = gedit_gtk_button_new_with_stock_icon (_("_Download"), + GTK_STOCK_SAVE); + gtk_widget_show (button); + + gtk_info_bar_add_action_widget (GTK_INFO_BAR (infobar), + button, + GTK_RESPONSE_YES); + + button = gedit_gtk_button_new_with_stock_icon (_("_Ignore Version"), + GTK_STOCK_DISCARD); + gtk_widget_show (button); + + gtk_info_bar_add_action_widget (GTK_INFO_BAR (infobar), + button, + GTK_RESPONSE_NO); + + gtk_info_bar_add_button (GTK_INFO_BAR (infobar), + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL); + + gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar), + GTK_MESSAGE_INFO); +#endif + + message = g_strdup_printf ("%s (%s)", _("There is a new version of gedit"), version); + set_message_area_text_and_icon (infobar, + "gtk-dialog-info", + message, + _("You can download the new version of gedit" + " by clicking on the download button or" + " ignore that version and wait for a new one")); + + g_free (message); + + g_signal_connect (infobar, "response", + G_CALLBACK (on_response_cb), + window); + + return infobar; +} + +static void +pack_infobar (GtkWidget *window, + GtkWidget *infobar) +{ + GtkWidget *vbox; + + vbox = gtk_bin_get_child (GTK_BIN (window)); + + gtk_box_pack_start (GTK_BOX (vbox), infobar, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (vbox), infobar, 2); +} + +static gchar * +get_file (const gchar *text, + const gchar *regex_place) +{ + GRegex *regex; + GMatchInfo *match_info; + gchar *word = NULL; + + regex = g_regex_new (regex_place, 0, 0, NULL); + g_regex_match (regex, text, 0, &match_info); + while (g_match_info_matches (match_info)) + { + g_free (word); + + word = g_match_info_fetch (match_info, 0); + + g_match_info_next (match_info, NULL); + } + g_match_info_free (match_info); + g_regex_unref (regex); + + return word; +} + +static void +get_numbers (const gchar *version, + gint *major, + gint *minor, + gint *micro) +{ + gchar **split; + gint num = 2; + + if (micro != NULL) + num = 3; + + split = g_strsplit (version, ".", num); + *major = atoi (split[0]); + *minor = atoi (split[1]); + if (micro != NULL) + *micro = atoi (split[2]); + + g_strfreev (split); +} + +static gboolean +newer_version (const gchar *v1, + const gchar *v2, + gboolean with_micro) +{ + gboolean newer = FALSE; + gint major1, minor1, micro1; + gint major2, minor2, micro2; + + if (v1 == NULL || v2 == NULL) + return FALSE; + + if (with_micro) + { + get_numbers (v1, &major1, &minor1, µ1); + get_numbers (v2, &major2, &minor2, µ2); + } + else + { + get_numbers (v1, &major1, &minor1, NULL); + get_numbers (v2, &major2, &minor2, NULL); + } + + if (major1 > major2) + { + newer = TRUE; + } + else if (minor1 > minor2 && major1 == major2) + { + newer = TRUE; + } + else if (with_micro && micro1 > micro2 && minor1 == minor2) + { + newer = TRUE; + } + + return newer; +} + +static gchar * +parse_file_version (const gchar *file) +{ + gchar *p, *aux; + + p = (gchar *)file; + + while (*p != '\0' && !g_ascii_isdigit (*p)) + { + p++; + } + + if (*p == '\0') + return NULL; + + aux = g_strrstr (p, "-"); + if (aux == NULL) + aux = g_strrstr (p, "."); + + return g_strndup (p, aux - p); +} + +static gchar * +get_ignore_version (GeditCheckUpdatePlugin *plugin) +{ + return mateconf_client_get_string (plugin->priv->mateconf_client, + MATECONF_KEY_IGNORE_VERSION, + NULL); +} + +static void +parse_page_file (SoupSession *session, + SoupMessage *msg, + GeditWindow *window) +{ + if (msg->status_code == SOUP_STATUS_OK) + { + gchar *file; + gchar *file_version; + gchar *ignore_version; + WindowData *data; + + data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + + file = get_file (msg->response_body->data, FILE_REGEX); + file_version = parse_file_version (file); + ignore_version = get_ignore_version (data->plugin); + + if (newer_version (file_version, VERSION, TRUE) && + (ignore_version == NULL || *ignore_version == '\0' || + newer_version (file_version, ignore_version, TRUE))) + { + GtkWidget *infobar; + WindowData *data; + gchar *file_url; + + data = g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + + file_url = g_strconcat (data->url, file, NULL); + + g_free (data->url); + data->url = file_url; + data->version = g_strdup (file_version); + + infobar = create_infobar (window, file_version); + pack_infobar (GTK_WIDGET (window), infobar); + gtk_widget_show (infobar); + } + + g_free (ignore_version); + g_free (file_version); + g_free (file); + } + else + { + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); + } +} + +static gboolean +is_unstable (const gchar *version) +{ + gchar **split; + gint minor; + gboolean unstable = TRUE;; + + split = g_strsplit (version, ".", 2); + minor = atoi (split[1]); + g_strfreev (split); + + if ((minor % 2) == 0) + unstable = FALSE; + + return unstable; +} + +static gchar * +get_file_page_version (const gchar *text, + const gchar *regex_place) +{ + GRegex *regex; + GMatchInfo *match_info; + GString *string = NULL; + gchar *unstable = NULL; + gchar *stable = NULL; + + regex = g_regex_new (regex_place, 0, 0, NULL); + g_regex_match (regex, text, 0, &match_info); + while (g_match_info_matches (match_info)) + { + gint end; + gint i; + + g_match_info_fetch_pos (match_info, 0, NULL, &end); + + string = g_string_new (""); + + i = end; + while (text[i] != '/') + { + string = g_string_append_c (string, text[i]); + i++; + } + + if (is_unstable (string->str)) + { + g_free (unstable); + unstable = g_string_free (string, FALSE); + } + else + { + g_free (stable); + stable = g_string_free (string, FALSE); + } + + g_match_info_next (match_info, NULL); + } + g_match_info_free (match_info); + g_regex_unref (regex); + + if ((GEDIT_MINOR_VERSION % 2) == 0) + { + g_free (unstable); + + return stable; + } + else + { + /* We need to check that stable isn't newer than unstable */ + if (newer_version (stable, unstable, FALSE)) + { + g_free (unstable); + + return stable; + } + else + { + g_free (stable); + + return unstable; + } + } +} + +static void +parse_page_version (SoupSession *session, + SoupMessage *msg, + GeditWindow *window) +{ + if (msg->status_code == SOUP_STATUS_OK) + { + gchar *version; + SoupMessage *msg2; + WindowData *data; + + data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY); + + version = get_file_page_version (msg->response_body->data, + VERSION_PLACE); + + data->url = g_strconcat (GEDIT_URL, version, "/", NULL); + g_free (version); + msg2 = soup_message_new ("GET", data->url); + + soup_session_queue_message (session, msg2, + (SoupSessionCallback)parse_page_file, + window); + } + else + { + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); + } +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + SoupMessage *msg; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = g_slice_new (WindowData); + data->plugin = GEDIT_CHECK_UPDATE_PLUGIN (plugin); + data->url = NULL; + data->version = NULL; + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + free_window_data); + + msg = soup_message_new ("GET", GEDIT_URL); + + soup_session_queue_message (GEDIT_CHECK_UPDATE_PLUGIN (plugin)->priv->session, msg, + (SoupSessionCallback)parse_page_version, + window); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + + gedit_debug (DEBUG_PLUGINS); + + soup_session_abort (GEDIT_CHECK_UPDATE_PLUGIN (plugin)->priv->session); + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); +} + +static void +gedit_check_update_plugin_class_init (GeditCheckUpdatePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (GeditCheckUpdatePluginPrivate)); + + object_class->finalize = gedit_check_update_plugin_finalize; + object_class->dispose = gedit_check_update_plugin_dispose; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; +} diff --git a/plugins/checkupdate/gedit-check-update-plugin.h b/plugins/checkupdate/gedit-check-update-plugin.h new file mode 100755 index 00000000..68dc7f97 --- /dev/null +++ b/plugins/checkupdate/gedit-check-update-plugin.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 - Ignacio Casal Quinteiro + * + * 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_CHECK_UPDATE_PLUGIN_H__ +#define __GEDIT_CHECK_UPDATE_PLUGIN_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_CHECK_UPDATE_PLUGIN (gedit_check_update_plugin_get_type ()) +#define GEDIT_CHECK_UPDATE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePlugin)) +#define GEDIT_CHECK_UPDATE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePluginClass)) +#define IS_GEDIT_CHECK_UPDATE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN)) +#define IS_GEDIT_CHECK_UPDATE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_CHECK_UPDATE_PLUGIN)) +#define GEDIT_CHECK_UPDATE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePluginClass)) + +/* Private structure type */ +typedef struct _GeditCheckUpdatePluginPrivate GeditCheckUpdatePluginPrivate; + +/* + * Main object structure + */ +typedef struct _GeditCheckUpdatePlugin GeditCheckUpdatePlugin; + +struct _GeditCheckUpdatePlugin +{ + GeditPlugin parent_instance; + + /*< private >*/ + GeditCheckUpdatePluginPrivate *priv; +}; + +/* + * Class definition + */ +typedef struct _GeditCheckUpdatePluginClass GeditCheckUpdatePluginClass; + +struct _GeditCheckUpdatePluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_check_update_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_CHECK_UPDATE_PLUGIN_H__ */ diff --git a/plugins/checkupdate/gedit-check-update.schemas.in b/plugins/checkupdate/gedit-check-update.schemas.in new file mode 100755 index 00000000..67bc892b --- /dev/null +++ b/plugins/checkupdate/gedit-check-update.schemas.in @@ -0,0 +1,13 @@ + + + + /schemas/apps/gedit-2/plugins/checkupdate/ignore_version + /apps/gedit-2/plugins/checkupdate/ignore_version + gedit + string + + Version to ignore until the next version is released + + + + diff --git a/plugins/docinfo/Makefile.am b/plugins/docinfo/Makefile.am new file mode 100755 index 00000000..edf2909c --- /dev/null +++ b/plugins/docinfo/Makefile.am @@ -0,0 +1,34 @@ +# docinfo plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +plugin_LTLIBRARIES = libdocinfo.la + +libdocinfo_la_SOURCES = \ + gedit-docinfo-plugin.h \ + gedit-docinfo-plugin.c + +libdocinfo_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libdocinfo_la_LIBADD = $(GEDIT_LIBS) + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/docinfo +ui_DATA = docinfo.ui + +plugin_in_files = docinfo.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 + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(ui_DATA) $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/docinfo/docinfo.gedit-plugin.desktop.in b/plugins/docinfo/docinfo.gedit-plugin.desktop.in new file mode 100755 index 00000000..77f2793d --- /dev/null +++ b/plugins/docinfo/docinfo.gedit-plugin.desktop.in @@ -0,0 +1,7 @@ +[Gedit Plugin] +Module=docinfo +IAge=2 +_Name=Document Statistics +_Description=Analyzes the current document and reports the number of words, lines, characters and non-space characters in it. +Authors=Paolo Maggi ;Jorge Alberto Torres +Copyright=Copyright © 2002-2005 Paolo Maggi diff --git a/plugins/docinfo/docinfo.ui b/plugins/docinfo/docinfo.ui new file mode 100755 index 00000000..bb73d40f --- /dev/null +++ b/plugins/docinfo/docinfo.ui @@ -0,0 +1,621 @@ + + + + + 5 + Document Statistics + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + False + True + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + False + + + True + False + 8 + + + True + GTK_BUTTONBOX_END + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + + + + + _Update + True + True + True + True + False + update_image + + + False + False + 1 + + + + + 0 + False + True + GTK_PACK_END + + + + + 5 + True + False + 6 + + + True + File Name + False + True + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + + + + 0 + False + False + + + + + True + False + 0 + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + 6 + True + 6 + 2 + False + 6 + 18 + + + True + Bytes + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 5 + 6 + fill + + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 5 + 6 + fill + + + + + + True + Characters (no spaces) + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 4 + 5 + fill + + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 4 + 5 + fill + + + + + + True + Characters (with spaces) + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 3 + 4 + fill + + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 3 + 4 + fill + + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 2 + 3 + fill + + + + + + True + Words + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 2 + 3 + fill + + + + + + True + Lines + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 1 + 2 + fill + + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 1 + 2 + fill + + + + + + True + Document + True + True + GTK_JUSTIFY_CENTER + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 1 + 2 + 0 + 1 + fill + + + + + + 0 + True + True + + + + + 6 + True + False + 6 + + + True + Selection + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + True + 0 + False + False + GTK_JUSTIFY_LEFT + False + False + 1 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + 0 + True + True + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + close_button + update_button + + + + True + gtk-refresh + + diff --git a/plugins/docinfo/gedit-docinfo-plugin.c b/plugins/docinfo/gedit-docinfo-plugin.c new file mode 100755 index 00000000..a143a5a6 --- /dev/null +++ b/plugins/docinfo/gedit-docinfo-plugin.c @@ -0,0 +1,580 @@ +/* + * gedit-docinfo-plugin.c + * + * Copyright (C) 2002-2005 Paolo Maggi + * + * 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. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gedit-docinfo-plugin.h" + +#include /* For strlen (...) */ + +#include +#include +#include + +#include +#include + +#define WINDOW_DATA_KEY "GeditDocInfoWindowData" +#define MENU_PATH "/MenuBar/ToolsMenu/ToolsOps_2" + +GEDIT_PLUGIN_REGISTER_TYPE(GeditDocInfoPlugin, gedit_docinfo_plugin) + +typedef struct +{ + GtkWidget *dialog; + GtkWidget *file_name_label; + GtkWidget *lines_label; + GtkWidget *words_label; + GtkWidget *chars_label; + GtkWidget *chars_ns_label; + GtkWidget *bytes_label; + GtkWidget *selection_vbox; + GtkWidget *selected_lines_label; + GtkWidget *selected_words_label; + GtkWidget *selected_chars_label; + GtkWidget *selected_chars_ns_label; + GtkWidget *selected_bytes_label; +} DocInfoDialog; + +typedef struct +{ + GeditPlugin *plugin; + + GtkActionGroup *ui_action_group; + guint ui_id; + + DocInfoDialog *dialog; +} WindowData; + +static void docinfo_dialog_response_cb (GtkDialog *widget, + gint res_id, + GeditWindow *window); + +static void +docinfo_dialog_destroy_cb (GtkObject *obj, + WindowData *data) +{ + gedit_debug (DEBUG_PLUGINS); + + if (data != NULL) + { + g_free (data->dialog); + data->dialog = NULL; + } +} + +static DocInfoDialog * +get_docinfo_dialog (GeditWindow *window, + WindowData *data) +{ + DocInfoDialog *dialog; + gchar *data_dir; + gchar *ui_file; + GtkWidget *content; + GtkWidget *error_widget; + gboolean ret; + + gedit_debug (DEBUG_PLUGINS); + + dialog = g_new (DocInfoDialog, 1); + + data_dir = gedit_plugin_get_data_dir (data->plugin); + ui_file = g_build_filename (data_dir, "docinfo.ui", NULL); + ret = gedit_utils_get_ui_objects (ui_file, + NULL, + &error_widget, + "dialog", &dialog->dialog, + "docinfo_dialog_content", &content, + "file_name_label", &dialog->file_name_label, + "words_label", &dialog->words_label, + "bytes_label", &dialog->bytes_label, + "lines_label", &dialog->lines_label, + "chars_label", &dialog->chars_label, + "chars_ns_label", &dialog->chars_ns_label, + "selection_vbox", &dialog->selection_vbox, + "selected_words_label", &dialog->selected_words_label, + "selected_bytes_label", &dialog->selected_bytes_label, + "selected_lines_label", &dialog->selected_lines_label, + "selected_chars_label", &dialog->selected_chars_label, + "selected_chars_ns_label", &dialog->selected_chars_ns_label, + NULL); + + g_free (data_dir); + g_free (ui_file); + + if (!ret) + { + const gchar *err_message; + + err_message = gtk_label_get_label (GTK_LABEL (error_widget)); + gedit_warning (GTK_WINDOW (window), "%s", err_message); + + g_free (dialog); + gtk_widget_destroy (error_widget); + + return NULL; + } + + gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog), + GTK_RESPONSE_OK); + gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), + GTK_WINDOW (window)); + + g_signal_connect (dialog->dialog, + "destroy", + G_CALLBACK (docinfo_dialog_destroy_cb), + data); + g_signal_connect (dialog->dialog, + "response", + G_CALLBACK (docinfo_dialog_response_cb), + window); + + return dialog; +} + +static void +calculate_info (GeditDocument *doc, + GtkTextIter *start, + GtkTextIter *end, + gint *chars, + gint *words, + gint *white_chars, + gint *bytes) +{ + gchar *text; + + gedit_debug (DEBUG_PLUGINS); + + text = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc), + start, + end, + TRUE); + + *chars = g_utf8_strlen (text, -1); + *bytes = strlen (text); + + if (*chars > 0) + { + PangoLogAttr *attrs; + gint i; + + attrs = g_new0 (PangoLogAttr, *chars + 1); + + pango_get_log_attrs (text, + -1, + 0, + pango_language_from_string ("C"), + attrs, + *chars + 1); + + for (i = 0; i < (*chars); i++) + { + if (attrs[i].is_white) + ++(*white_chars); + + if (attrs[i].is_word_start) + ++(*words); + } + + g_free (attrs); + } + else + { + *white_chars = 0; + *words = 0; + } + + g_free (text); +} + +static void +docinfo_real (GeditDocument *doc, + DocInfoDialog *dialog) +{ + GtkTextIter start, end; + gint words = 0; + gint chars = 0; + gint white_chars = 0; + gint lines = 0; + gint bytes = 0; + gchar *tmp_str; + gchar *doc_name; + + gedit_debug (DEBUG_PLUGINS); + + gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc), + &start, + &end); + + lines = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (doc)); + + calculate_info (doc, + &start, &end, + &chars, &words, &white_chars, &bytes); + + if (chars == 0) + lines = 0; + + gedit_debug_message (DEBUG_PLUGINS, "Chars: %d", chars); + gedit_debug_message (DEBUG_PLUGINS, "Lines: %d", lines); + gedit_debug_message (DEBUG_PLUGINS, "Words: %d", words); + gedit_debug_message (DEBUG_PLUGINS, "Chars non-space: %d", chars - white_chars); + gedit_debug_message (DEBUG_PLUGINS, "Bytes: %d", bytes); + + doc_name = gedit_document_get_short_name_for_display (doc); + tmp_str = g_strdup_printf ("%s", doc_name); + gtk_label_set_markup (GTK_LABEL (dialog->file_name_label), tmp_str); + g_free (doc_name); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", lines); + gtk_label_set_text (GTK_LABEL (dialog->lines_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", words); + gtk_label_set_text (GTK_LABEL (dialog->words_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars); + gtk_label_set_text (GTK_LABEL (dialog->chars_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars - white_chars); + gtk_label_set_text (GTK_LABEL (dialog->chars_ns_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", bytes); + gtk_label_set_text (GTK_LABEL (dialog->bytes_label), tmp_str); + g_free (tmp_str); +} + +static void +selectioninfo_real (GeditDocument *doc, + DocInfoDialog *dialog) +{ + gboolean sel; + GtkTextIter start, end; + gint words = 0; + gint chars = 0; + gint white_chars = 0; + gint lines = 0; + gint bytes = 0; + gchar *tmp_str; + + gedit_debug (DEBUG_PLUGINS); + + sel = gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), + &start, + &end); + + if (sel) + { + lines = gtk_text_iter_get_line (&end) - gtk_text_iter_get_line (&start) + 1; + + calculate_info (doc, + &start, &end, + &chars, &words, &white_chars, &bytes); + + gedit_debug_message (DEBUG_PLUGINS, "Selected chars: %d", chars); + gedit_debug_message (DEBUG_PLUGINS, "Selected lines: %d", lines); + gedit_debug_message (DEBUG_PLUGINS, "Selected words: %d", words); + gedit_debug_message (DEBUG_PLUGINS, "Selected chars non-space: %d", chars - white_chars); + gedit_debug_message (DEBUG_PLUGINS, "Selected bytes: %d", bytes); + + gtk_widget_set_sensitive (dialog->selection_vbox, TRUE); + } + else + { + gtk_widget_set_sensitive (dialog->selection_vbox, FALSE); + + gedit_debug_message (DEBUG_PLUGINS, "Selection empty"); + } + + if (chars == 0) + lines = 0; + + tmp_str = g_strdup_printf("%d", lines); + gtk_label_set_text (GTK_LABEL (dialog->selected_lines_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", words); + gtk_label_set_text (GTK_LABEL (dialog->selected_words_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars); + gtk_label_set_text (GTK_LABEL (dialog->selected_chars_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars - white_chars); + gtk_label_set_text (GTK_LABEL (dialog->selected_chars_ns_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", bytes); + gtk_label_set_text (GTK_LABEL (dialog->selected_bytes_label), tmp_str); + g_free (tmp_str); +} + +static void +docinfo_cb (GtkAction *action, + GeditWindow *window) +{ + GeditDocument *doc; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + + doc = gedit_window_get_active_document (window); + g_return_if_fail (doc != NULL); + + if (data->dialog != NULL) + { + gtk_window_present (GTK_WINDOW (data->dialog->dialog)); + gtk_widget_grab_focus (GTK_WIDGET (data->dialog->dialog)); + } + else + { + DocInfoDialog *dialog; + + dialog = get_docinfo_dialog (window, data); + g_return_if_fail (dialog != NULL); + + data->dialog = dialog; + + gtk_widget_show (GTK_WIDGET (dialog->dialog)); + } + + docinfo_real (doc, + data->dialog); + selectioninfo_real (doc, + data->dialog); +} + +static void +docinfo_dialog_response_cb (GtkDialog *widget, + gint res_id, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + + switch (res_id) + { + case GTK_RESPONSE_CLOSE: + { + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CLOSE"); + gtk_widget_destroy (data->dialog->dialog); + + break; + } + + case GTK_RESPONSE_OK: + { + GeditDocument *doc; + + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK"); + + doc = gedit_window_get_active_document (window); + g_return_if_fail (doc != NULL); + + docinfo_real (doc, + data->dialog); + + selectioninfo_real (doc, + data->dialog); + + break; + } + } +} + +static const GtkActionEntry action_entries[] = +{ + { "DocumentStatistics", + NULL, + N_("_Document Statistics"), + NULL, + N_("Get statistical information on the current document"), + G_CALLBACK (docinfo_cb) } +}; + +static void +free_window_data (WindowData *data) +{ + g_return_if_fail (data != NULL); + + gedit_debug (DEBUG_PLUGINS); + + g_object_unref (data->plugin); + + g_object_unref (data->ui_action_group); + + if (data->dialog != NULL) + { + gtk_widget_destroy (data->dialog->dialog); + } + + g_free (data); +} + +static void +update_ui_real (GeditWindow *window, + WindowData *data) +{ + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + view = gedit_window_get_active_view (window); + + gtk_action_group_set_sensitive (data->ui_action_group, + (view != NULL)); + + if (data->dialog != NULL) + { + gtk_dialog_set_response_sensitive (GTK_DIALOG (data->dialog->dialog), + GTK_RESPONSE_OK, + (view != NULL)); + } +} + +static void +gedit_docinfo_plugin_init (GeditDocInfoPlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditDocInfoPlugin initializing"); +} + +static void +gedit_docinfo_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditDocInfoPlugin finalizing"); + + G_OBJECT_CLASS (gedit_docinfo_plugin_parent_class)->finalize (object); +} + +static void +impl_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = g_new (WindowData, 1); + + data->plugin = g_object_ref (plugin); + data->dialog = NULL; + data->ui_action_group = gtk_action_group_new ("GeditDocInfoPluginActions"); + + gtk_action_group_set_translation_domain (data->ui_action_group, + GETTEXT_PACKAGE); + gtk_action_group_add_actions (data->ui_action_group, + action_entries, + G_N_ELEMENTS (action_entries), + window); + + manager = gedit_window_get_ui_manager (window); + gtk_ui_manager_insert_action_group (manager, + data->ui_action_group, + -1); + + data->ui_id = gtk_ui_manager_new_merge_id (manager); + + g_object_set_data_full (G_OBJECT (window), + WINDOW_DATA_KEY, + data, + (GDestroyNotify) free_window_data); + + gtk_ui_manager_add_ui (manager, + data->ui_id, + MENU_PATH, + "DocumentStatistics", + "DocumentStatistics", + GTK_UI_MANAGER_MENUITEM, + FALSE); + + update_ui_real (window, + data); +} + +static void +impl_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + GtkUIManager *manager; + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + manager = gedit_window_get_ui_manager (window); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + gtk_ui_manager_remove_ui (manager, + data->ui_id); + gtk_ui_manager_remove_action_group (manager, + data->ui_action_group); + + g_object_set_data (G_OBJECT (window), + WINDOW_DATA_KEY, + NULL); +} + +static void +impl_update_ui (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *data; + + gedit_debug (DEBUG_PLUGINS); + + data = (WindowData *) g_object_get_data (G_OBJECT (window), + WINDOW_DATA_KEY); + g_return_if_fail (data != NULL); + + update_ui_real (window, + data); +} + +static void +gedit_docinfo_plugin_class_init (GeditDocInfoPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->finalize = gedit_docinfo_plugin_finalize; + + plugin_class->activate = impl_activate; + plugin_class->deactivate = impl_deactivate; + plugin_class->update_ui = impl_update_ui; +} diff --git a/plugins/docinfo/gedit-docinfo-plugin.h b/plugins/docinfo/gedit-docinfo-plugin.h new file mode 100755 index 00000000..36d6bddc --- /dev/null +++ b/plugins/docinfo/gedit-docinfo-plugin.h @@ -0,0 +1,75 @@ +/* + * gedit-docinfo-plugin.h + * + * Copyright (C) 2002-2005 Paolo Maggi + * + * 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. + * + * $Id$ + */ + +#ifndef __GEDIT_DOCINFO_PLUGIN_H__ +#define __GEDIT_DOCINFO_PLUGIN_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_DOCINFO_PLUGIN (gedit_docinfo_plugin_get_type ()) +#define GEDIT_DOCINFO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPlugin)) +#define GEDIT_DOCINFO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPluginClass)) +#define GEDIT_IS_DOCINFO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_DOCINFO_PLUGIN)) +#define GEDIT_IS_DOCINFO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_DOCINFO_PLUGIN)) +#define GEDIT_DOCINFO_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPluginClass)) + +/* Private structure type */ +typedef struct _GeditDocInfoPluginPrivate GeditDocInfoPluginPrivate; + +/* + * Main object structure + */ +typedef struct _GeditDocInfoPlugin GeditDocInfoPlugin; + +struct _GeditDocInfoPlugin +{ + GeditPlugin parent_instance; +}; + +/* + * Class definition + */ +typedef struct _GeditDocInfoPluginClass GeditDocInfoPluginClass; + +struct _GeditDocInfoPluginClass +{ + GeditPluginClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_docinfo_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_DOCINFO_PLUGIN_H__ */ diff --git a/plugins/externaltools/Makefile.am b/plugins/externaltools/Makefile.am new file mode 100755 index 00000000..f529640c --- /dev/null +++ b/plugins/externaltools/Makefile.am @@ -0,0 +1,15 @@ +# External Tools plugin +SUBDIRS = tools data scripts +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +plugin_in_files = externaltools.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 + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/externaltools/data/Makefile.am b/plugins/externaltools/data/Makefile.am new file mode 100755 index 00000000..ae3a1c66 --- /dev/null +++ b/plugins/externaltools/data/Makefile.am @@ -0,0 +1,65 @@ +TOOL_MERGE=$(top_srcdir)/plugins/externaltools/scripts/gedit-tool-merge.pl + +tools_in_files = \ + build.tool.in \ + remove-trailing-spaces.tool.in + +tools_in_linux = \ + open-terminal-here.tool.in \ + run-command.tool.in + +tools_in_osx = \ + open-terminal-here-osx.tool.in + +tools_in_win32 = + +install_tools_in_files = $(tools_in_files) + +if PLATFORM_OSX +install_tools_in_files += $(tools_in_osx) +else +if PLATFORM_WIN32 +install_tools_in_files += $(tools_in_win32) +else +install_tools_in_files += $(tools_in_linux) +endif +endif + +desktop_in_files = $(install_tools_in_files:.tool.in=.desktop.in) +desktop_files = $(install_tools_in_files:.tool.in=.desktop) + +tools_SCRIPTS = $(install_tools_in_files:.tool.in=) +toolsdir = $(GEDIT_PLUGINS_DATA_DIR)/externaltools/tools + +all_tools_in_files = \ + $(tools_in_files) \ + $(tools_in_linux) \ + $(tools_in_osx) \ + $(tools_in_win32) + +all_desktop_in_files = $(all_tools_in_files:.tool.in=.desktop.in) +all_desktop_files = $(all_tools_in_files:.tool.in=.desktop) +all_tools_files = $(all_tools_in_files:.tool.in=) + +@INTLTOOL_DESKTOP_RULE@ + +# Tools are generated by merging a script file (.tool.in) with a data file +# (.desktop), which happens to be translated using intltool. +$(tools_SCRIPTS): %: %.tool.in %.desktop $(TOOL_MERGE) + perl $(TOOL_MERGE) -o $@ $< $(word 2,$^) + chmod 755 $@ + +EXTRA_DIST = \ + $(all_desktop_in_files) \ + $(all_tools_in_files) + +CLEANFILES = \ + $(all_desktop_files) \ + $(all_tools_files) + +DISTCLEANFILES = \ + $(all_desktop_files) \ + $(all_tools_files) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/externaltools/data/build.desktop.in b/plugins/externaltools/data/build.desktop.in new file mode 100755 index 00000000..13767ee7 --- /dev/null +++ b/plugins/externaltools/data/build.desktop.in @@ -0,0 +1,9 @@ +[Gedit Tool] +_Name=Build +_Comment=Run "make" in the document directory +Input=nothing +Output=output-panel +Shortcut=F8 +Applicability=local +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/build.tool.in b/plugins/externaltools/data/build.tool.in new file mode 100755 index 00000000..0b81d5b6 --- /dev/null +++ b/plugins/externaltools/data/build.tool.in @@ -0,0 +1,15 @@ +#!/bin/sh + +EHOME=`echo $HOME | sed "s/#/\#/"` +DIR=$GEDIT_CURRENT_DOCUMENT_DIR +while test "$DIR" != "/"; do + for m in GNUmakefile makefile Makefile; do + if [ -f "${DIR}/${m}" ]; then + echo "Using ${m} from ${DIR}" | sed "s#$EHOME#~#" > /dev/stderr + make -C "${DIR}" + exit + fi + done + DIR=`dirname "${DIR}"` +done +echo "No Makefile found!" > /dev/stderr diff --git a/plugins/externaltools/data/open-terminal-here-osx.desktop.in b/plugins/externaltools/data/open-terminal-here-osx.desktop.in new file mode 100755 index 00000000..801b003c --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here-osx.desktop.in @@ -0,0 +1,8 @@ +[Gedit Tool] +_Name=Open terminal here +_Comment=Open a terminal in the document location +Input=nothing +Output=output-panel +Applicability=local +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/open-terminal-here-osx.tool.in b/plugins/externaltools/data/open-terminal-here-osx.tool.in new file mode 100755 index 00000000..c3360064 --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here-osx.tool.in @@ -0,0 +1,16 @@ +#!/usr/bin/osascript + +set the_path to system attribute "GEDIT_CURRENT_DOCUMENT_DIR" +set cmd to "cd " & quoted form of the_path + +tell application "System Events" to set terminalIsRunning to exists application process "Terminal" + +tell application "Terminal" + activate + + if terminalIsRunning is true then + do script with command cmd + else + do script with command cmd in window 1 + end if +end tell diff --git a/plugins/externaltools/data/open-terminal-here.desktop.in b/plugins/externaltools/data/open-terminal-here.desktop.in new file mode 100755 index 00000000..801b003c --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here.desktop.in @@ -0,0 +1,8 @@ +[Gedit Tool] +_Name=Open terminal here +_Comment=Open a terminal in the document location +Input=nothing +Output=output-panel +Applicability=local +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/open-terminal-here.tool.in b/plugins/externaltools/data/open-terminal-here.tool.in new file mode 100755 index 00000000..d2dda8db --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here.tool.in @@ -0,0 +1,4 @@ +#!/bin/sh + +#TODO: use "mateconftool-2 -g /desktop/mate/applications/terminal/exec" +mate-terminal --working-directory=$GEDIT_CURRENT_DOCUMENT_DIR & diff --git a/plugins/externaltools/data/remove-trailing-spaces.desktop.in b/plugins/externaltools/data/remove-trailing-spaces.desktop.in new file mode 100755 index 00000000..99b8b703 --- /dev/null +++ b/plugins/externaltools/data/remove-trailing-spaces.desktop.in @@ -0,0 +1,9 @@ +[Gedit Tool] +_Name=Remove trailing spaces +_Comment=Remove useless trailing spaces in your file +Input=document +Output=replace-document +Shortcut=F12 +Applicability=all +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/remove-trailing-spaces.tool.in b/plugins/externaltools/data/remove-trailing-spaces.tool.in new file mode 100755 index 00000000..83e4c19b --- /dev/null +++ b/plugins/externaltools/data/remove-trailing-spaces.tool.in @@ -0,0 +1,3 @@ +#!/bin/sh + +sed 's/[[:blank:]]*$//' diff --git a/plugins/externaltools/data/run-command.desktop.in b/plugins/externaltools/data/run-command.desktop.in new file mode 100755 index 00000000..b58294b0 --- /dev/null +++ b/plugins/externaltools/data/run-command.desktop.in @@ -0,0 +1,8 @@ +[Gedit Tool] +_Name=Run command +_Comment=Execute a custom command and put its output in a new document +Input=nothing +Output=new-document +Applicability=all +Save-files=nothing +Languages= diff --git a/plugins/externaltools/data/run-command.tool.in b/plugins/externaltools/data/run-command.tool.in new file mode 100755 index 00000000..ee611bbb --- /dev/null +++ b/plugins/externaltools/data/run-command.tool.in @@ -0,0 +1,4 @@ +#!/bin/sh + +#TODO: use "mateconftool-2 -g /desktop/mate/applications/terminal/exec" +exec `matedialog --entry --title="Run command - gedit" --text="Command to run"` diff --git a/plugins/externaltools/externaltools.gedit-plugin.desktop.in b/plugins/externaltools/externaltools.gedit-plugin.desktop.in new file mode 100755 index 00000000..5212c49b --- /dev/null +++ b/plugins/externaltools/externaltools.gedit-plugin.desktop.in @@ -0,0 +1,9 @@ +[Gedit Plugin] +Loader=python +Module=externaltools +IAge=2 +_Name=External Tools +_Description=Execute external commands and shell scripts. +Authors=Steve Frécinaux +Copyright=Copyright © 2005 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/externaltools/scripts/Makefile.am b/plugins/externaltools/scripts/Makefile.am new file mode 100755 index 00000000..4ff8060b --- /dev/null +++ b/plugins/externaltools/scripts/Makefile.am @@ -0,0 +1,4 @@ +EXTRA_DIST = gedit-tool-merge.pl + + +-include $(top_srcdir)/git.mk diff --git a/plugins/externaltools/scripts/gedit-tool-merge.pl b/plugins/externaltools/scripts/gedit-tool-merge.pl new file mode 100755 index 00000000..780d95dd --- /dev/null +++ b/plugins/externaltools/scripts/gedit-tool-merge.pl @@ -0,0 +1,78 @@ +#!/usr/bin/perl + +# gedit-tool-merge.pl +# This file is part of gedit +# +# Copyright (C) 2006 - Steve Frécinaux +# +# gedit 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 of the License, or +# (at your option) any later version. +# +# gedit 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 gedit; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301 USA + +# This script merges a script file with a desktop file containing +# metadata about the external tool. This is required in order to +# have translatable tools (bug #342042) since intltool can't extract +# string directly from tool files (a tool file being the combination +# of a script file and a metadata section). +# +# The desktop file is embedded in a comment of the script file, under +# the assumption that any scripting language supports # as a comment +# mark (this is likely to be true since the shebang uses #!). The +# section is placed at the top of the tool file, after the shebang and +# modelines if present. + +use strict; +use warnings; +use Getopt::Long; + +sub usage { + print < \$help, "output|o=s" => \$output) or &usage; +usage if $help or @ARGV lt 2; + +open INFILE, "<", $ARGV[0]; +open DFILE, "<", $ARGV[1]; +open STDOUT, ">", $output if $output; + +# Put shebang and various modelines at the top of the generated file. +$_ = ; +print and $_ = if /^#!/; +print and $_ = if /-\*-/; +print and $_ = if /(ex|vi|vim):/; + +# Put a blank line before the info block if there is one in INFILE. +print and $_ = if /^\s*$/; +seek INFILE, -length, 1; + +# Embed the desktop file... +print "# $_" while ; +print "\n"; + +# ...and write the remaining part of the script. +print while ; + +close INFILE; +close DFILE; +close STDOUT; diff --git a/plugins/externaltools/tools/Makefile.am b/plugins/externaltools/tools/Makefile.am new file mode 100755 index 00000000..5edcab58 --- /dev/null +++ b/plugins/externaltools/tools/Makefile.am @@ -0,0 +1,23 @@ +# Python snippets plugin + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/externaltools +plugin_PYTHON = \ + __init__.py \ + capture.py \ + library.py \ + functions.py \ + manager.py \ + outputpanel.py \ + filelookup.py \ + linkparsing.py + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/externaltools/ui +ui_DATA = tools.ui \ + outputpanel.ui + +EXTRA_DIST = $(ui_DATA) + +CLEANFILES = *.bak *.gladep +DISTCLEANFILES = *.bak *.gladep + +-include $(top_srcdir)/git.mk diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py new file mode 100755 index 00000000..a46aef8f --- /dev/null +++ b/plugins/externaltools/tools/__init__.py @@ -0,0 +1,281 @@ +# -*- coding: UTF-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux +# +# 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +__all__ = ('ExternalToolsPlugin', 'ExternalToolsWindowHelper', + 'Manager', 'OutputPanel', 'Capture', 'UniqueById') + +import gedit +import gtk +from manager import Manager +from library import ToolLibrary +from outputpanel import OutputPanel +from capture import Capture +from functions import * + +class ToolMenu(object): + ACTION_HANDLER_DATA_KEY = "ExternalToolActionHandlerData" + ACTION_ITEM_DATA_KEY = "ExternalToolActionItemData" + + def __init__(self, library, window, menupath): + super(ToolMenu, self).__init__() + self._library = library + self._window = window + self._menupath = menupath + + self._merge_id = 0 + self._action_group = gtk.ActionGroup("ExternalToolsPluginToolActions") + self._signals = [] + + self.update() + + def deactivate(self): + self.remove() + + def remove(self): + if self._merge_id != 0: + self._window.get_ui_manager().remove_ui(self._merge_id) + self._window.get_ui_manager().remove_action_group(self._action_group) + self._merge_id = 0 + + for action in self._action_group.list_actions(): + handler = action.get_data(self.ACTION_HANDLER_DATA_KEY) + + if handler is not None: + action.disconnect(handler) + + action.set_data(self.ACTION_ITEM_DATA_KEY, None) + action.set_data(self.ACTION_HANDLER_DATA_KEY, None) + + self._action_group.remove_action(action) + + accelmap = gtk.accel_map_get() + + for s in self._signals: + accelmap.disconnect(s) + + self._signals = [] + + def _insert_directory(self, directory, path): + manager = self._window.get_ui_manager() + + for item in directory.subdirs: + action_name = 'ExternalToolDirectory%X' % id(item) + action = gtk.Action(action_name, item.name.replace('_', '__'), None, None) + self._action_group.add_action(action) + + manager.add_ui(self._merge_id, path, + action_name, action_name, + gtk.UI_MANAGER_MENU, False) + + self._insert_directory(item, path + '/' + action_name) + + for item in directory.tools: + action_name = 'ExternalToolTool%X' % id(item) + action = gtk.Action(action_name, item.name.replace('_', '__'), item.comment, None) + handler = action.connect("activate", capture_menu_action, self._window, item) + + action.set_data(self.ACTION_ITEM_DATA_KEY, item) + action.set_data(self.ACTION_HANDLER_DATA_KEY, handler) + + # Make sure to replace accel + accelpath = '/ExternalToolsPluginToolActions/%s' % (action_name, ) + + if item.shortcut: + key, mod = gtk.accelerator_parse(item.shortcut) + gtk.accel_map_change_entry(accelpath, key, mod, True) + + self._signals.append(gtk.accel_map_get().connect('changed::%s' % (accelpath,), self.on_accelmap_changed, item)) + + self._action_group.add_action_with_accel(action, item.shortcut) + + manager.add_ui(self._merge_id, path, + action_name, action_name, + gtk.UI_MANAGER_MENUITEM, False) + + def on_accelmap_changed(self, accelmap, path, key, mod, tool): + tool.shortcut = gtk.accelerator_name(key, mod) + tool.save() + + self._window.get_data("ExternalToolsPluginWindowData").update_manager(tool) + + def update(self): + self.remove() + self._merge_id = self._window.get_ui_manager().new_merge_id() + self._insert_directory(self._library.tree, self._menupath) + self._window.get_ui_manager().insert_action_group(self._action_group, -1) + self.filter(self._window.get_active_document()) + + def filter_language(self, language, item): + if not item.languages: + return True + + if not language and 'plain' in item.languages: + return True + + if language and (language.get_id() in item.languages): + return True + else: + return False + + def filter(self, document): + if document is None: + return + + titled = document.get_uri() is not None + remote = not document.is_local() + + states = { + 'all' : True, + 'local': titled and not remote, + 'remote': titled and remote, + 'titled': titled, + 'untitled': not titled, + } + + language = document.get_language() + + for action in self._action_group.list_actions(): + item = action.get_data(self.ACTION_ITEM_DATA_KEY) + + if item is not None: + action.set_visible(states[item.applicability] and self.filter_language(language, item)) + +class ExternalToolsWindowHelper(object): + def __init__(self, plugin, window): + super(ExternalToolsWindowHelper, self).__init__() + + self._window = window + self._plugin = plugin + self._library = ToolLibrary() + + manager = window.get_ui_manager() + + self._action_group = gtk.ActionGroup('ExternalToolsPluginActions') + self._action_group.set_translation_domain('gedit') + self._action_group.add_actions([('ExternalToolManager', + None, + _('Manage _External Tools...'), + None, + _("Opens the External Tools Manager"), + lambda action: plugin.open_dialog()), + ('ExternalTools', + None, + _('External _Tools'), + None, + _("External tools"), + None)]) + manager.insert_action_group(self._action_group, -1) + + ui_string = """ + + + + + + + + + + + + + + + + """ + + self._merge_id = manager.add_ui_from_string(ui_string) + + self.menu = ToolMenu(self._library, self._window, + "/MenuBar/ToolsMenu/ToolsOps_4/ExternalToolsMenu/ExternalToolPlaceholder") + manager.ensure_update() + + # Create output console + self._output_buffer = OutputPanel(self._plugin.get_data_dir(), window) + bottom = window.get_bottom_panel() + bottom.add_item(self._output_buffer.panel, + _("Shell Output"), + gtk.STOCK_EXECUTE) + + def update_ui(self): + self.menu.filter(self._window.get_active_document()) + self._window.get_ui_manager().ensure_update() + + def deactivate(self): + manager = self._window.get_ui_manager() + self.menu.deactivate() + manager.remove_ui(self._merge_id) + manager.remove_action_group(self._action_group) + manager.ensure_update() + + bottom = self._window.get_bottom_panel() + bottom.remove_item(self._output_buffer.panel) + + def update_manager(self, tool): + self._plugin.update_manager(tool) + +class ExternalToolsPlugin(gedit.Plugin): + WINDOW_DATA_KEY = "ExternalToolsPluginWindowData" + + def __init__(self): + super(ExternalToolsPlugin, self).__init__() + + self._manager = None + self._manager_default_size = None + + ToolLibrary().set_locations(os.path.join(self.get_data_dir(), 'tools')) + + def activate(self, window): + helper = ExternalToolsWindowHelper(self, window) + window.set_data(self.WINDOW_DATA_KEY, helper) + + def deactivate(self, window): + window.get_data(self.WINDOW_DATA_KEY).deactivate() + window.set_data(self.WINDOW_DATA_KEY, None) + + def update_ui(self, window): + window.get_data(self.WINDOW_DATA_KEY).update_ui() + + def create_configure_dialog(self): + return self.open_dialog() + + def open_dialog(self): + if not self._manager: + self._manager = Manager(self.get_data_dir()) + + if self._manager_default_size: + self._manager.dialog.set_default_size(*self._manager_default_size) + + self._manager.dialog.connect('destroy', self.on_manager_destroy) + + window = gedit.app_get_default().get_active_window() + self._manager.run(window) + + return self._manager.dialog + + def update_manager(self, tool): + if not self._manager: + return + + self._manager.tool_changed(tool, True) + + def on_manager_destroy(self, dialog): + self._manager_default_size = [dialog.allocation.width, dialog.allocation.height] + self._manager = None + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py new file mode 100755 index 00000000..e47862c8 --- /dev/null +++ b/plugins/externaltools/tools/capture.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux +# +# 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +__all__ = ('Capture', ) + +import os, sys, signal +import locale +import subprocess +import gobject +import fcntl +import glib + +class Capture(gobject.GObject): + CAPTURE_STDOUT = 0x01 + CAPTURE_STDERR = 0x02 + CAPTURE_BOTH = 0x03 + CAPTURE_NEEDS_SHELL = 0x04 + + WRITE_BUFFER_SIZE = 0x4000 + + __gsignals__ = { + 'stdout-line' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'stderr-line' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), + 'begin-execute': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, tuple()), + 'end-execute' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,)) + } + + def __init__(self, command, cwd = None, env = {}): + gobject.GObject.__init__(self) + self.pipe = None + self.env = env + self.cwd = cwd + self.flags = self.CAPTURE_BOTH | self.CAPTURE_NEEDS_SHELL + self.command = command + self.input_text = None + + def set_env(self, **values): + self.env.update(**values) + + def set_command(self, command): + self.command = command + + def set_flags(self, flags): + self.flags = flags + + def set_input(self, text): + self.input_text = text + + def set_cwd(self, cwd): + self.cwd = cwd + + def execute(self): + if self.command is None: + return + + # Initialize pipe + popen_args = { + 'cwd' : self.cwd, + 'shell': self.flags & self.CAPTURE_NEEDS_SHELL, + 'env' : self.env + } + + if self.input_text is not None: + popen_args['stdin'] = subprocess.PIPE + if self.flags & self.CAPTURE_STDOUT: + popen_args['stdout'] = subprocess.PIPE + if self.flags & self.CAPTURE_STDERR: + popen_args['stderr'] = subprocess.PIPE + + self.tried_killing = False + self.idle_write_id = 0 + self.read_buffer = '' + + try: + self.pipe = subprocess.Popen(self.command, **popen_args) + except OSError, e: + self.pipe = None + self.emit('stderr-line', _('Could not execute command: %s') % (e, )) + return + + # Signal + self.emit('begin-execute') + + if self.flags & self.CAPTURE_STDOUT: + # Set non blocking + flags = fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK + fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_SETFL, flags) + + gobject.io_add_watch(self.pipe.stdout, + gobject.IO_IN | gobject.IO_HUP, + self.on_output) + + if self.flags & self.CAPTURE_STDERR: + # Set non blocking + flags = fcntl.fcntl(self.pipe.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK + fcntl.fcntl(self.pipe.stderr.fileno(), fcntl.F_SETFL, flags) + + gobject.io_add_watch(self.pipe.stderr, + gobject.IO_IN | gobject.IO_HUP, + self.on_output) + + # IO + if self.input_text is not None: + # Write async, in chunks of something + self.write_buffer = str(self.input_text) + + if self.idle_write_chunk(): + self.idle_write_id = gobject.idle_add(self.idle_write_chunk) + + # Wait for the process to complete + gobject.child_watch_add(self.pipe.pid, self.on_child_end) + + def idle_write_chunk(self): + if not self.pipe: + self.idle_write_id = 0 + return False + + try: + l = len(self.write_buffer) + m = min(l, self.WRITE_BUFFER_SIZE) + + self.pipe.stdin.write(self.write_buffer[:m]) + + if m == l: + self.write_buffer = '' + self.pipe.stdin.close() + + self.idle_write_id = 0 + + return False + else: + self.write_buffer = self.write_buffer[m:] + return True + except IOError: + self.pipe.stdin.close() + self.idle_write_id = 0 + + return False + + def on_output(self, source, condition): + if condition & (glib.IO_IN | glib.IO_PRI): + line = source.read() + + if len(line) > 0: + try: + line = unicode(line, 'utf-8') + except: + line = unicode(line, + locale.getdefaultlocale()[1], + 'replace') + + self.read_buffer += line + lines = self.read_buffer.splitlines(True) + + if not lines[-1].endswith("\n"): + self.read_buffer = lines[-1] + lines = lines[0:-1] + else: + self.read_buffer = '' + + for line in lines: + if not self.pipe or source == self.pipe.stdout: + self.emit('stdout-line', line) + else: + self.emit('stderr-line', line) + + if condition & ~(glib.IO_IN | glib.IO_PRI): + if self.read_buffer: + if source == self.pipe.stdout: + self.emit('stdout-line', self.read_buffer) + else: + self.emit('stderr-line', self.read_buffer) + + self.read_buffer = '' + + self.pipe = None + + return False + else: + return True + + def stop(self, error_code = -1): + if self.pipe is not None: + if self.idle_write_id: + gobject.source_remove(self.idle_write_id) + self.idle_write_id = 0 + + if not self.tried_killing: + os.kill(self.pipe.pid, signal.SIGTERM) + self.tried_killing = True + else: + os.kill(self.pipe.pid, signal.SIGKILL) + + def on_child_end(self, pid, error_code): + # In an idle, so it is emitted after all the std*-line signals + # have been intercepted + gobject.idle_add(self.emit, 'end-execute', error_code) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/filelookup.py b/plugins/externaltools/tools/filelookup.py new file mode 100755 index 00000000..229823b7 --- /dev/null +++ b/plugins/externaltools/tools/filelookup.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2010 Per Arneng +# +# 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import gio +import gedit + +class FileLookup: + """ + This class is responsible for looking up files given a part or the whole + path of a real file. The lookup is delegated to providers wich use different + methods of trying to find the real file. + """ + + def __init__(self): + self.providers = [] + self.providers.append(AbsoluteFileLookupProvider()) + self.providers.append(CwdFileLookupProvider()) + self.providers.append(OpenDocumentRelPathFileLookupProvider()) + self.providers.append(OpenDocumentFileLookupProvider()) + + def lookup(self, path): + """ + Tries to find a file specified by the path parameter. It delegates to + different lookup providers and the first match is returned. If no file + was found then None is returned. + + path -- the path to find + """ + found_file = None + for provider in self.providers: + found_file = provider.lookup(path) + if found_file is not None: + break + + return found_file + + +class FileLookupProvider: + """ + The base class of all file lookup providers. + """ + + def lookup(self, path): + """ + This method must be implemented by subclasses. Implementors will be + given a path and will try to find a matching file. If no file is found + then None is returned. + """ + raise NotImplementedError("need to implement a lookup method") + + +class AbsoluteFileLookupProvider(FileLookupProvider): + """ + This file tries to see if the path given is an absolute path and that the + path references a file. + """ + + def lookup(self, path): + if os.path.isabs(path) and os.path.isfile(path): + return gio.File(path) + else: + return None + + +class CwdFileLookupProvider(FileLookupProvider): + """ + This lookup provider tries to find a file specified by the path relative to + the current working directory. + """ + + def lookup(self, path): + try: + cwd = os.getcwd() + except OSError: + cwd = os.getenv('HOME') + + real_path = os.path.join(cwd, path) + + if os.path.isfile(real_path): + return gio.File(real_path) + else: + return None + + +class OpenDocumentRelPathFileLookupProvider(FileLookupProvider): + """ + Tries to see if the path is relative to any directories where the + currently open documents reside in. Example: If you have a document opened + '/tmp/Makefile' and a lookup is made for 'src/test2.c' then this class + will try to find '/tmp/src/test2.c'. + """ + + def lookup(self, path): + if path.startswith('/'): + return None + + for doc in gedit.app_get_default().get_documents(): + if doc.is_local(): + location = doc.get_location() + if location: + rel_path = location.get_parent().get_path() + joined_path = os.path.join(rel_path, path) + if os.path.isfile(joined_path): + return gio.File(joined_path) + + return None + + +class OpenDocumentFileLookupProvider(FileLookupProvider): + """ + Makes a guess that the if the path that was looked for matches the end + of the path of a currently open document then that document is the one + that is looked for. Example: If a document is opened called '/tmp/t.c' + and a lookup is made for 't.c' or 'tmp/t.c' then both will match since + the open document ends with the path that is searched for. + """ + + def lookup(self, path): + if path.startswith('/'): + return None + + for doc in gedit.app_get_default().get_documents(): + if doc.is_local(): + location = doc.get_location() + if location and location.get_uri().endswith(path): + return location + return None + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/functions.py b/plugins/externaltools/tools/functions.py new file mode 100755 index 00000000..0d2bfdbf --- /dev/null +++ b/plugins/externaltools/tools/functions.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux +# +# 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import gtk +from gtk import gdk +import gio +import gedit +#import gtksourceview +from outputpanel import OutputPanel +from capture import * + +def default(val, d): + if val is not None: + return val + else: + return d + +def current_word(document): + piter = document.get_iter_at_mark(document.get_insert()) + start = piter.copy() + + if not piter.starts_word() and (piter.inside_word() or piter.ends_word()): + start.backward_word_start() + + if not piter.ends_word() and piter.inside_word(): + piter.forward_word_end() + + return (start, piter) + +# ==== Capture related functions ==== +def run_external_tool(window, node): + # Configure capture environment + try: + cwd = os.getcwd() + except OSError: + cwd = os.getenv('HOME'); + + capture = Capture(node.command, cwd) + capture.env = os.environ.copy() + capture.set_env(GEDIT_CWD = cwd) + + view = window.get_active_view() + if view is not None: + # Environment vars relative to current document + document = view.get_buffer() + uri = document.get_uri() + + # Current line number + piter = document.get_iter_at_mark(document.get_insert()) + capture.set_env(GEDIT_CURRENT_LINE_NUMBER=str(piter.get_line() + 1)) + + # Current line text + piter.set_line_offset(0) + end = piter.copy() + + if not end.ends_line(): + end.forward_to_line_end() + + capture.set_env(GEDIT_CURRENT_LINE=piter.get_text(end)) + + # Selected text (only if input is not selection) + if node.input != 'selection' and node.input != 'selection-document': + bounds = document.get_selection_bounds() + + if bounds: + capture.set_env(GEDIT_SELECTED_TEXT=bounds[0].get_text(bounds[1])) + + bounds = current_word(document) + capture.set_env(GEDIT_CURRENT_WORD=bounds[0].get_text(bounds[1])) + + capture.set_env(GEDIT_CURRENT_DOCUMENT_TYPE=document.get_mime_type()) + + if uri is not None: + gfile = gio.File(uri) + scheme = gfile.get_uri_scheme() + name = os.path.basename(uri) + capture.set_env(GEDIT_CURRENT_DOCUMENT_URI = uri, + GEDIT_CURRENT_DOCUMENT_NAME = name, + GEDIT_CURRENT_DOCUMENT_SCHEME = scheme) + if gedit.utils.uri_has_file_scheme(uri): + path = gfile.get_path() + cwd = os.path.dirname(path) + capture.set_cwd(cwd) + capture.set_env(GEDIT_CURRENT_DOCUMENT_PATH = path, + GEDIT_CURRENT_DOCUMENT_DIR = cwd) + + documents_uri = [doc.get_uri() + for doc in window.get_documents() + if doc.get_uri() is not None] + documents_path = [gio.File(uri).get_path() + for uri in documents_uri + if gedit.utils.uri_has_file_scheme(uri)] + capture.set_env(GEDIT_DOCUMENTS_URI = ' '.join(documents_uri), + GEDIT_DOCUMENTS_PATH = ' '.join(documents_path)) + + flags = capture.CAPTURE_BOTH + + if not node.has_hash_bang(): + flags |= capture.CAPTURE_NEEDS_SHELL + + capture.set_flags(flags) + + # Get input text + input_type = node.input + output_type = node.output + + # Get the panel + panel = window.get_data("ExternalToolsPluginWindowData")._output_buffer + panel.clear() + + if output_type == 'output-panel': + panel.show() + + # Assign the error output to the output panel + panel.set_process(capture) + + if input_type != 'nothing' and view is not None: + if input_type == 'document': + start, end = document.get_bounds() + elif input_type == 'selection' or input_type == 'selection-document': + try: + start, end = document.get_selection_bounds() + + print start, end + except ValueError: + if input_type == 'selection-document': + start, end = document.get_bounds() + + if output_type == 'replace-selection': + document.select_range(start, end) + else: + start = document.get_iter_at_mark(document.get_insert()) + end = start.copy() + + elif input_type == 'line': + start = document.get_iter_at_mark(document.get_insert()) + end = start.copy() + if not start.starts_line(): + start.set_line_offset(0) + if not end.ends_line(): + end.forward_to_line_end() + elif input_type == 'word': + start = document.get_iter_at_mark(document.get_insert()) + end = start.copy() + if not start.inside_word(): + panel.write(_('You must be inside a word to run this command'), + panel.command_tag) + return + if not start.starts_word(): + start.backward_word_start() + if not end.ends_word(): + end.forward_word_end() + + input_text = document.get_text(start, end) + capture.set_input(input_text) + + # Assign the standard output to the chosen "file" + if output_type == 'new-document': + tab = window.create_tab(True) + view = tab.get_view() + document = tab.get_document() + pos = document.get_start_iter() + capture.connect('stdout-line', capture_stdout_line_document, document, pos) + document.begin_user_action() + view.set_editable(False) + view.set_cursor_visible(False) + elif output_type != 'output-panel' and output_type != 'nothing' and view is not None: + document.begin_user_action() + view.set_editable(False) + view.set_cursor_visible(False) + + if output_type == 'insert': + pos = document.get_iter_at_mark(document.get_mark('insert')) + elif output_type == 'replace-selection': + document.delete_selection(False, False) + pos = document.get_iter_at_mark(document.get_mark('insert')) + elif output_type == 'replace-document': + document.set_text('') + pos = document.get_end_iter() + else: + pos = document.get_end_iter() + capture.connect('stdout-line', capture_stdout_line_document, document, pos) + elif output_type != 'nothing': + capture.connect('stdout-line', capture_stdout_line_panel, panel) + document.begin_user_action() + + capture.connect('stderr-line', capture_stderr_line_panel, panel) + capture.connect('begin-execute', capture_begin_execute_panel, panel, view, node.name) + capture.connect('end-execute', capture_end_execute_panel, panel, view, output_type) + + # Run the command + capture.execute() + + if output_type != 'nothing': + document.end_user_action() + +class MultipleDocumentsSaver: + def __init__(self, window, docs, node): + self._window = window + self._node = node + self._error = False + + self._counter = len(docs) + self._signal_ids = {} + self._counter = 0 + + signals = {} + + for doc in docs: + signals[doc] = doc.connect('saving', self.on_document_saving) + gedit.commands.save_document(window, doc) + doc.disconnect(signals[doc]) + + def on_document_saving(self, doc, size, total_size): + self._counter += 1 + self._signal_ids[doc] = doc.connect('saved', self.on_document_saved) + + def on_document_saved(self, doc, error): + if error: + self._error = True + + doc.disconnect(self._signal_ids[doc]) + del self._signal_ids[doc] + + self._counter -= 1 + + if self._counter == 0 and not self._error: + run_external_tool(self._window, self._node) + +def capture_menu_action(action, window, node): + if node.save_files == 'document' and window.get_active_document(): + MultipleDocumentsSaver(window, [window.get_active_document()], node) + return + elif node.save_files == 'all': + MultipleDocumentsSaver(window, window.get_documents(), node) + return + + run_external_tool(window, node) + +def capture_stderr_line_panel(capture, line, panel): + if not panel.visible(): + panel.show() + + panel.write(line, panel.error_tag) + +def capture_begin_execute_panel(capture, panel, view, label): + view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gdk.Cursor(gdk.WATCH)) + + panel['stop'].set_sensitive(True) + panel.clear() + panel.write(_("Running tool:"), panel.italic_tag); + panel.write(" %s\n\n" % label, panel.bold_tag); + +def capture_end_execute_panel(capture, exit_code, panel, view, output_type): + panel['stop'].set_sensitive(False) + + if output_type in ('new-document','replace-document'): + doc = view.get_buffer() + start = doc.get_start_iter() + end = start.copy() + end.forward_chars(300) + + mtype = gio.content_type_guess(data=doc.get_text(start, end)) + lmanager = gedit.get_language_manager() + + language = lmanager.guess_language(doc.get_uri(), mtype) + + if language is not None: + doc.set_language(language) + + view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gdk.Cursor(gdk.XTERM)) + view.set_cursor_visible(True) + view.set_editable(True) + + if exit_code == 0: + panel.write("\n" + _("Done.") + "\n", panel.italic_tag) + else: + panel.write("\n" + _("Exited") + ":", panel.italic_tag) + panel.write(" %d\n" % exit_code, panel.bold_tag) + +def capture_stdout_line_panel(capture, line, panel): + panel.write(line) + +def capture_stdout_line_document(capture, line, document, pos): + document.insert(pos, line) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py new file mode 100755 index 00000000..6eb6ff1a --- /dev/null +++ b/plugins/externaltools/tools/library.py @@ -0,0 +1,493 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2006 Steve Frécinaux +# +# 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import re +import locale +import platform + +class Singleton(object): + _instance = None + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(Singleton, cls).__new__( + cls, *args, **kwargs) + cls._instance.__init_once__() + + return cls._instance + +class ToolLibrary(Singleton): + def __init_once__(self): + self.locations = [] + + def set_locations(self, datadir): + self.locations = [] + + if platform.platform() != 'Windows': + for d in self.get_xdg_data_dirs(): + self.locations.append(os.path.join(d, 'gedit-2', 'plugins', 'externaltools', 'tools')) + + self.locations.append(datadir) + + # self.locations[0] is where we save the custom scripts + if platform.platform() == 'Windows': + toolsdir = os.path.expanduser('~/gedit/tools') + else: + userdir = os.getenv('MATE22_USER_DIR') + if userdir: + toolsdir = os.path.join(userdir, 'gedit/tools') + else: + toolsdir = os.path.expanduser('~/.mate2/gedit/tools') + + self.locations.insert(0, toolsdir); + + if not os.path.isdir(self.locations[0]): + os.makedirs(self.locations[0]) + self.tree = ToolDirectory(self, '') + self.import_old_xml_store() + else: + self.tree = ToolDirectory(self, '') + + # cf. http://standards.freedesktop.org/basedir-spec/latest/ + def get_xdg_data_dirs(self): + dirs = os.getenv('XDG_DATA_DIRS') + if dirs: + dirs = dirs.split(os.pathsep) + else: + dirs = ('/usr/local/share', '/usr/share') + return dirs + + # This function is meant to be ran only once, when the tools directory is + # created. It imports eventual tools that have been saved in the old XML + # storage file. + def import_old_xml_store(self): + import xml.etree.ElementTree as et + userdir = os.getenv('MATE22_USER_DIR') + if userdir: + filename = os.path.join(userdir, 'gedit/gedit-tools.xml') + else: + filename = os.path.expanduser('~/.mate2/gedit/gedit-tools.xml') + + if not os.path.isfile(filename): + return + + print "External tools: importing old tools into the new store..." + + xtree = et.parse(filename) + xroot = xtree.getroot() + + for xtool in xroot: + for i in self.tree.tools: + if i.name == xtool.get('label'): + tool = i + break + else: + tool = Tool(self.tree) + tool.name = xtool.get('label') + tool.autoset_filename() + self.tree.tools.append(tool) + tool.comment = xtool.get('description') + tool.shortcut = xtool.get('accelerator') + tool.applicability = xtool.get('applicability') + tool.output = xtool.get('output') + tool.input = xtool.get('input') + + tool.save_with_script(xtool.text) + + def get_full_path(self, path, mode='r', system = True, local = True): + assert (system or local) + if path is None: + return None + if mode == 'r': + if system and local: + locations = self.locations + elif local and not system: + locations = [self.locations[0]] + elif system and not local: + locations = self.locations[1:] + else: + raise ValueError("system and local can't be both set to False") + + for i in locations: + p = os.path.join(i, path) + if os.path.lexists(p): + return p + return None + else: + path = os.path.join(self.locations[0], path) + dirname = os.path.dirname(path) + if not os.path.isdir(dirname): + os.mkdir(dirname) + return path + +class ToolDirectory(object): + def __init__(self, parent, dirname): + super(ToolDirectory, self).__init__() + self.subdirs = list() + self.tools = list() + if isinstance(parent, ToolDirectory): + self.parent = parent + self.library = parent.library + else: + self.parent = None + self.library = parent + self.dirname = dirname + self._load() + + def listdir(self): + elements = dict() + for l in self.library.locations: + d = os.path.join(l, self.dirname) + if not os.path.isdir(d): + continue + for i in os.listdir(d): + elements[i] = None + keys = elements.keys() + keys.sort() + return keys + + def _load(self): + for p in self.listdir(): + path = os.path.join(self.dirname, p) + full_path = self.library.get_full_path(path) + if os.path.isdir(full_path): + self.subdirs.append(ToolDirectory(self, p)) + elif os.path.isfile(full_path) and os.access(full_path, os.X_OK): + self.tools.append(Tool(self, p)) + + def get_path(self): + if self.parent is None: + return self.dirname + else: + return os.path.join(self.parent.get_path(), self.dirname) + path = property(get_path) + + def get_name(self): + return os.path.basename(self.dirname) + name = property(get_name) + + def delete_tool(self, tool): + # Only remove it if it lays in $HOME + if tool in self.tools: + path = tool.get_path() + if path is not None: + filename = os.path.join(self.library.locations[0], path) + if os.path.isfile(filename): + os.unlink(filename) + self.tools.remove(tool) + return True + else: + return False + + def revert_tool(self, tool): + # Only remove it if it lays in $HOME + filename = os.path.join(self.library.locations[0], tool.get_path()) + if tool in self.tools and os.path.isfile(filename): + os.unlink(filename) + tool._load() + return True + else: + return False + + +class Tool(object): + RE_KEY = re.compile('^([a-zA-Z_][a-zA-Z0-9_.\-]*)(\[([a-zA-Z_@]+)\])?$') + + def __init__(self, parent, filename = None): + super(Tool, self).__init__() + self.parent = parent + self.library = parent.library + self.filename = filename + self.changed = False + self._properties = dict() + self._transform = { + 'Languages': [self._to_list, self._from_list] + } + self._load() + + def _to_list(self, value): + if value.strip() == '': + return [] + else: + return map(lambda x: x.strip(), value.split(',')) + + def _from_list(self, value): + return ','.join(value) + + def _parse_value(self, key, value): + if key in self._transform: + return self._transform[key][0](value) + else: + return value + + def _load(self): + if self.filename is None: + return + + filename = self.library.get_full_path(self.get_path()) + if filename is None: + return + + fp = file(filename, 'r', 1) + in_block = False + lang = locale.getlocale(locale.LC_MESSAGES)[0] + + for line in fp: + if not in_block: + in_block = line.startswith('# [Gedit Tool]') + continue + if line.startswith('##') or line.startswith('# #'): continue + if not line.startswith('# '): break + + try: + (key, value) = [i.strip() for i in line[2:].split('=', 1)] + m = self.RE_KEY.match(key) + if m.group(3) is None: + self._properties[m.group(1)] = self._parse_value(m.group(1), value) + elif lang is not None and lang.startswith(m.group(3)): + self._properties[m.group(1)] = self._parse_value(m.group(1), value) + except ValueError: + break + fp.close() + self.changed = False + + def _set_property_if_changed(self, key, value): + if value != self._properties.get(key): + self._properties[key] = value + + self.changed = True + + def is_global(self): + return self.library.get_full_path(self.get_path(), local=False) is not None + + def is_local(self): + return self.library.get_full_path(self.get_path(), system=False) is not None + + def is_global(self): + return self.library.get_full_path(self.get_path(), local=False) is not None + + def get_path(self): + if self.filename is not None: + return os.path.join(self.parent.get_path(), self.filename) + else: + return None + path = property(get_path) + + # This command is the one that is meant to be ran + # (later, could have an Exec key or something) + def get_command(self): + return self.library.get_full_path(self.get_path()) + command = property(get_command) + + def get_applicability(self): + applicability = self._properties.get('Applicability') + if applicability: return applicability + return 'all' + def set_applicability(self, value): + self._set_property_if_changed('Applicability', value) + applicability = property(get_applicability, set_applicability) + + def get_name(self): + name = self._properties.get('Name') + if name: return name + return os.path.basename(self.filename) + def set_name(self, value): + self._set_property_if_changed('Name', value) + name = property(get_name, set_name) + + def get_shortcut(self): + shortcut = self._properties.get('Shortcut') + if shortcut: return shortcut + return None + def set_shortcut(self, value): + self._set_property_if_changed('Shortcut', value) + shortcut = property(get_shortcut, set_shortcut) + + def get_comment(self): + comment = self._properties.get('Comment') + if comment: return comment + return self.filename + def set_comment(self, value): + self._set_property_if_changed('Comment', value) + comment = property(get_comment, set_comment) + + def get_input(self): + input = self._properties.get('Input') + if input: return input + return 'nothing' + def set_input(self, value): + self._set_property_if_changed('Input', value) + input = property(get_input, set_input) + + def get_output(self): + output = self._properties.get('Output') + if output: return output + return 'output-panel' + def set_output(self, value): + self._set_property_if_changed('Output', value) + output = property(get_output, set_output) + + def get_save_files(self): + save_files = self._properties.get('Save-files') + if save_files: return save_files + return 'nothing' + def set_save_files(self, value): + self._set_property_if_changed('Save-files', value) + save_files = property(get_save_files, set_save_files) + + def get_languages(self): + languages = self._properties.get('Languages') + if languages: return languages + return [] + def set_languages(self, value): + self._set_property_if_changed('Languages', value) + languages = property(get_languages, set_languages) + + def has_hash_bang(self): + if self.filename is None: + return True + + filename = self.library.get_full_path(self.get_path()) + if filename is None: + return True + + fp = open(filename, 'r', 1) + for line in fp: + if line.strip() == '': + continue + + return line.startswith('#!') + + # There is no property for this one because this function is quite + # expensive to perform + def get_script(self): + if self.filename is None: + return ["#!/bin/sh\n"] + + filename = self.library.get_full_path(self.get_path()) + if filename is None: + return ["#!/bin/sh\n"] + + fp = open(filename, 'r', 1) + lines = list() + + # before entering the data block + for line in fp: + if line.startswith('# [Gedit Tool]'): + break + lines.append(line) + # in the block: + for line in fp: + if line.startswith('##'): continue + if not (line.startswith('# ') and '=' in line): + # after the block: strip one emtpy line (if present) + if line.strip() != '': + lines.append(line) + break + # after the block + for line in fp: + lines.append(line) + fp.close() + return lines + + def _dump_properties(self): + lines = ['# [Gedit Tool]'] + for item in self._properties.iteritems(): + if item[0] in self._transform: + lines.append('# %s=%s' % (item[0], self._transform[item[0]][1](item[1]))) + elif item[1] is not None: + lines.append('# %s=%s' % item) + return '\n'.join(lines) + '\n' + + def save_with_script(self, script): + filename = self.library.get_full_path(self.filename, 'w') + + fp = open(filename, 'w', 1) + + # Make sure to first print header (shebang, modeline), then + # properties, and then actual content + header = [] + content = [] + inheader = True + + # Parse + for line in script: + line = line.rstrip("\n") + + if not inheader: + content.append(line) + elif line.startswith('#!'): + # Shebang (should be always present) + header.append(line) + elif line.strip().startswith('#') and ('-*-' in line or 'ex:' in line or 'vi:' in line or 'vim:' in line): + header.append(line) + else: + content.append(line) + inheader = False + + # Write out header + for line in header: + fp.write(line + "\n") + + fp.write(self._dump_properties()) + fp.write("\n") + + for line in content: + fp.write(line + "\n") + + fp.close() + os.chmod(filename, 0750) + self.changed = False + + def save(self): + if self.changed: + self.save_with_script(self.get_script()) + + def autoset_filename(self): + if self.filename is not None: + return + dirname = self.parent.path + if dirname != '': + dirname += os.path.sep + + basename = self.name.lower().replace(' ', '-').replace('/', '-') + + if self.library.get_full_path(dirname + basename): + i = 2 + while self.library.get_full_path(dirname + "%s-%d" % (basename, i)): + i += 1 + basename = "%s-%d" % (basename, i) + self.filename = basename + +if __name__ == '__main__': + library = ToolLibrary() + + def print_tool(t, indent): + print indent * " " + "%s: %s" % (t.filename, t.name) + + def print_dir(d, indent): + print indent * " " + d.dirname + '/' + for i in d.subdirs: + print_dir(i, indent+1) + for i in d.tools: + print_tool(i, indent+1) + + print_dir(library.tree, 0) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/linkparsing.py b/plugins/externaltools/tools/linkparsing.py new file mode 100755 index 00000000..27b9ba89 --- /dev/null +++ b/plugins/externaltools/tools/linkparsing.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2009-2010 Per Arneng +# +# 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import re + +class Link: + """ + This class represents a file link from within a string given by the + output of some software tool. A link contains a reference to a file, the + line number within the file and the boundaries within the given output + string that should be marked as a link. + """ + + def __init__(self, path, line_nr, start, end): + """ + path -- the path of the file (that could be extracted) + line_nr -- the line nr of the specified file + start -- the index within the string that the link starts at + end -- the index within the string where the link ends at + """ + self.path = path + self.line_nr = int(line_nr) + self.start = start + self.end = end + + def __repr__(self): + return "%s[%s](%s:%s)" % (self.path, self.line_nr, + self.start, self.end) + +class LinkParser: + """ + Parses a text using different parsing providers with the goal of finding one + or more file links within the text. A typical example could be the output + from a compiler that specifies an error in a specific file. The path of the + file, the line nr and some more info is then returned so that it can be used + to be able to navigate from the error output in to the specific file. + + The actual work of parsing the text is done by instances of classes that + inherits from AbstractLinkParser or by regular expressions. To add a new + parser just create a class that inherits from AbstractLinkParser and then + register in this class cunstructor using the method add_parser. If you want + to add a regular expression then just call add_regexp in this class + constructor and provide your regexp string as argument. + """ + + def __init__(self): + self._providers = [] + self.add_regexp(REGEXP_STANDARD) + self.add_regexp(REGEXP_PYTHON) + self.add_regexp(REGEXP_VALAC) + self.add_regexp(REGEXP_BASH) + self.add_regexp(REGEXP_RUBY) + self.add_regexp(REGEXP_PERL) + self.add_regexp(REGEXP_MCS) + + def add_parser(self, parser): + self._providers.append(parser) + + def add_regexp(self, regexp): + """ + Adds a regular expression string that should match a link using + re.MULTILINE and re.VERBOSE regexp. The area marked as a link should + be captured by a group named lnk. The path of the link should be + captured by a group named pth. The line number should be captured by + a group named ln. To read more about this look at the documentation + for the RegexpLinkParser constructor. + """ + self.add_parser(RegexpLinkParser(regexp)) + + def parse(self, text): + """ + Parses the given text and returns a list of links that are parsed from + the text. This method delegates to parser providers that can parse + output from different kinds of formats. If no links are found then an + empty list is returned. + + text -- the text to scan for file links. 'text' can not be None. + """ + if text is None: + raise ValueError("text can not be None") + + links = [] + + for provider in self._providers: + links.extend(provider.parse(text)) + + return links + +class AbstractLinkParser(object): + """The "abstract" base class for link parses""" + + def parse(self, text): + """ + This method should be implemented by subclasses. It takes a text as + argument (never None) and then returns a list of Link objects. If no + links are found then an empty list is expected. The Link class is + defined in this module. If you do not override this method then a + NotImplementedError will be thrown. + + text -- the text to parse. This argument is never None. + """ + raise NotImplementedError("need to implement a parse method") + +class RegexpLinkParser(AbstractLinkParser): + """ + A class that represents parsers that only use one single regular expression. + It can be used by subclasses or by itself. See the constructor documentation + for details about the rules surrouning the regexp. + """ + + def __init__(self, regex): + """ + Creates a new RegexpLinkParser based on the given regular expression. + The regular expression is multiline and verbose (se python docs on + compilation flags). The regular expression should contain three named + capturing groups 'lnk', 'pth' and 'ln'. 'lnk' represents the area wich + should be marked as a link in the text. 'pth' is the path that should + be looked for and 'ln' is the line number in that file. + """ + self.re = re.compile(regex, re.MULTILINE | re.VERBOSE) + + def parse(self, text): + links = [] + for m in re.finditer(self.re, text): + path = m.group("pth") + line_nr = m.group("ln") + start = m.start("lnk") + end = m.end("lnk") + link = Link(path, line_nr, start, end) + links.append(link) + + return links + +# gcc 'test.c:13: warning: ...' +# javac 'Test.java:13: ...' +# ruby 'test.rb:5: ...' +# scalac 'Test.scala:5: ...' +# 6g (go) 'test.go:9: ...' +REGEXP_STANDARD = r""" +^ +(?P + (?P .*[a-z0-9] ) + \: + (?P \d+) +) +\:\s""" + +# python ' File "test.py", line 13' +REGEXP_PYTHON = r""" +^\s\sFile\s +(?P + \" + (?P [^\"]+ ) + \",\sline\s + (?P \d+ ) +),""" + +# python 'test.sh: line 5:' +REGEXP_BASH = r""" +^(?P + (?P .* ) + \:\sline\s + (?P \d+ ) +)\:""" + +# valac 'Test.vala:13.1-13.3: ...' +REGEXP_VALAC = r""" +^(?P + (?P + .*vala + ) + \: + (?P + \d+ + ) + \.\d+-\d+\.\d+ + )\: """ + +#ruby +#test.rb:5: ... +# from test.rb:3:in `each' +# fist line parsed by REGEXP_STANDARD +REGEXP_RUBY = r""" +^\s+from\s +(?P + (?P + .* + ) + \: + (?P + \d+ + ) + )""" + +# perl 'syntax error at test.pl line 88, near "$fake_var' +REGEXP_PERL = r""" +\sat\s +(?P + (?P .* ) + \sline\s + (?P \d+ ) +)""" + +# mcs (C#) 'Test.cs(12,7): error CS0103: The name `fakeMethod' +REGEXP_MCS = r""" +^ +(?P + (?P .*\.[cC][sS] ) + \( + (?P \d+ ) + ,\d+\) +) +\:\s +""" + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/manager.py b/plugins/externaltools/tools/manager.py new file mode 100755 index 00000000..e28a088a --- /dev/null +++ b/plugins/externaltools/tools/manager.py @@ -0,0 +1,948 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux +# +# 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +__all__ = ('Manager', ) + +import gedit +import gtk +import gtksourceview2 as gsv +import os.path +from library import * +from functions import * +import hashlib +from xml.sax import saxutils +import gobject + +class LanguagesPopup(gtk.Window): + COLUMN_NAME = 0 + COLUMN_ID = 1 + COLUMN_ENABLED = 2 + + def __init__(self, languages): + gtk.Window.__init__(self, gtk.WINDOW_POPUP) + + self.set_default_size(200, 200) + self.props.can_focus = True + + self.build() + self.init_languages(languages) + + self.show() + self.map() + + self.grab_add() + + gtk.gdk.keyboard_grab(self.window, False, 0L) + gtk.gdk.pointer_grab(self.window, False, gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.BUTTON_RELEASE_MASK | + gtk.gdk.POINTER_MOTION_MASK | + gtk.gdk.ENTER_NOTIFY_MASK | + gtk.gdk.LEAVE_NOTIFY_MASK | + gtk.gdk.PROXIMITY_IN_MASK | + gtk.gdk.PROXIMITY_OUT_MASK, None, None, 0L) + + self.view.get_selection().select_path((0,)) + + def build(self): + self.model = gtk.ListStore(str, str, bool) + + self.sw = gtk.ScrolledWindow() + self.sw.show() + + self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) + + self.view = gtk.TreeView(self.model) + self.view.show() + + self.view.set_headers_visible(False) + + column = gtk.TreeViewColumn() + + renderer = gtk.CellRendererToggle() + column.pack_start(renderer, False) + column.set_attributes(renderer, active=self.COLUMN_ENABLED) + + renderer.connect('toggled', self.on_language_toggled) + + renderer = gtk.CellRendererText() + column.pack_start(renderer, True) + column.set_attributes(renderer, text=self.COLUMN_NAME) + + self.view.append_column(column) + self.view.set_row_separator_func(self.on_separator) + + self.sw.add(self.view) + + self.add(self.sw) + + def enabled_languages(self, model, path, piter, ret): + enabled = model.get_value(piter, self.COLUMN_ENABLED) + + if path == (0,) and enabled: + return True + + if enabled: + ret.append(model.get_value(piter, self.COLUMN_ID)) + + return False + + def languages(self): + ret = [] + + self.model.foreach(self.enabled_languages, ret) + return ret + + def on_separator(self, model, piter): + val = model.get_value(piter, self.COLUMN_NAME) + return val == '-' + + def init_languages(self, languages): + manager = gsv.LanguageManager() + langs = gedit.language_manager_list_languages_sorted(manager, True) + + self.model.append([_('All languages'), None, not languages]) + self.model.append(['-', None, False]) + self.model.append([_('Plain Text'), 'plain', 'plain' in languages]) + self.model.append(['-', None, False]) + + for lang in langs: + self.model.append([lang.get_name(), lang.get_id(), lang.get_id() in languages]) + + def correct_all(self, model, path, piter, enabled): + if path == (0,): + return False + + model.set_value(piter, self.COLUMN_ENABLED, enabled) + + def on_language_toggled(self, renderer, path): + piter = self.model.get_iter(path) + + enabled = self.model.get_value(piter, self.COLUMN_ENABLED) + self.model.set_value(piter, self.COLUMN_ENABLED, not enabled) + + if path == '0': + self.model.foreach(self.correct_all, False) + else: + self.model.set_value(self.model.get_iter_first(), self.COLUMN_ENABLED, False) + + def do_key_press_event(self, event): + if event.keyval == gtk.keysyms.Escape: + self.destroy() + return True + else: + event.window = self.view.get_bin_window() + return self.view.event(event) + + def do_key_release_event(self, event): + event.window = self.view.get_bin_window() + return self.view.event(event) + + def in_window(self, event, window=None): + if not window: + window = self.window + + geometry = window.get_geometry() + origin = window.get_origin() + + return event.x_root >= origin[0] and \ + event.x_root <= origin[0] + geometry[2] and \ + event.y_root >= origin[1] and \ + event.y_root <= origin[1] + geometry[3] + + def do_destroy(self): + gtk.gdk.keyboard_ungrab(0L) + gtk.gdk.pointer_ungrab(0L) + + return gtk.Window.do_destroy(self) + + def setup_event(self, event, window): + fr = event.window.get_origin() + to = window.get_origin() + + event.window = window + event.x += fr[0] - to[0] + event.y += fr[1] - to[1] + + def resolve_widgets(self, root): + res = [root] + + if isinstance(root, gtk.Container): + root.forall(lambda x, y: res.extend(self.resolve_widgets(x)), None) + + return res + + def resolve_windows(self, window): + if not window: + return [] + + res = [window] + res.extend(window.get_children()) + + return res + + def propagate_mouse_event(self, event): + allwidgets = self.resolve_widgets(self.get_child()) + allwidgets.reverse() + + orig = [event.x, event.y] + + for widget in allwidgets: + windows = self.resolve_windows(widget.window) + windows.reverse() + + for window in windows: + if not (window.get_events() & event.type): + continue + + if self.in_window(event, window): + self.setup_event(event, window) + + if widget.event(event): + return True + + return False + + def do_button_press_event(self, event): + if not self.in_window(event): + self.destroy() + else: + return self.propagate_mouse_event(event) + + def do_button_release_event(self, event): + if not self.in_window(event): + self.destroy() + else: + return self.propagate_mouse_event(event) + + def do_scroll_event(self, event): + return self.propagate_mouse_event(event) + + def do_motion_notify_event(self, event): + return self.propagate_mouse_event(event) + + def do_enter_notify_event(self, event): + return self.propagate_mouse_event(event) + + def do_leave_notify_event(self, event): + return self.propagate_mouse_event(event) + + def do_proximity_in_event(self, event): + return self.propagate_mouse_event(event) + + def do_proximity_out_event(self, event): + return self.propagate_mouse_event(event) + +gobject.type_register(LanguagesPopup) + +class Manager: + TOOL_COLUMN = 0 # For Tree + NAME_COLUMN = 1 # For Combo + + def __init__(self, datadir): + self.datadir = datadir + self.dialog = None + self._languages = {} + self._tool_rows = {} + + self.build() + + def build(self): + callbacks = { + 'on_new_tool_button_clicked' : self.on_new_tool_button_clicked, + 'on_remove_tool_button_clicked' : self.on_remove_tool_button_clicked, + 'on_tool_manager_dialog_response' : self.on_tool_manager_dialog_response, + 'on_tool_manager_dialog_focus_out': self.on_tool_manager_dialog_focus_out, + 'on_accelerator_key_press' : self.on_accelerator_key_press, + 'on_accelerator_focus_in' : self.on_accelerator_focus_in, + 'on_accelerator_focus_out' : self.on_accelerator_focus_out, + 'on_languages_button_clicked' : self.on_languages_button_clicked + } + + # Load the "main-window" widget from the ui file. + self.ui = gtk.Builder() + self.ui.add_from_file(os.path.join(self.datadir, 'ui', 'tools.ui')) + self.ui.connect_signals(callbacks) + self.dialog = self.ui.get_object('tool-manager-dialog') + + self.view = self.ui.get_object('view') + + self.__init_tools_model() + self.__init_tools_view() + + for name in ['input', 'output', 'applicability', 'save-files']: + self.__init_combobox(name) + + self.do_update() + + def expand_from_doc(self, doc): + row = None + + if doc: + if doc.get_language(): + lid = doc.get_language().get_id() + + if lid in self._languages: + row = self._languages[lid] + elif 'plain' in self._languages: + row = self._languages['plain'] + + if not row and None in self._languages: + row = self._languages[None] + + if not row: + return + + self.view.expand_row(row.get_path(), False) + self.view.get_selection().select_path(row.get_path()) + + def run(self, window): + if self.dialog == None: + self.build() + + # Open up language + self.expand_from_doc(window.get_active_document()) + + self.dialog.set_transient_for(window) + window.get_group().add_window(self.dialog) + self.dialog.present() + + def add_accelerator(self, item): + if not item.shortcut: + return + + if item.shortcut in self.accelerators: + if not item in self.accelerators[item.shortcut]: + self.accelerators[item.shortcut].append(item) + else: + self.accelerators[item.shortcut] = [item] + + def remove_accelerator(self, item, shortcut=None): + if not shortcut: + shortcut = item.shortcut + + if not shortcut in self.accelerators: + return + + self.accelerators[shortcut].remove(item) + + if not self.accelerators[shortcut]: + del self.accelerators[shortcut] + + def add_tool_to_language(self, tool, language): + if isinstance(language, gsv.Language): + lid = language.get_id() + else: + lid = language + + if not lid in self._languages: + piter = self.model.append(None, [language]) + + parent = gtk.TreeRowReference(self.model, self.model.get_path(piter)) + self._languages[lid] = parent + else: + parent = self._languages[lid] + + piter = self.model.get_iter(parent.get_path()) + child = self.model.append(piter, [tool]) + + if not tool in self._tool_rows: + self._tool_rows[tool] = [] + + self._tool_rows[tool].append(gtk.TreeRowReference(self.model, self.model.get_path(child))) + return child + + def add_tool(self, tool): + manager = gsv.LanguageManager() + ret = None + + for lang in tool.languages: + l = manager.get_language(lang) + + if l: + ret = self.add_tool_to_language(tool, l) + elif lang == 'plain': + ret = self.add_tool_to_language(tool, 'plain') + + if not ret: + ret = self.add_tool_to_language(tool, None) + + self.add_accelerator(tool) + return ret + + def __init_tools_model(self): + self.tools = ToolLibrary() + self.current_node = None + self.script_hash = None + self.accelerators = dict() + + self.model = gtk.TreeStore(object) + self.view.set_model(self.model) + + for tool in self.tools.tree.tools: + self.add_tool(tool) + + self.model.set_default_sort_func(self.sort_tools) + self.model.set_sort_column_id(-1, gtk.SORT_ASCENDING) + + def sort_tools(self, model, iter1, iter2): + # For languages, sort All before everything else, otherwise alphabetical + t1 = model.get_value(iter1, self.TOOL_COLUMN) + t2 = model.get_value(iter2, self.TOOL_COLUMN) + + if model.iter_parent(iter1) == None: + if t1 == None: + return -1 + + if t2 == None: + return 1 + + def lang_name(lang): + if isinstance(lang, gsv.Language): + return lang.get_name() + else: + return _('Plain Text') + + n1 = lang_name(t1) + n2 = lang_name(t2) + else: + n1 = t1.name + n2 = t2.name + + return cmp(n1.lower(), n2.lower()) + + def __init_tools_view(self): + # Tools column + column = gtk.TreeViewColumn('Tools') + renderer = gtk.CellRendererText() + column.pack_start(renderer, False) + renderer.set_property('editable', True) + self.view.append_column(column) + + column.set_cell_data_func(renderer, self.get_cell_data_cb) + + renderer.connect('edited', self.on_view_label_cell_edited) + renderer.connect('editing-started', self.on_view_label_cell_editing_started) + + self.selection_changed_id = self.view.get_selection().connect('changed', self.on_view_selection_changed, None) + + def __init_combobox(self, name): + combo = self[name] + combo.set_active(0) + + # Convenience function to get an object from its name + def __getitem__(self, key): + return self.ui.get_object(key) + + def set_active_by_name(self, combo_name, option_name): + combo = self[combo_name] + model = combo.get_model() + piter = model.get_iter_first() + while piter is not None: + if model.get_value(piter, self.NAME_COLUMN) == option_name: + combo.set_active_iter(piter) + return True + piter = model.iter_next(piter) + return False + + def get_selected_tool(self): + model, piter = self.view.get_selection().get_selected() + + if piter is not None: + tool = model.get_value(piter, self.TOOL_COLUMN) + + if not isinstance(tool, Tool): + tool = None + + return piter, tool + else: + return None, None + + def compute_hash(self, string): + return hashlib.md5(string).hexdigest() + + def save_current_tool(self): + if self.current_node is None: + return + + if self.current_node.filename is None: + self.current_node.autoset_filename() + + def combo_value(o, name): + combo = o[name] + return combo.get_model().get_value(combo.get_active_iter(), self.NAME_COLUMN) + + self.current_node.input = combo_value(self, 'input') + self.current_node.output = combo_value(self, 'output') + self.current_node.applicability = combo_value(self, 'applicability') + self.current_node.save_files = combo_value(self, 'save-files') + + buf = self['commands'].get_buffer() + script = buf.get_text(*buf.get_bounds()) + h = self.compute_hash(script) + if h != self.script_hash: + # script has changed -> save it + self.current_node.save_with_script([line + "\n" for line in script.splitlines()]) + self.script_hash = h + else: + self.current_node.save() + + self.update_remove_revert() + + def clear_fields(self): + self['accelerator'].set_text('') + + buf = self['commands'].get_buffer() + buf.begin_not_undoable_action() + buf.set_text('') + buf.end_not_undoable_action() + + for nm in ('input', 'output', 'applicability', 'save-files'): + self[nm].set_active(0) + + self['languages_label'].set_text(_('All Languages')) + + def fill_languages_button(self): + if not self.current_node or not self.current_node.languages: + self['languages_label'].set_text(_('All Languages')) + else: + manager = gsv.LanguageManager() + langs = [] + + for lang in self.current_node.languages: + if lang == 'plain': + langs.append(_('Plain Text')) + else: + l = manager.get_language(lang) + + if l: + langs.append(l.get_name()) + + self['languages_label'].set_text(', '.join(langs)) + + def fill_fields(self): + node = self.current_node + self['accelerator'].set_text(default(node.shortcut, '')) + + buf = self['commands'].get_buffer() + script = default(''.join(node.get_script()), '') + + buf.begin_not_undoable_action() + buf.set_text(script) + buf.end_not_undoable_action() + + self.script_hash = self.compute_hash(script) + contenttype = gio.content_type_guess(data=script) + lmanager = gedit.get_language_manager() + language = lmanager.guess_language(content_type=contenttype) + + if language is not None: + buf.set_language(language) + buf.set_highlight_syntax(True) + else: + buf.set_highlight_syntax(False) + + for nm in ('input', 'output', 'applicability', 'save-files'): + model = self[nm].get_model() + piter = model.get_iter_first() + + self.set_active_by_name(nm, + default(node.__getattribute__(nm.replace('-', '_')), + model.get_value(piter, self.NAME_COLUMN))) + + self.fill_languages_button() + + def update_remove_revert(self): + piter, node = self.get_selected_tool() + + removable = node is not None and node.is_local() + + self['remove-tool-button'].set_sensitive(removable) + self['revert-tool-button'].set_sensitive(removable) + + if node is not None and node.is_global(): + self['remove-tool-button'].hide() + self['revert-tool-button'].show() + else: + self['remove-tool-button'].show() + self['revert-tool-button'].hide() + + def do_update(self): + self.update_remove_revert() + + piter, node = self.get_selected_tool() + self.current_node = node + + if node is not None: + self.fill_fields() + self['tool-table'].set_sensitive(True) + else: + self.clear_fields() + self['tool-table'].set_sensitive(False) + + def language_id_from_iter(self, piter): + if not piter: + return None + + tool = self.model.get_value(piter, self.TOOL_COLUMN) + + if isinstance(tool, Tool): + piter = self.model.iter_parent(piter) + tool = self.model.get_value(piter, self.TOOL_COLUMN) + + if isinstance(tool, gsv.Language): + return tool.get_id() + elif tool: + return 'plain' + + return None + + def selected_language_id(self): + # Find current language if there is any + model, piter = self.view.get_selection().get_selected() + + return self.language_id_from_iter(piter) + + def on_new_tool_button_clicked(self, button): + self.save_current_tool() + + # block handlers while inserting a new item + self.view.get_selection().handler_block(self.selection_changed_id) + + self.current_node = Tool(self.tools.tree); + self.current_node.name = _('New tool') + self.tools.tree.tools.append(self.current_node) + + lang = self.selected_language_id() + + if lang: + self.current_node.languages = [lang] + + piter = self.add_tool(self.current_node) + + self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), True) + self.fill_fields() + + self['tool-table'].set_sensitive(True) + self.view.get_selection().handler_unblock(self.selection_changed_id) + + def tool_changed(self, tool, refresh=False): + for row in self._tool_rows[tool]: + self.model.row_changed(row.get_path(), self.model.get_iter(row.get_path())) + + if refresh and tool == self.current_node: + self.fill_fields() + + self.update_remove_revert() + + def on_remove_tool_button_clicked(self, button): + piter, node = self.get_selected_tool() + + if not node: + return + + if node.is_global(): + shortcut = node.shortcut + + if node.parent.revert_tool(node): + self.remove_accelerator(node, shortcut) + self.add_accelerator(node) + + self['revert-tool-button'].set_sensitive(False) + self.fill_fields() + + self.tool_changed(node) + else: + parent = self.model.iter_parent(piter) + language = self.language_id_from_iter(parent) + + self.model.remove(piter) + + if language in node.languages: + node.languages.remove(language) + + self._tool_rows[node] = filter(lambda x: x.valid(), self._tool_rows[node]) + + if not self._tool_rows[node]: + del self._tool_rows[node] + + if node.parent.delete_tool(node): + self.remove_accelerator(node) + self.current_node = None + self.script_hash = None + + if self.model.iter_is_valid(piter): + self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), False) + + self.view.grab_focus() + + path = self._languages[language].get_path() + parent = self.model.get_iter(path) + + if not self.model.iter_has_child(parent): + self.model.remove(parent) + del self._languages[language] + + def on_view_label_cell_edited(self, cell, path, new_text): + if new_text != '': + piter = self.model.get_iter(path) + tool = self.model.get_value(piter, self.TOOL_COLUMN) + + tool.name = new_text + + self.save_current_tool() + self.tool_changed(tool) + + def on_view_label_cell_editing_started(self, renderer, editable, path): + piter = self.model.get_iter(path) + tool = self.model.get_value(piter, self.TOOL_COLUMN) + + if isinstance(editable, gtk.Entry): + editable.set_text(tool.name) + editable.grab_focus() + + def on_view_selection_changed(self, selection, userdata): + self.save_current_tool() + self.do_update() + + def accelerator_collision(self, name, node): + if not name in self.accelerators: + return [] + + ret = [] + + for other in self.accelerators[name]: + if not other.languages or not node.languages: + ret.append(other) + continue + + for lang in other.languages: + if lang in node.languages: + ret.append(other) + continue + + return ret + + def set_accelerator(self, keyval, mod): + # Check whether accelerator already exists + self.remove_accelerator(self.current_node) + + name = gtk.accelerator_name(keyval, mod) + + if name == '': + self.current_node.shorcut = None + self.save_current_tool() + return True + + col = self.accelerator_collision(name, self.current_node) + + if col: + dialog = gtk.MessageDialog(self.dialog, + gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + _('This accelerator is already bound to %s') % (', '.join(map(lambda x: x.name, col)),)) + + dialog.run() + dialog.destroy() + + self.add_accelerator(self.current_node) + return False + + self.current_node.shortcut = name + self.add_accelerator(self.current_node) + self.save_current_tool() + + return True + + def on_accelerator_key_press(self, entry, event): + mask = event.state & gtk.accelerator_get_default_mod_mask() + + if event.keyval == gtk.keysyms.Escape: + entry.set_text(default(self.current_node.shortcut, '')) + self['commands'].grab_focus() + return True + elif event.keyval == gtk.keysyms.Delete \ + or event.keyval == gtk.keysyms.BackSpace: + entry.set_text('') + self.remove_accelerator(self.current_node) + self.current_node.shortcut = None + self['commands'].grab_focus() + return True + elif event.keyval in range(gtk.keysyms.F1, gtk.keysyms.F12 + 1): + # New accelerator + if self.set_accelerator(event.keyval, mask): + entry.set_text(default(self.current_node.shortcut, '')) + self['commands'].grab_focus() + + # Capture all `normal characters` + return True + elif gtk.gdk.keyval_to_unicode(event.keyval): + if mask: + # New accelerator + if self.set_accelerator(event.keyval, mask): + entry.set_text(default(self.current_node.shortcut, '')) + self['commands'].grab_focus() + # Capture all `normal characters` + return True + else: + return False + + def on_accelerator_focus_in(self, entry, event): + if self.current_node is None: + return + if self.current_node.shortcut: + entry.set_text(_('Type a new accelerator, or press Backspace to clear')) + else: + entry.set_text(_('Type a new accelerator')) + + def on_accelerator_focus_out(self, entry, event): + if self.current_node is not None: + entry.set_text(default(self.current_node.shortcut, '')) + self.tool_changed(self.current_node) + + def on_tool_manager_dialog_response(self, dialog, response): + if response == gtk.RESPONSE_HELP: + gedit.help_display(self.dialog, 'gedit', 'gedit-external-tools-plugin') + return + + self.on_tool_manager_dialog_focus_out(dialog, None) + + self.dialog.destroy() + self.dialog = None + self.tools = None + + def on_tool_manager_dialog_focus_out(self, dialog, event): + self.save_current_tool() + + for window in gedit.app_get_default().get_windows(): + helper = window.get_data("ExternalToolsPluginWindowData") + helper.menu.update() + + def get_cell_data_cb(self, column, cell, model, piter): + tool = model.get_value(piter, self.TOOL_COLUMN) + + if tool == None or not isinstance(tool, Tool): + if tool == None: + label = _('All Languages') + elif not isinstance(tool, gsv.Language): + label = _('Plain Text') + else: + label = tool.get_name() + + markup = saxutils.escape(label) + editable = False + else: + escaped = saxutils.escape(tool.name) + + if tool.shortcut: + markup = '%s (%s)' % (escaped, saxutils.escape(tool.shortcut)) + else: + markup = escaped + + editable = True + + cell.set_properties(markup=markup, editable=editable) + + def tool_in_language(self, tool, lang): + if not lang in self._languages: + return False + + ref = self._languages[lang] + parent = ref.get_path() + + for row in self._tool_rows[tool]: + path = row.get_path() + + if path[0] == parent[0]: + return True + + return False + + def update_languages(self, popup): + self.current_node.languages = popup.languages() + self.fill_languages_button() + + piter, node = self.get_selected_tool() + ret = None + + if node: + ref = gtk.TreeRowReference(self.model, self.model.get_path(piter)) + + # Update languages, make sure to inhibit selection change stuff + self.view.get_selection().handler_block(self.selection_changed_id) + + # Remove all rows that are no longer + for row in list(self._tool_rows[self.current_node]): + piter = self.model.get_iter(row.get_path()) + language = self.language_id_from_iter(piter) + + if (not language and not self.current_node.languages) or \ + (language in self.current_node.languages): + continue + + # Remove from language + self.model.remove(piter) + self._tool_rows[self.current_node].remove(row) + + # If language is empty, remove it + parent = self.model.get_iter(self._languages[language].get_path()) + + if not self.model.iter_has_child(parent): + self.model.remove(parent) + del self._languages[language] + + # Now, add for any that are new + manager = gsv.LanguageManager() + + for lang in self.current_node.languages: + if not self.tool_in_language(self.current_node, lang): + l = manager.get_language(lang) + + if not l: + l = 'plain' + + self.add_tool_to_language(self.current_node, l) + + if not self.current_node.languages and not self.tool_in_language(self.current_node, None): + self.add_tool_to_language(self.current_node, None) + + # Check if we can still keep the current + if not ref or not ref.valid(): + # Change selection to first language + path = self._tool_rows[self.current_node][0].get_path() + piter = self.model.get_iter(path) + parent = self.model.iter_parent(piter) + + # Expand parent, select child and scroll to it + self.view.expand_row(self.model.get_path(parent), False) + self.view.get_selection().select_path(path) + self.view.set_cursor(path, self.view.get_column(self.TOOL_COLUMN), False) + + self.view.get_selection().handler_unblock(self.selection_changed_id) + + def on_languages_button_clicked(self, button): + popup = LanguagesPopup(self.current_node.languages) + popup.set_transient_for(self.dialog) + + origin = button.window.get_origin() + popup.move(origin[0], origin[1] - popup.allocation.height) + + popup.connect('destroy', self.update_languages) + +# ex:et:ts=4: diff --git a/plugins/externaltools/tools/outputpanel.py b/plugins/externaltools/tools/outputpanel.py new file mode 100755 index 00000000..a30aad72 --- /dev/null +++ b/plugins/externaltools/tools/outputpanel.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2005-2006 Steve Frécinaux +# Copyright (C) 2010 Per Arneng +# +# 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +__all__ = ('OutputPanel', 'UniqueById') + +import gtk, gedit +import pango +import gobject +import os +from weakref import WeakKeyDictionary +from capture import * +from gtk import gdk +import re +import gio +import linkparsing +import filelookup + +class UniqueById: + __shared_state = WeakKeyDictionary() + + def __init__(self, i): + if i in self.__class__.__shared_state: + self.__dict__ = self.__class__.__shared_state[i] + return True + else: + self.__class__.__shared_state[i] = self.__dict__ + return False + + def states(self): + return self.__class__.__shared_state + +class OutputPanel(UniqueById): + def __init__(self, datadir, window): + if UniqueById.__init__(self, window): + return + + callbacks = { + 'on_stop_clicked' : self.on_stop_clicked, + 'on_view_visibility_notify_event': self.on_view_visibility_notify_event, + 'on_view_motion_notify_event': self.on_view_motion_notify_event, + 'on_view_button_press_event': self.on_view_button_press_event + } + + self.window = window + self.ui = gtk.Builder() + self.ui.add_from_file(os.path.join(datadir, 'ui', 'outputpanel.ui')) + self.ui.connect_signals(callbacks) + + self.panel = self["output-panel"] + self['view'].modify_font(pango.FontDescription('Monospace')) + + buffer = self['view'].get_buffer() + + self.normal_tag = buffer.create_tag('normal') + + self.error_tag = buffer.create_tag('error') + self.error_tag.set_property('foreground', 'red') + + self.italic_tag = buffer.create_tag('italic') + self.italic_tag.set_property('style', pango.STYLE_OBLIQUE) + + self.bold_tag = buffer.create_tag('bold') + self.bold_tag.set_property('weight', pango.WEIGHT_BOLD) + + self.invalid_link_tag = buffer.create_tag('invalid_link') + + self.link_tag = buffer.create_tag('link') + self.link_tag.set_property('underline', pango.UNDERLINE_SINGLE) + + self.link_cursor = gdk.Cursor(gdk.HAND2) + self.normal_cursor = gdk.Cursor(gdk.XTERM) + + self.process = None + + self.links = [] + + self.link_parser = linkparsing.LinkParser() + self.file_lookup = filelookup.FileLookup() + + def set_process(self, process): + self.process = process + + def __getitem__(self, key): + # Convenience function to get an object from its name + return self.ui.get_object(key) + + def on_stop_clicked(self, widget, *args): + if self.process is not None: + self.write("\n" + _('Stopped.') + "\n", + self.italic_tag) + self.process.stop(-1) + + def scroll_to_end(self): + iter = self['view'].get_buffer().get_end_iter() + self['view'].scroll_to_iter(iter, 0.0) + return False # don't requeue this handler + + def clear(self): + self['view'].get_buffer().set_text("") + self.links = [] + + def visible(self): + panel = self.window.get_bottom_panel() + return panel.props.visible and panel.item_is_active(self.panel) + + def write(self, text, tag = None): + buffer = self['view'].get_buffer() + + end_iter = buffer.get_end_iter() + insert = buffer.create_mark(None, end_iter, True) + + if tag is None: + buffer.insert(end_iter, text) + else: + buffer.insert_with_tags(end_iter, text, tag) + + # find all links and apply the appropriate tag for them + links = self.link_parser.parse(text) + for lnk in links: + + insert_iter = buffer.get_iter_at_mark(insert) + lnk.start = insert_iter.get_offset() + lnk.start + lnk.end = insert_iter.get_offset() + lnk.end + + start_iter = buffer.get_iter_at_offset(lnk.start) + end_iter = buffer.get_iter_at_offset(lnk.end) + + tag = None + + # if the link points to an existing file then it is a valid link + if self.file_lookup.lookup(lnk.path) is not None: + self.links.append(lnk) + tag = self.link_tag + else: + tag = self.invalid_link_tag + + buffer.apply_tag(tag, start_iter, end_iter) + + buffer.delete_mark(insert) + gobject.idle_add(self.scroll_to_end) + + def show(self): + panel = self.window.get_bottom_panel() + panel.show() + panel.activate_item(self.panel) + + def update_cursor_style(self, view, x, y): + if self.get_link_at_location(view, x, y) is not None: + cursor = self.link_cursor + else: + cursor = self.normal_cursor + + view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor) + + def on_view_motion_notify_event(self, view, event): + if event.window == view.get_window(gtk.TEXT_WINDOW_TEXT): + self.update_cursor_style(view, int(event.x), int(event.y)) + + return False + + def on_view_visibility_notify_event(self, view, event): + if event.window == view.get_window(gtk.TEXT_WINDOW_TEXT): + x, y, m = event.window.get_pointer() + self.update_cursor_style(view, x, y) + + return False + + def idle_grab_focus(self): + self.window.get_active_view().grab_focus() + return False + + def get_link_at_location(self, view, x, y): + """ + Get the link under a specified x,y coordinate. If no link exists then + None is returned. + """ + + # get the offset within the buffer from the x,y coordinates + buff_x, buff_y = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, + x, y) + iter_at_xy = view.get_iter_at_location(buff_x, buff_y) + offset = iter_at_xy.get_offset() + + # find the first link that contains the offset + for lnk in self.links: + if offset >= lnk.start and offset <= lnk.end: + return lnk + + # no link was found at x,y + return None + + def on_view_button_press_event(self, view, event): + if event.button != 1 or event.type != gdk.BUTTON_PRESS or \ + event.window != view.get_window(gtk.TEXT_WINDOW_TEXT): + return False + + link = self.get_link_at_location(view, int(event.x), int(event.y)) + if link is None: + return False + + gfile = self.file_lookup.lookup(link.path) + + if gfile: + gedit.commands.load_uri(self.window, gfile.get_uri(), None, + link.line_nr) + gobject.idle_add(self.idle_grab_focus) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/outputpanel.ui b/plugins/externaltools/tools/outputpanel.ui new file mode 100755 index 00000000..f0281792 --- /dev/null +++ b/plugins/externaltools/tools/outputpanel.ui @@ -0,0 +1,53 @@ + + + + + True + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + False + GTK_WRAP_WORD + False + False + + + + + + + + + + True + 6 + 6 + GTK_BUTTONBOX_END + + + True + False + gtk-stop + True + + + + + + False + 1 + + + + diff --git a/plugins/externaltools/tools/tools.ui b/plugins/externaltools/tools/tools.ui new file mode 100755 index 00000000..dff7d192 --- /dev/null +++ b/plugins/externaltools/tools/tools.ui @@ -0,0 +1,606 @@ + + + + + + + + + + + + Nothing + nothing + + + Current document + document + + + All documents + all + + + + + + + + + + + Nothing + nothing + + + Current document + document + + + Current selection + selection + + + Current selection (default to document) + selection-document + + + Current line + line + + + Current word + word + + + + + + + + + + + + + Nothing + nothing + + + Display in bottom pane + output-panel + + + Create new document + new-document + + + Append to current document + append-document + + + Replace current document + replace-document + + + Replace current selection + replace-selection + + + Insert at cursor position + insert + + + + + + + + + + + + + All documents + all + + + All documents except untitled ones + titled + + + Local files only + local + + + Remote files only + remote + + + Untitled documents only + untitled + + + + + True + + + External Tools Manager + 750 + 500 + dialog + True + False + + + + + True + + + True + True + 6 + 275 + + + True + 6 + + + True + 0 + _Tools: + True + view + + + False + False + 0 + + + + + True + False + automatic + automatic + in + + + True + True + False + True + + + + + 1 + + + + + True + 6 + + + True + False + True + False + + + + True + gtk-new + 4 + + + + + False + False + 0 + + + + + False + False + + + + True + gtk-revert-to-saved + 4 + + + + + False + False + end + 2 + + + + + True + False + True + False + + + + True + gtk-delete + 4 + + + + + False + False + end + 1 + + + + + False + False + 2 + + + + + False + False + + + + + True + 6 + + + True + 0 + 0.5 + _Edit: + commands + True + + + False + False + 0 + + + + + True + + + True + + + + False + False + 0 + + + + + True + 6 + 2 + 6 + 6 + + + True + True + + + + + + 1 + 2 + 1 + 2 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + True + + + True + model_applicability + + + + 0 + + + + + 0 + False + True + + + + + True + True + + + True + + + + True + All Languages + 0 + 0.5 + PANGO_ELLIPSIZE_MIDDLE + + + + + + + 1 + True + True + + + + + 1 + 2 + 5 + 6 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + True + model_output + + + + 0 + + + + + 1 + 2 + 4 + 5 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + True + model_input + + + + 0 + + + + + 1 + 2 + 3 + 4 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + model_save_files + True + + + + 0 + + + + + 1 + 2 + 2 + 3 + GTK_EXPAND | GTK_SHRINK | GTK_FILL + GTK_FILL + + + + + True + 0 + _Applicability: + True + applicability + + + 5 + 6 + GTK_FILL + + + + + + True + 0 + _Output: + True + output + + + 4 + 5 + GTK_FILL + + + + + + True + 0 + _Input: + True + input + + + 3 + 4 + GTK_FILL + + + + + + 0 + _Save: + True + save-files + True + + + 2 + 3 + GTK_FILL + + + + + + True + 0 + _Shortcut Key: + True + accelerator + + + 1 + 2 + GTK_FILL + + + + + + True + True + automatic + automatic + in + + + commands_buffer + True + True + False + GTK_SOURCE_SMART_HOME_END_AFTER + 2 + True + False + True + + + + + 2 + + + + + 1 + + + + + 1 + + + + + True + False + + + + + 1 + + + + + True + end + + + gtk-help + True + True + True + False + True + + + False + False + 0 + + + + + gtk-close + True + True + True + False + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button1 + button2 + + + 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. + + + + diff --git a/plugins/modelines/Makefile.am b/plugins/modelines/Makefile.am new file mode 100755 index 00000000..ddcfccc8 --- /dev/null +++ b/plugins/modelines/Makefile.am @@ -0,0 +1,38 @@ +# Modelines Plugin +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +INCLUDES = \ + -I$(top_srcdir) \ + $(GEDIT_CFLAGS) \ + $(WARN_CFLAGS) \ + $(DISABLE_DEPRECATED_CFLAGS) + +modelinesdir = $(GEDIT_PLUGINS_DATA_DIR)/modelines +modelines_DATA = \ + language-mappings + +plugin_LTLIBRARIES = libmodelines.la + +libmodelines_la_SOURCES = \ + gedit-modeline-plugin.h \ + gedit-modeline-plugin.c \ + modeline-parser.h \ + modeline-parser.c + +libmodelines_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) +libmodelines_la_LIBADD = $(GEDIT_LIBS) + +plugin_in_files = modelines.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 + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = \ + $(plugin_in_files) \ + $(modelines_DATA) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + + +-include $(top_srcdir)/git.mk diff --git a/plugins/modelines/gedit-modeline-plugin.c b/plugins/modelines/gedit-modeline-plugin.c new file mode 100755 index 00000000..49fc2f69 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.c @@ -0,0 +1,248 @@ +/* + * gedit-modeline-plugin.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux + * + * 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 "gedit-modeline-plugin.h" +#include "modeline-parser.h" + +#include +#include + +#define WINDOW_DATA_KEY "GeditModelinePluginWindowData" +#define DOCUMENT_DATA_KEY "GeditModelinePluginDocumentData" + +typedef struct +{ + gulong tab_added_handler_id; + gulong tab_removed_handler_id; +} WindowData; + +typedef struct +{ + gulong document_loaded_handler_id; + gulong document_saved_handler_id; +} DocumentData; + +static void gedit_modeline_plugin_activate (GeditPlugin *plugin, GeditWindow *window); +static void gedit_modeline_plugin_deactivate (GeditPlugin *plugin, GeditWindow *window); +static GObject *gedit_modeline_plugin_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_param); +static void gedit_modeline_plugin_finalize (GObject *object); + +GEDIT_PLUGIN_REGISTER_TYPE(GeditModelinePlugin, gedit_modeline_plugin) + +static void +window_data_free (WindowData *wdata) +{ + g_slice_free (WindowData, wdata); +} + +static void +document_data_free (DocumentData *ddata) +{ + g_slice_free (DocumentData, ddata); +} + +static void +gedit_modeline_plugin_class_init (GeditModelinePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass); + + object_class->constructor = gedit_modeline_plugin_constructor; + object_class->finalize = gedit_modeline_plugin_finalize; + + plugin_class->activate = gedit_modeline_plugin_activate; + plugin_class->deactivate = gedit_modeline_plugin_deactivate; +} + +static GObject * +gedit_modeline_plugin_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_param) +{ + GObject *object; + gchar *data_dir; + + object = G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->constructor (type, + n_construct_properties, + construct_param); + + data_dir = gedit_plugin_get_data_dir (GEDIT_PLUGIN (object)); + + modeline_parser_init (data_dir); + + g_free (data_dir); + + return object; +} + +static void +gedit_modeline_plugin_init (GeditModelinePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin initializing"); +} + +static void +gedit_modeline_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin finalizing"); + + modeline_parser_shutdown (); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->finalize (object); +} + +static void +on_document_loaded_or_saved (GeditDocument *document, + const GError *error, + GtkSourceView *view) +{ + modeline_parser_apply_modeline (view); +} + +static void +connect_handlers (GeditView *view) +{ + DocumentData *data; + GtkTextBuffer *doc; + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + data = g_slice_new (DocumentData); + + data->document_loaded_handler_id = + g_signal_connect (doc, "loaded", + G_CALLBACK (on_document_loaded_or_saved), + view); + data->document_saved_handler_id = + g_signal_connect (doc, "saved", + G_CALLBACK (on_document_loaded_or_saved), + view); + + g_object_set_data_full (G_OBJECT (doc), DOCUMENT_DATA_KEY, + data, (GDestroyNotify) document_data_free); +} + +static void +disconnect_handlers (GeditView *view) +{ + DocumentData *data; + GtkTextBuffer *doc; + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + + data = g_object_steal_data (G_OBJECT (doc), DOCUMENT_DATA_KEY); + + if (data) + { + g_signal_handler_disconnect (doc, data->document_loaded_handler_id); + g_signal_handler_disconnect (doc, data->document_saved_handler_id); + + document_data_free (data); + } + else + { + g_warning ("Modeline handlers not found"); + } +} + +static void +on_window_tab_added (GeditWindow *window, + GeditTab *tab, + gpointer user_data) +{ + connect_handlers (gedit_tab_get_view (tab)); +} + +static void +on_window_tab_removed (GeditWindow *window, + GeditTab *tab, + gpointer user_data) +{ + disconnect_handlers (gedit_tab_get_view (tab)); +} + +static void +gedit_modeline_plugin_activate (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *wdata; + GList *views; + GList *l; + + gedit_debug (DEBUG_PLUGINS); + + views = gedit_window_get_views (window); + for (l = views; l != NULL; l = l->next) + { + connect_handlers (GEDIT_VIEW (l->data)); + modeline_parser_apply_modeline (GTK_SOURCE_VIEW (l->data)); + } + g_list_free (views); + + wdata = g_slice_new (WindowData); + + wdata->tab_added_handler_id = + g_signal_connect (window, "tab-added", + G_CALLBACK (on_window_tab_added), NULL); + + wdata->tab_removed_handler_id = + g_signal_connect (window, "tab-removed", + G_CALLBACK (on_window_tab_removed), NULL); + + g_object_set_data_full (G_OBJECT (window), WINDOW_DATA_KEY, + wdata, (GDestroyNotify) window_data_free); +} + +static void +gedit_modeline_plugin_deactivate (GeditPlugin *plugin, + GeditWindow *window) +{ + WindowData *wdata; + GList *views; + GList *l; + + gedit_debug (DEBUG_PLUGINS); + + wdata = g_object_steal_data (G_OBJECT (window), WINDOW_DATA_KEY); + + g_signal_handler_disconnect (window, wdata->tab_added_handler_id); + g_signal_handler_disconnect (window, wdata->tab_removed_handler_id); + + window_data_free (wdata); + + views = gedit_window_get_views (window); + + for (l = views; l != NULL; l = l->next) + { + disconnect_handlers (GEDIT_VIEW (l->data)); + + modeline_parser_deactivate (GTK_SOURCE_VIEW (l->data)); + } + + g_list_free (views); +} + diff --git a/plugins/modelines/gedit-modeline-plugin.h b/plugins/modelines/gedit-modeline-plugin.h new file mode 100755 index 00000000..92b01e70 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.h @@ -0,0 +1,48 @@ +/* + * gedit-modeline-plugin.h + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux + * + * 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_MODELINE_PLUGIN_H__ +#define __GEDIT_MODELINE_PLUGIN_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_MODELINE_PLUGIN (gedit_modeline_plugin_get_type ()) +#define GEDIT_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePlugin)) +#define GEDIT_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass)) +#define GEDIT_IS_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_MODELINE_PLUGIN)) +#define GEDIT_IS_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_MODELINE_PLUGIN)) +#define GEDIT_MODELINE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass)) + +/* Private structure type */ +typedef GeditPluginClass GeditModelinePluginClass; +typedef GeditPlugin GeditModelinePlugin; + +GType gedit_modeline_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module); + +G_END_DECLS + +#endif /* __GEDIT_MODELINE_PLUGIN_H__ */ diff --git a/plugins/modelines/language-mappings b/plugins/modelines/language-mappings new file mode 100755 index 00000000..47d00296 --- /dev/null +++ b/plugins/modelines/language-mappings @@ -0,0 +1,14 @@ +[vim] +cs=c-sharp +docbk=docbook +javascript=js +lhaskell=haskell-literate +spec=rpmspec +tex=latex +xhtml=html + +[emacs] +c++=cpp + +[kate] + diff --git a/plugins/modelines/modeline-parser.c b/plugins/modelines/modeline-parser.c new file mode 100755 index 00000000..6feafc55 --- /dev/null +++ b/plugins/modelines/modeline-parser.c @@ -0,0 +1,852 @@ +/* + * modeline-parser.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux + * + * 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 +#include "modeline-parser.h" + +#define MODELINES_LANGUAGE_MAPPINGS_FILE "language-mappings" + +/* base dir to lookup configuration files */ +static gchar *modelines_data_dir; + +/* Mappings: language name -> Gedit language ID */ +static GHashTable *vim_languages; +static GHashTable *emacs_languages; +static GHashTable *kate_languages; + +typedef enum +{ + MODELINE_SET_NONE = 0, + MODELINE_SET_TAB_WIDTH = 1 << 0, + MODELINE_SET_INDENT_WIDTH = 1 << 1, + MODELINE_SET_WRAP_MODE = 1 << 2, + MODELINE_SET_SHOW_RIGHT_MARGIN = 1 << 3, + MODELINE_SET_RIGHT_MARGIN_POSITION = 1 << 4, + MODELINE_SET_LANGUAGE = 1 << 5, + MODELINE_SET_INSERT_SPACES = 1 << 6 +} ModelineSet; + +typedef struct _ModelineOptions +{ + gchar *language_id; + + /* these options are similar to the GtkSourceView properties of the + * same names. + */ + gboolean insert_spaces; + guint tab_width; + guint indent_width; + GtkWrapMode wrap_mode; + gboolean display_right_margin; + guint right_margin_position; + + ModelineSet set; +} ModelineOptions; + +#define MODELINE_OPTIONS_DATA_KEY "ModelineOptionsDataKey" + +static gboolean +has_option (ModelineOptions *options, + ModelineSet set) +{ + return options->set & set; +} + +void +modeline_parser_init (const gchar *data_dir) +{ + modelines_data_dir = g_strdup (data_dir); +} + +void +modeline_parser_shutdown () +{ + if (vim_languages != NULL) + g_hash_table_destroy (vim_languages); + + if (emacs_languages != NULL) + g_hash_table_destroy (emacs_languages); + + if (kate_languages != NULL) + g_hash_table_destroy (kate_languages); + + vim_languages = NULL; + emacs_languages = NULL; + kate_languages = NULL; + + g_free (modelines_data_dir); +} + +static GHashTable * +load_language_mappings_group (GKeyFile *key_file, const gchar *group) +{ + GHashTable *table; + gchar **keys; + gsize length = 0; + int i; + + table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + keys = g_key_file_get_keys (key_file, group, &length, NULL); + + gedit_debug_message (DEBUG_PLUGINS, + "%" G_GSIZE_FORMAT " mappings in group %s", + length, group); + + for (i = 0; i < length; i++) + { + gchar *name = keys[i]; + gchar *id = g_key_file_get_string (key_file, group, name, NULL); + g_hash_table_insert (table, name, id); + } + g_free (keys); + + return table; +} + +/* lazy loading of language mappings */ +static void +load_language_mappings (void) +{ + gchar *fname; + GKeyFile *mappings; + GError *error = NULL; + + fname = g_build_filename (modelines_data_dir, + MODELINES_LANGUAGE_MAPPINGS_FILE, + NULL); + + mappings = g_key_file_new (); + + if (g_key_file_load_from_file (mappings, fname, 0, &error)) + { + gedit_debug_message (DEBUG_PLUGINS, + "Loaded language mappings from %s", + fname); + + vim_languages = load_language_mappings_group (mappings, "vim"); + emacs_languages = load_language_mappings_group (mappings, "emacs"); + kate_languages = load_language_mappings_group (mappings, "kate"); + } + else + { + gedit_debug_message (DEBUG_PLUGINS, + "Failed to loaded language mappings from %s: %s", + fname, error->message); + + g_error_free (error); + } + + g_key_file_free (mappings); + g_free (fname); +} + +static gchar * +get_language_id (const gchar *language_name, GHashTable *mapping) +{ + gchar *name; + gchar *language_id; + + name = g_ascii_strdown (language_name, -1); + + language_id = g_hash_table_lookup (mapping, name); + + if (language_id != NULL) + { + g_free (name); + return g_strdup (language_id); + } + else + { + /* by default assume that the gtksourcevuew id is the same */ + return name; + } +} + +static gchar * +get_language_id_vim (const gchar *language_name) +{ + if (vim_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, vim_languages); +} + +static gchar * +get_language_id_emacs (const gchar *language_name) +{ + if (emacs_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, emacs_languages); +} + +static gchar * +get_language_id_kate (const gchar *language_name) +{ + if (kate_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, kate_languages); +} + +static gboolean +skip_whitespaces (gchar **s) +{ + while (**s != '\0' && g_ascii_isspace (**s)) + (*s)++; + return **s != '\0'; +} + +/* Parse vi(m) modelines. + * Vi(m) modelines looks like this: + * - first form: [text]{white}{vi:|vim:|ex:}[white]{options} + * - second form: [text]{white}{vi:|vim:|ex:}[white]se[t] {options}:[text] + * They can happen on the three first or last lines. + */ +static gchar * +parse_vim_modeline (gchar *s, + ModelineOptions *options) +{ + gboolean in_set = FALSE; + gboolean neg; + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0' && !(in_set && *s == ':')) + { + while (*s != '\0' && (*s == ':' || g_ascii_isspace (*s))) + s++; + + if (*s == '\0') + break; + + if (strncmp (s, "set ", 4) == 0 || + strncmp (s, "se ", 3) == 0) + { + s = strchr(s, ' ') + 1; + in_set = TRUE; + } + + neg = FALSE; + if (strncmp (s, "no", 2) == 0) + { + neg = TRUE; + s += 2; + } + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ':' && *s != '=' && + !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (*s == '=') + { + s++; + while (*s != '\0' && *s != ':' && + !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + } + + if (strcmp (key->str, "ft") == 0 || + strcmp (key->str, "filetype") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_vim (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "et") == 0 || + strcmp (key->str, "expandtab") == 0) + { + options->insert_spaces = !neg; + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "ts") == 0 || + strcmp (key->str, "tabstop") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "sw") == 0 || + strcmp (key->str, "shiftwidth") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->indent_width = intval; + options->set |= MODELINE_SET_INDENT_WIDTH; + } + } + else if (strcmp (key->str, "wrap") == 0) + { + options->wrap_mode = neg ? GTK_WRAP_NONE : GTK_WRAP_WORD; + + options->set |= MODELINE_SET_WRAP_MODE; + } + else if (strcmp (key->str, "textwidth") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->right_margin_position = intval; + options->display_right_margin = TRUE; + + options->set |= MODELINE_SET_SHOW_RIGHT_MARGIN | + MODELINE_SET_RIGHT_MARGIN_POSITION; + + } + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return s; +} + +/* Parse emacs modelines. + * Emacs modelines looks like this: "-*- key1: value1; key2: value2 -*-" + * They can happen on the first line, or on the second one if the first line is + * a shebang (#!) + * See http://www.delorie.com/gnu/docs/emacs/emacs_486.html + */ +static gchar * +parse_emacs_modeline (gchar *s, + ModelineOptions *options) +{ + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0') + { + while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s))) + s++; + if (*s == '\0' || strncmp (s, "-*-", 3) == 0) + break; + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ':' && *s != ';' && + !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (!skip_whitespaces (&s)) + break; + + if (*s != ':') + continue; + s++; + + if (!skip_whitespaces (&s)) + break; + + while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + + gedit_debug_message (DEBUG_PLUGINS, + "Emacs modeline bit: %s = %s", + key->str, value->str); + + /* "Mode" key is case insenstive */ + if (g_ascii_strcasecmp (key->str, "Mode") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_emacs (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "tab-width") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "indent-offset") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->indent_width = intval; + options->set |= MODELINE_SET_INDENT_WIDTH; + } + } + else if (strcmp (key->str, "indent-tabs-mode") == 0) + { + intval = strcmp (value->str, "nil") == 0; + options->insert_spaces = intval; + + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "autowrap") == 0) + { + intval = strcmp (value->str, "nil") != 0; + options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE; + + options->set |= MODELINE_SET_WRAP_MODE; + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return *s == '\0' ? s : s + 2; +} + +/* + * Parse kate modelines. + * Kate modelines are of the form "kate: key1 value1; key2 value2;" + * These can happen on the 10 first or 10 last lines of the buffer. + * See http://wiki.kate-editor.org/index.php/Modelines + */ +static gchar * +parse_kate_modeline (gchar *s, + ModelineOptions *options) +{ + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0') + { + while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s))) + s++; + if (*s == '\0') + break; + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (!skip_whitespaces (&s)) + break; + if (*s == ';') + continue; + + while (*s != '\0' && *s != ';' && + !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + + gedit_debug_message (DEBUG_PLUGINS, + "Kate modeline bit: %s = %s", + key->str, value->str); + + if (strcmp (key->str, "hl") == 0 || + strcmp (key->str, "syntax") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_kate (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "tab-width") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "indent-width") == 0) + { + intval = atoi (value->str); + if (intval) options->indent_width = intval; + } + else if (strcmp (key->str, "space-indent") == 0) + { + intval = strcmp (value->str, "on") == 0 || + strcmp (value->str, "true") == 0 || + strcmp (value->str, "1") == 0; + + options->insert_spaces = intval; + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "word-wrap") == 0) + { + intval = strcmp (value->str, "on") == 0 || + strcmp (value->str, "true") == 0 || + strcmp (value->str, "1") == 0; + + options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE; + + options->set |= MODELINE_SET_WRAP_MODE; + } + else if (strcmp (key->str, "word-wrap-column") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->right_margin_position = intval; + options->display_right_margin = TRUE; + + options->set |= MODELINE_SET_RIGHT_MARGIN_POSITION | + MODELINE_SET_SHOW_RIGHT_MARGIN; + } + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return s; +} + +/* Scan a line for vi(m)/emacs/kate modelines. + * Line numbers are counted starting at one. + */ +static void +parse_modeline (gchar *s, + gint line_number, + gint line_count, + ModelineOptions *options) +{ + gchar prev; + + /* look for the beginning of a modeline */ + for (prev = ' '; (s != NULL) && (*s != '\0'); prev = *(s++)) + { + if (!g_ascii_isspace (prev)) + continue; + + if ((line_number <= 3 || line_number > line_count - 3) && + (strncmp (s, "ex:", 3) == 0 || + strncmp (s, "vi:", 3) == 0 || + strncmp (s, "vim:", 4) == 0)) + { + gedit_debug_message (DEBUG_PLUGINS, "Vim modeline on line %d", line_number); + + while (*s != ':') s++; + s = parse_vim_modeline (s + 1, options); + } + else if (line_number <= 2 && strncmp (s, "-*-", 3) == 0) + { + gedit_debug_message (DEBUG_PLUGINS, "Emacs modeline on line %d", line_number); + + s = parse_emacs_modeline (s + 3, options); + } + else if ((line_number <= 10 || line_number > line_count - 10) && + strncmp (s, "kate:", 5) == 0) + { + gedit_debug_message (DEBUG_PLUGINS, "Kate modeline on line %d", line_number); + + s = parse_kate_modeline (s + 5, options); + } + } +} + +static gboolean +check_previous (GtkSourceView *view, + ModelineOptions *previous, + ModelineSet set) +{ + GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + + /* Do not restore default when this is the first time */ + if (!previous) + return FALSE; + + /* Do not restore default when previous was not set */ + if (!(previous->set & set)) + return FALSE; + + /* Only restore default when setting has not changed */ + switch (set) + { + case MODELINE_SET_INSERT_SPACES: + return gtk_source_view_get_insert_spaces_instead_of_tabs (view) == + previous->insert_spaces; + break; + case MODELINE_SET_TAB_WIDTH: + return gtk_source_view_get_tab_width (view) == previous->tab_width; + break; + case MODELINE_SET_INDENT_WIDTH: + return gtk_source_view_get_indent_width (view) == previous->indent_width; + break; + case MODELINE_SET_WRAP_MODE: + return gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view)) == + previous->wrap_mode; + break; + case MODELINE_SET_RIGHT_MARGIN_POSITION: + return gtk_source_view_get_right_margin_position (view) == + previous->right_margin_position; + break; + case MODELINE_SET_SHOW_RIGHT_MARGIN: + return gtk_source_view_get_show_right_margin (view) == + previous->display_right_margin; + break; + case MODELINE_SET_LANGUAGE: + { + GtkSourceLanguage *language = gtk_source_buffer_get_language (buffer); + + return (language == NULL && previous->language_id == NULL) || + (language != NULL && g_strcmp0 (gtk_source_language_get_id (language), + previous->language_id) == 0); + } + break; + default: + return FALSE; + break; + } +} + +static void +free_modeline_options (ModelineOptions *options) +{ + g_free (options->language_id); + g_slice_free (ModelineOptions, options); +} + +void +modeline_parser_apply_modeline (GtkSourceView *view) +{ + ModelineOptions options; + GtkTextBuffer *buffer; + GtkTextIter iter, liter; + gint line_count; + + options.language_id = NULL; + options.set = MODELINE_SET_NONE; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + gtk_text_buffer_get_start_iter (buffer, &iter); + + line_count = gtk_text_buffer_get_line_count (buffer); + + /* Parse the modelines on the 10 first lines... */ + while ((gtk_text_iter_get_line (&iter) < 10) && + !gtk_text_iter_is_end (&iter)) + { + gchar *line; + + liter = iter; + gtk_text_iter_forward_to_line_end (&iter); + line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE); + + parse_modeline (line, + 1 + gtk_text_iter_get_line (&iter), + line_count, + &options); + + gtk_text_iter_forward_line (&iter); + + g_free (line); + } + + /* ...and on the 10 last ones (modelines are not allowed in between) */ + if (!gtk_text_iter_is_end (&iter)) + { + gint cur_line; + guint remaining_lines; + + /* we are on the 11th line (count from 0) */ + cur_line = gtk_text_iter_get_line (&iter); + /* g_assert (10 == cur_line); */ + + remaining_lines = line_count - cur_line - 1; + + if (remaining_lines > 10) + { + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_iter_backward_lines (&iter, 9); + } + } + + while (!gtk_text_iter_is_end (&iter)) + { + gchar *line; + + liter = iter; + gtk_text_iter_forward_to_line_end (&iter); + line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE); + + parse_modeline (line, + 1 + gtk_text_iter_get_line (&iter), + line_count, + &options); + + gtk_text_iter_forward_line (&iter); + + g_free (line); + } + + /* Try to set language */ + if (has_option (&options, MODELINE_SET_LANGUAGE) && options.language_id) + { + GtkSourceLanguageManager *manager; + GtkSourceLanguage *language; + + manager = gedit_get_language_manager (); + language = gtk_source_language_manager_get_language + (manager, options.language_id); + + if (language != NULL) + { + gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), + language); + } + } + + ModelineOptions *previous = g_object_get_data (G_OBJECT (buffer), + MODELINE_OPTIONS_DATA_KEY); + + /* Apply the options we got from modelines and restore defaults if + we set them before */ + if (has_option (&options, MODELINE_SET_INSERT_SPACES)) + { + gtk_source_view_set_insert_spaces_instead_of_tabs + (view, options.insert_spaces); + } + else if (check_previous (view, previous, MODELINE_SET_INSERT_SPACES)) + { + gtk_source_view_set_insert_spaces_instead_of_tabs + (view, + gedit_prefs_manager_get_insert_spaces ()); + } + + if (has_option (&options, MODELINE_SET_TAB_WIDTH)) + { + gtk_source_view_set_tab_width (view, options.tab_width); + } + else if (check_previous (view, previous, MODELINE_SET_TAB_WIDTH)) + { + gtk_source_view_set_tab_width (view, + gedit_prefs_manager_get_tabs_size ()); + } + + if (has_option (&options, MODELINE_SET_INDENT_WIDTH)) + { + gtk_source_view_set_indent_width (view, options.indent_width); + } + else if (check_previous (view, previous, MODELINE_SET_INDENT_WIDTH)) + { + gtk_source_view_set_indent_width (view, -1); + } + + if (has_option (&options, MODELINE_SET_WRAP_MODE)) + { + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), options.wrap_mode); + } + else if (check_previous (view, previous, MODELINE_SET_WRAP_MODE)) + { + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), + gedit_prefs_manager_get_wrap_mode ()); + } + + if (has_option (&options, MODELINE_SET_RIGHT_MARGIN_POSITION)) + { + gtk_source_view_set_right_margin_position (view, options.right_margin_position); + } + else if (check_previous (view, previous, MODELINE_SET_RIGHT_MARGIN_POSITION)) + { + gtk_source_view_set_right_margin_position (view, + gedit_prefs_manager_get_right_margin_position ()); + } + + if (has_option (&options, MODELINE_SET_SHOW_RIGHT_MARGIN)) + { + gtk_source_view_set_show_right_margin (view, options.display_right_margin); + } + else if (check_previous (view, previous, MODELINE_SET_SHOW_RIGHT_MARGIN)) + { + gtk_source_view_set_show_right_margin (view, + gedit_prefs_manager_get_display_right_margin ()); + } + + if (previous) + { + *previous = options; + previous->language_id = g_strdup (options.language_id); + } + else + { + previous = g_slice_new (ModelineOptions); + *previous = options; + previous->language_id = g_strdup (options.language_id); + + g_object_set_data_full (G_OBJECT (buffer), + MODELINE_OPTIONS_DATA_KEY, + previous, + (GDestroyNotify)free_modeline_options); + } + + g_free (options.language_id); +} + +void +modeline_parser_deactivate (GtkSourceView *view) +{ + g_object_set_data (G_OBJECT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))), + MODELINE_OPTIONS_DATA_KEY, + NULL); +} + +/* vi:ts=8 */ diff --git a/plugins/modelines/modeline-parser.h b/plugins/modelines/modeline-parser.h new file mode 100755 index 00000000..2e8559e4 --- /dev/null +++ b/plugins/modelines/modeline-parser.h @@ -0,0 +1,37 @@ +/* + * modelie-parser.h + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux + * + * 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 __MODELINE_PARSER_H__ +#define __MODELINE_PARSER_H__ + +#include +#include + +G_BEGIN_DECLS + +void modeline_parser_init (const gchar *data_dir); +void modeline_parser_shutdown (void); +void modeline_parser_apply_modeline (GtkSourceView *view); +void modeline_parser_deactivate (GtkSourceView *view); + +G_END_DECLS + +#endif /* __MODELINE_PARSER_H__ */ diff --git a/plugins/modelines/modelines.gedit-plugin.desktop.in b/plugins/modelines/modelines.gedit-plugin.desktop.in new file mode 100755 index 00000000..c72f0199 --- /dev/null +++ b/plugins/modelines/modelines.gedit-plugin.desktop.in @@ -0,0 +1,8 @@ +[Gedit Plugin] +Module=modelines +IAge=2 +_Name=Modelines +_Description=Emacs, Kate and Vim-style modelines support for gedit. +Authors=Steve Frécinaux +Copyright=Copyright © 2005 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/pythonconsole/Makefile.am b/plugins/pythonconsole/Makefile.am new file mode 100755 index 00000000..c27227f3 --- /dev/null +++ b/plugins/pythonconsole/Makefile.am @@ -0,0 +1,15 @@ +# Python Console Plugin +SUBDIRS = pythonconsole +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +plugin_in_files = pythonconsole.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 + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in b/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in new file mode 100755 index 00000000..8cc65648 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in @@ -0,0 +1,10 @@ +[Gedit Plugin] +Loader=python +Module=pythonconsole +IAge=2 +_Name=Python Console +_Description=Interactive Python console standing in the bottom panel +Icon=mate-mime-text-x-python +Authors=Steve Frécinaux +Copyright=Copyright © 2006 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/pythonconsole/pythonconsole/Makefile.am b/plugins/pythonconsole/pythonconsole/Makefile.am new file mode 100755 index 00000000..7aa91fe9 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/Makefile.am @@ -0,0 +1,17 @@ +# Python console plugin + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/pythonconsole +plugin_PYTHON = \ + __init__.py \ + console.py \ + config.py + +uidir = $(GEDIT_PLUGINS_DATA_DIR)/pythonconsole/ui +ui_DATA = config.ui + +EXTRA_DIST = $(ui_DATA) + +CLEANFILES = +DISTCLEANFILES = + +-include $(top_srcdir)/git.mk diff --git a/plugins/pythonconsole/pythonconsole/__init__.py b/plugins/pythonconsole/pythonconsole/__init__.py new file mode 100755 index 00000000..60f70e9f --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/__init__.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +# __init__.py -- plugin object +# +# Copyright (C) 2006 - Steve Frécinaux +# +# 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. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge +# Copyright (C), 2005 Adam Hooper +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import gtk +import gedit + +from console import PythonConsole +from config import PythonConsoleConfigDialog +from config import PythonConsoleConfig + +PYTHON_ICON = 'mate-mime-text-x-python' + +class PythonConsolePlugin(gedit.Plugin): + def __init__(self): + gedit.Plugin.__init__(self) + self.dlg = None + + def activate(self, window): + console = PythonConsole(namespace = {'__builtins__' : __builtins__, + 'gedit' : gedit, + 'window' : window}) + console.eval('print "You can access the main window through ' \ + '\'window\' :\\n%s" % window', False) + bottom = window.get_bottom_panel() + image = gtk.Image() + image.set_from_icon_name(PYTHON_ICON, gtk.ICON_SIZE_MENU) + bottom.add_item(console, _('Python Console'), image) + window.set_data('PythonConsolePluginInfo', console) + + def deactivate(self, window): + console = window.get_data("PythonConsolePluginInfo") + console.stop() + window.set_data("PythonConsolePluginInfo", None) + bottom = window.get_bottom_panel() + bottom.remove_item(console) + +def create_configure_dialog(self): + + if not self.dlg: + self.dlg = PythonConsoleConfigDialog(self.get_data_dir()) + + dialog = self.dlg.dialog() + window = gedit.app_get_default().get_active_window() + if window: + dialog.set_transient_for(window) + + return dialog + +# Here we dynamically insert create_configure_dialog based on if configuration +# is enabled. This has to be done like this because gedit checks if a plugin +# is configurable solely on the fact that it has this member defined or not +if PythonConsoleConfig.enabled(): + PythonConsolePlugin.create_configure_dialog = create_configure_dialog + +# ex:et:ts=4: diff --git a/plugins/pythonconsole/pythonconsole/config.py b/plugins/pythonconsole/pythonconsole/config.py new file mode 100755 index 00000000..fed4699b --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/config.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- + +# config.py -- Config dialog +# +# Copyright (C) 2008 - B. Clausius +# +# 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. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge +# Copyright (C), 2005 Adam Hooper +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import os +import gtk + +__all__ = ('PythonConsoleConfig', 'PythonConsoleConfigDialog') + +MATECONF_KEY_BASE = '/apps/gedit-2/plugins/pythonconsole' +MATECONF_KEY_COMMAND_COLOR = MATECONF_KEY_BASE + '/command-color' +MATECONF_KEY_ERROR_COLOR = MATECONF_KEY_BASE + '/error-color' + +DEFAULT_COMMAND_COLOR = '#314e6c' # Blue Shadow +DEFAULT_ERROR_COLOR = '#990000' # Accent Red Dark + +class PythonConsoleConfig(object): + try: + import mateconf + except ImportError: + mateconf = None + + def __init__(self): + pass + + @staticmethod + def enabled(): + return PythonConsoleConfig.mateconf != None + + @staticmethod + def add_handler(handler): + if PythonConsoleConfig.mateconf: + PythonConsoleConfig.mateconf.client_get_default().notify_add(MATECONF_KEY_BASE, handler) + + color_command = property( + lambda self: self.mateconf_get_str(MATECONF_KEY_COMMAND_COLOR, DEFAULT_COMMAND_COLOR), + lambda self, value: self.mateconf_set_str(MATECONF_KEY_COMMAND_COLOR, value)) + + color_error = property( + lambda self: self.mateconf_get_str(MATECONF_KEY_ERROR_COLOR, DEFAULT_ERROR_COLOR), + lambda self, value: self.mateconf_set_str(MATECONF_KEY_ERROR_COLOR, value)) + + @staticmethod + def mateconf_get_str(key, default=''): + if not PythonConsoleConfig.mateconf: + return default + + val = PythonConsoleConfig.mateconf.client_get_default().get(key) + if val is not None and val.type == mateconf.VALUE_STRING: + return val.get_string() + else: + return default + + @staticmethod + def mateconf_set_str(key, value): + if not PythonConsoleConfig.mateconf: + return + + v = PythonConsoleConfig.mateconf.Value(mateconf.VALUE_STRING) + v.set_string(value) + PythonConsoleConfig.mateconf.client_get_default().set(key, v) + +class PythonConsoleConfigDialog(object): + + def __init__(self, datadir): + object.__init__(self) + self._dialog = None + self._ui_path = os.path.join(datadir, 'ui', 'config.ui') + self.config = PythonConsoleConfig() + + def dialog(self): + if self._dialog is None: + self._ui = gtk.Builder() + self._ui.add_from_file(self._ui_path) + + self.set_colorbutton_color(self._ui.get_object('colorbutton-command'), + self.config.color_command) + self.set_colorbutton_color(self._ui.get_object('colorbutton-error'), + self.config.color_error) + + self._ui.connect_signals(self) + + self._dialog = self._ui.get_object('dialog-config') + self._dialog.show_all() + else: + self._dialog.present() + + return self._dialog + + @staticmethod + def set_colorbutton_color(colorbutton, value): + try: + color = gtk.gdk.color_parse(value) + except ValueError: + pass # Default color in config.ui used + else: + colorbutton.set_color(color) + + def on_dialog_config_response(self, dialog, response_id): + self._dialog.destroy() + + def on_dialog_config_destroy(self, dialog): + self._dialog = None + self._ui = None + + def on_colorbutton_command_color_set(self, colorbutton): + self.config.color_command = colorbutton.get_color().to_string() + + def on_colorbutton_error_color_set(self, colorbutton): + self.config.color_error = colorbutton.get_color().to_string() + +# ex:et:ts=4: diff --git a/plugins/pythonconsole/pythonconsole/config.ui b/plugins/pythonconsole/pythonconsole/config.ui new file mode 100755 index 00000000..4ffe26f6 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/config.ui @@ -0,0 +1,107 @@ + + + + + + center-on-parent + True + dialog + False + + + + + True + + + True + 6 + 2 + 2 + 6 + 6 + + + True + 0 + C_ommand color: + True + colorbutton-command + + + + + True + 0 + _Error color: + True + colorbutton-error + + + 1 + 2 + + + + + True + True + True + #31314e4e6c6c + + + + 1 + 2 + + + + + True + True + True + #999900000000 + + + + 1 + 2 + 1 + 2 + + + + + 1 + + + + + True + end + + + gtk-close + True + True + True + True + + + 0 + + + + + False + end + 0 + + + + + + button1 + + + diff --git a/plugins/pythonconsole/pythonconsole/console.py b/plugins/pythonconsole/pythonconsole/console.py new file mode 100755 index 00000000..e9d7a331 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/console.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- + +# pythonconsole.py -- Console widget +# +# Copyright (C) 2006 - Steve Frécinaux +# +# 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. + +# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py) +# Copyright (C), 1998 James Henstridge +# Copyright (C), 2005 Adam Hooper +# Bits from gedit Python Console Plugin +# Copyrignt (C), 2005 Raphaël Slinckx + +import string +import sys +import re +import traceback +import gobject +import gtk +import pango + +from config import PythonConsoleConfig + +__all__ = ('PythonConsole', 'OutFile') + +class PythonConsole(gtk.ScrolledWindow): + + __gsignals__ = { + 'grab-focus' : 'override', + } + + def __init__(self, namespace = {}): + gtk.ScrolledWindow.__init__(self) + + self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self.set_shadow_type(gtk.SHADOW_IN) + self.view = gtk.TextView() + self.view.modify_font(pango.FontDescription('Monospace')) + self.view.set_editable(True) + self.view.set_wrap_mode(gtk.WRAP_WORD_CHAR) + self.add(self.view) + self.view.show() + + buffer = self.view.get_buffer() + self.normal = buffer.create_tag("normal") + self.error = buffer.create_tag("error") + self.command = buffer.create_tag("command") + + PythonConsoleConfig.add_handler(self.apply_preferences) + self.apply_preferences() + + self.__spaces_pattern = re.compile(r'^\s+') + self.namespace = namespace + + self.block_command = False + + # Init first line + buffer.create_mark("input-line", buffer.get_end_iter(), True) + buffer.insert(buffer.get_end_iter(), ">>> ") + buffer.create_mark("input", buffer.get_end_iter(), True) + + # Init history + self.history = [''] + self.history_pos = 0 + self.current_command = '' + self.namespace['__history__'] = self.history + + # Set up hooks for standard output. + self.stdout = OutFile(self, sys.stdout.fileno(), self.normal) + self.stderr = OutFile(self, sys.stderr.fileno(), self.error) + + # Signals + self.view.connect("key-press-event", self.__key_press_event_cb) + buffer.connect("mark-set", self.__mark_set_cb) + + def do_grab_focus(self): + self.view.grab_focus() + + def apply_preferences(self, *args): + config = PythonConsoleConfig() + self.error.set_property("foreground", config.color_error) + self.command.set_property("foreground", config.color_command) + + def stop(self): + self.namespace = None + + def __key_press_event_cb(self, view, event): + modifier_mask = gtk.accelerator_get_default_mod_mask() + event_state = event.state & modifier_mask + + if event.keyval == gtk.keysyms.d and event_state == gtk.gdk.CONTROL_MASK: + self.destroy() + + elif event.keyval == gtk.keysyms.Return and event_state == gtk.gdk.CONTROL_MASK: + # Get the command + buffer = view.get_buffer() + inp_mark = buffer.get_mark("input") + inp = buffer.get_iter_at_mark(inp_mark) + cur = buffer.get_end_iter() + line = buffer.get_text(inp, cur) + self.current_command = self.current_command + line + "\n" + self.history_add(line) + + # Prepare the new line + cur = buffer.get_end_iter() + buffer.insert(cur, "\n... ") + cur = buffer.get_end_iter() + buffer.move_mark(inp_mark, cur) + + # Keep indentation of precendent line + spaces = re.match(self.__spaces_pattern, line) + if spaces is not None: + buffer.insert(cur, line[spaces.start() : spaces.end()]) + cur = buffer.get_end_iter() + + buffer.place_cursor(cur) + gobject.idle_add(self.scroll_to_end) + return True + + elif event.keyval == gtk.keysyms.Return: + # Get the marks + buffer = view.get_buffer() + lin_mark = buffer.get_mark("input-line") + inp_mark = buffer.get_mark("input") + + # Get the command line + inp = buffer.get_iter_at_mark(inp_mark) + cur = buffer.get_end_iter() + line = buffer.get_text(inp, cur) + self.current_command = self.current_command + line + "\n" + self.history_add(line) + + # Make the line blue + lin = buffer.get_iter_at_mark(lin_mark) + buffer.apply_tag(self.command, lin, cur) + buffer.insert(cur, "\n") + + cur_strip = self.current_command.rstrip() + + if cur_strip.endswith(":") \ + or (self.current_command[-2:] != "\n\n" and self.block_command): + # Unfinished block command + self.block_command = True + com_mark = "... " + elif cur_strip.endswith("\\"): + com_mark = "... " + else: + # Eval the command + self.__run(self.current_command) + self.current_command = '' + self.block_command = False + com_mark = ">>> " + + # Prepare the new line + cur = buffer.get_end_iter() + buffer.move_mark(lin_mark, cur) + buffer.insert(cur, com_mark) + cur = buffer.get_end_iter() + buffer.move_mark(inp_mark, cur) + buffer.place_cursor(cur) + gobject.idle_add(self.scroll_to_end) + return True + + elif event.keyval == gtk.keysyms.KP_Down or event.keyval == gtk.keysyms.Down: + # Next entry from history + view.emit_stop_by_name("key_press_event") + self.history_down() + gobject.idle_add(self.scroll_to_end) + return True + + elif event.keyval == gtk.keysyms.KP_Up or event.keyval == gtk.keysyms.Up: + # Previous entry from history + view.emit_stop_by_name("key_press_event") + self.history_up() + gobject.idle_add(self.scroll_to_end) + return True + + elif event.keyval == gtk.keysyms.KP_Left or event.keyval == gtk.keysyms.Left or \ + event.keyval == gtk.keysyms.BackSpace: + buffer = view.get_buffer() + inp = buffer.get_iter_at_mark(buffer.get_mark("input")) + cur = buffer.get_iter_at_mark(buffer.get_insert()) + if inp.compare(cur) == 0: + if not event_state: + buffer.place_cursor(inp) + return True + return False + + # For the console we enable smart/home end behavior incoditionally + # since it is useful when editing python + + elif (event.keyval == gtk.keysyms.KP_Home or event.keyval == gtk.keysyms.Home) and \ + event_state == event_state & (gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK): + # Go to the begin of the command instead of the begin of the line + buffer = view.get_buffer() + iter = buffer.get_iter_at_mark(buffer.get_mark("input")) + ins = buffer.get_iter_at_mark(buffer.get_insert()) + + while iter.get_char().isspace(): + iter.forward_char() + + if iter.equal(ins): + iter = buffer.get_iter_at_mark(buffer.get_mark("input")) + + if event_state & gtk.gdk.SHIFT_MASK: + buffer.move_mark_by_name("insert", iter) + else: + buffer.place_cursor(iter) + return True + + elif (event.keyval == gtk.keysyms.KP_End or event.keyval == gtk.keysyms.End) and \ + event_state == event_state & (gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK): + + buffer = view.get_buffer() + iter = buffer.get_end_iter() + ins = buffer.get_iter_at_mark(buffer.get_insert()) + + iter.backward_char() + + while iter.get_char().isspace(): + iter.backward_char() + + iter.forward_char() + + if iter.equal(ins): + iter = buffer.get_end_iter() + + if event_state & gtk.gdk.SHIFT_MASK: + buffer.move_mark_by_name("insert", iter) + else: + buffer.place_cursor(iter) + return True + + def __mark_set_cb(self, buffer, iter, name): + input = buffer.get_iter_at_mark(buffer.get_mark("input")) + pos = buffer.get_iter_at_mark(buffer.get_insert()) + self.view.set_editable(pos.compare(input) != -1) + + def get_command_line(self): + buffer = self.view.get_buffer() + inp = buffer.get_iter_at_mark(buffer.get_mark("input")) + cur = buffer.get_end_iter() + return buffer.get_text(inp, cur) + + def set_command_line(self, command): + buffer = self.view.get_buffer() + mark = buffer.get_mark("input") + inp = buffer.get_iter_at_mark(mark) + cur = buffer.get_end_iter() + buffer.delete(inp, cur) + buffer.insert(inp, command) + self.view.grab_focus() + + def history_add(self, line): + if line.strip() != '': + self.history_pos = len(self.history) + self.history[self.history_pos - 1] = line + self.history.append('') + + def history_up(self): + if self.history_pos > 0: + self.history[self.history_pos] = self.get_command_line() + self.history_pos = self.history_pos - 1 + self.set_command_line(self.history[self.history_pos]) + + def history_down(self): + if self.history_pos < len(self.history) - 1: + self.history[self.history_pos] = self.get_command_line() + self.history_pos = self.history_pos + 1 + self.set_command_line(self.history[self.history_pos]) + + def scroll_to_end(self): + iter = self.view.get_buffer().get_end_iter() + self.view.scroll_to_iter(iter, 0.0) + return False + + def write(self, text, tag = None): + buffer = self.view.get_buffer() + if tag is None: + buffer.insert(buffer.get_end_iter(), text) + else: + buffer.insert_with_tags(buffer.get_end_iter(), text, tag) + gobject.idle_add(self.scroll_to_end) + + def eval(self, command, display_command = False): + buffer = self.view.get_buffer() + lin = buffer.get_mark("input-line") + buffer.delete(buffer.get_iter_at_mark(lin), + buffer.get_end_iter()) + + if isinstance(command, list) or isinstance(command, tuple): + for c in command: + if display_command: + self.write(">>> " + c + "\n", self.command) + self.__run(c) + else: + if display_command: + self.write(">>> " + c + "\n", self.command) + self.__run(command) + + cur = buffer.get_end_iter() + buffer.move_mark_by_name("input-line", cur) + buffer.insert(cur, ">>> ") + cur = buffer.get_end_iter() + buffer.move_mark_by_name("input", cur) + self.view.scroll_to_iter(buffer.get_end_iter(), 0.0) + + def __run(self, command): + sys.stdout, self.stdout = self.stdout, sys.stdout + sys.stderr, self.stderr = self.stderr, sys.stderr + + # eval and exec are broken in how they deal with utf8-encoded + # strings so we have to explicitly decode the command before + # passing it along + command = command.decode('utf8') + + try: + try: + r = eval(command, self.namespace, self.namespace) + if r is not None: + print `r` + except SyntaxError: + exec command in self.namespace + except: + if hasattr(sys, 'last_type') and sys.last_type == SystemExit: + self.destroy() + else: + traceback.print_exc() + + sys.stdout, self.stdout = self.stdout, sys.stdout + sys.stderr, self.stderr = self.stderr, sys.stderr + + def destroy(self): + pass + #gtk.ScrolledWindow.destroy(self) + +class OutFile: + """A fake output file object. It sends output to a TK test widget, + and if asked for a file number, returns one set on instance creation""" + def __init__(self, console, fn, tag): + self.fn = fn + self.console = console + self.tag = tag + def close(self): pass + def flush(self): pass + def fileno(self): return self.fn + def isatty(self): return 0 + def read(self, a): return '' + def readline(self): return '' + def readlines(self): return [] + def write(self, s): self.console.write(s, self.tag) + def writelines(self, l): self.console.write(l, self.tag) + def seek(self, a): raise IOError, (29, 'Illegal seek') + def tell(self): raise IOError, (29, 'Illegal seek') + truncate = tell + +# ex:et:ts=4: diff --git a/plugins/quickopen/Makefile.am b/plugins/quickopen/Makefile.am new file mode 100755 index 00000000..4b5faf00 --- /dev/null +++ b/plugins/quickopen/Makefile.am @@ -0,0 +1,15 @@ +# Quick Open Plugin +SUBDIRS = quickopen +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +plugin_in_files = quickopen.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 + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/quickopen/quickopen.gedit-plugin.desktop.in b/plugins/quickopen/quickopen.gedit-plugin.desktop.in new file mode 100755 index 00000000..40f7f040 --- /dev/null +++ b/plugins/quickopen/quickopen.gedit-plugin.desktop.in @@ -0,0 +1,10 @@ +[Gedit Plugin] +Loader=python +Module=quickopen +IAge=2 +_Name=Quick Open +_Description=Quickly open files +Icon=gtk-open +Authors=Jesse van den Kieboom +Copyright=Copyright © 2009 Jesse van den Kieboom +Website=http://www.gedit.org diff --git a/plugins/quickopen/quickopen/Makefile.am b/plugins/quickopen/quickopen/Makefile.am new file mode 100755 index 00000000..88882fdf --- /dev/null +++ b/plugins/quickopen/quickopen/Makefile.am @@ -0,0 +1,13 @@ +# Quick Open Plugin + +plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/quickopen +plugin_PYTHON = \ + __init__.py \ + popup.py \ + virtualdirs.py \ + windowhelper.py + +CLEANFILES = +DISTCLEANFILES = + +-include $(top_srcdir)/git.mk diff --git a/plugins/quickopen/quickopen/__init__.py b/plugins/quickopen/quickopen/__init__.py new file mode 100755 index 00000000..a41c9400 --- /dev/null +++ b/plugins/quickopen/quickopen/__init__.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2009 - 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307, USA. + +import gedit +from windowhelper import WindowHelper + +class QuickOpenPlugin(gedit.Plugin): + def __init__(self): + gedit.Plugin.__init__(self) + + self._popup_size = (450, 300) + self._helpers = {} + + def activate(self, window): + self._helpers[window] = WindowHelper(window, self) + + def deactivate(self, window): + self._helpers[window].deactivate() + del self._helpers[window] + + def update_ui(self, window): + self._helpers[window].update_ui() + + def get_popup_size(self): + return self._popup_size + + def set_popup_size(self, size): + self._popup_size = size + +# ex:ts=8:et: diff --git a/plugins/quickopen/quickopen/popup.py b/plugins/quickopen/quickopen/popup.py new file mode 100755 index 00000000..a80caf31 --- /dev/null +++ b/plugins/quickopen/quickopen/popup.py @@ -0,0 +1,534 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2009 - 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk +import gtk.gdk +import gobject +import os +import gio +import pango +import glib +import fnmatch +import gedit +import xml.sax.saxutils +from virtualdirs import VirtualDirectory + +class Popup(gtk.Dialog): + def __init__(self, window, paths, handler): + gtk.Dialog.__init__(self, + title=_('Quick Open'), + parent=window, + flags=gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR | gtk.DIALOG_MODAL) + + self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + self._open_button = self.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT) + + self._handler = handler + self._build_ui() + + self._dirs = [] + self._cache = {} + self._theme = None + self._cursor = None + self._shift_start = None + + accel_group = gtk.AccelGroup() + accel_group.connect_group(gtk.keysyms.l, gtk.gdk.CONTROL_MASK, 0, self.on_focus_entry) + + self.add_accel_group(accel_group) + + unique = [] + + for path in paths: + if not path.get_uri() in unique: + self._dirs.append(path) + unique.append(path.get_uri()) + + def _build_ui(self): + vbox = self.get_content_area() + vbox.set_spacing(3) + + self._entry = gtk.Entry() + + self._entry.connect('changed', self.on_changed) + self._entry.connect('key-press-event', self.on_key_press_event) + + sw = gtk.ScrolledWindow(None, None) + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + sw.set_shadow_type(gtk.SHADOW_OUT) + + tv = gtk.TreeView() + tv.set_headers_visible(False) + + self._store = gtk.ListStore(gio.Icon, str, object, int) + tv.set_model(self._store) + + self._treeview = tv + tv.connect('row-activated', self.on_row_activated) + + renderer = gtk.CellRendererPixbuf() + column = gtk.TreeViewColumn() + column.pack_start(renderer, False) + column.set_attributes(renderer, gicon=0) + + renderer = gtk.CellRendererText() + column.pack_start(renderer, True) + column.set_attributes(renderer, markup=1) + + column.set_cell_data_func(renderer, self.on_cell_data_cb) + + tv.append_column(column) + sw.add(tv) + + selection = tv.get_selection() + selection.connect('changed', self.on_selection_changed) + selection.set_mode(gtk.SELECTION_MULTIPLE) + + vbox.pack_start(self._entry, False, False, 0) + vbox.pack_start(sw, True, True, 0) + + lbl = gtk.Label() + lbl.set_alignment(0, 0.5) + lbl.set_ellipsize(pango.ELLIPSIZE_MIDDLE) + self._info_label = lbl + + vbox.pack_start(lbl, False, False, 0) + + # Initial selection + self.on_selection_changed(tv.get_selection()) + vbox.show_all() + + def on_cell_data_cb(self, column, cell, model, piter): + path = model.get_path(piter) + + if self._cursor and path == self._cursor.get_path(): + style = self._treeview.get_style() + bg = style.bg[gtk.STATE_PRELIGHT] + + cell.set_property('cell-background-gdk', bg) + cell.set_property('style', pango.STYLE_ITALIC) + else: + cell.set_property('cell-background-set', False) + cell.set_property('style-set', False) + + def _icon_from_stock(self, stock): + theme = gtk.icon_theme_get_default() + size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU) + pixbuf = theme.load_icon(stock, size[0], gtk.ICON_LOOKUP_USE_BUILTIN) + + return pixbuf + + def _list_dir(self, gfile): + entries = [] + + try: + entries = gfile.enumerate_children("standard::*") + except glib.GError: + pass + + children = [] + + for entry in entries: + if isinstance(gfile, VirtualDirectory): + child, entry = entry + else: + child = gfile.get_child(entry.get_name()) + + children.append((child, entry.get_name(), entry.get_file_type(), entry.get_icon())) + + return children + + def _compare_entries(self, a, b, lpart): + if lpart in a: + if lpart in b: + return cmp(a.index(lpart), b.index(lpart)) + else: + return -1 + elif lpart in b: + return 1 + else: + return 0 + + def _match_glob(self, s, glob): + if glob: + glob += '*' + + return fnmatch.fnmatch(s, glob) + + def do_search_dir(self, parts, d): + if not parts or not d: + return [] + + if not d in self._cache: + entries = self._list_dir(d) + entries.sort(lambda x, y: cmp(x[1].lower(), y[1].lower())) + + self._cache[d] = entries + else: + entries = self._cache[d] + + found = [] + newdirs = [] + + lpart = parts[0].lower() + + for entry in entries: + if not entry: + continue + + lentry = entry[1].lower() + + if not lpart or lpart in lentry or self._match_glob(lentry, lpart): + if entry[2] == gio.FILE_TYPE_DIRECTORY: + if len(parts) > 1: + newdirs.append(entry[0]) + else: + found.append(entry) + elif entry[2] == gio.FILE_TYPE_REGULAR and \ + (not lpart or len(parts) == 1): + found.append(entry) + + found.sort(lambda a, b: self._compare_entries(a[1].lower(), b[1].lower(), lpart)) + + if lpart == '..': + newdirs.append(d.get_parent()) + + for dd in newdirs: + found.extend(self.do_search_dir(parts[1:], dd)) + + return found + + def _replace_insensitive(self, s, find, rep): + out = '' + l = s.lower() + find = find.lower() + last = 0 + + if len(find) == 0: + return xml.sax.saxutils.escape(s) + + while True: + m = l.find(find, last) + + if m == -1: + break + else: + out += xml.sax.saxutils.escape(s[last:m]) + rep % (xml.sax.saxutils.escape(s[m:m + len(find)]),) + last = m + len(find) + + return out + xml.sax.saxutils.escape(s[last:]) + + + def make_markup(self, parts, path): + out = [] + + for i in range(0, len(parts)): + out.append(self._replace_insensitive(path[i], parts[i], "%s")) + + return os.sep.join(out) + + def _get_icon(self, f): + query = f.query_info(gio.FILE_ATTRIBUTE_STANDARD_ICON) + + if not query: + return None + else: + return query.get_icon() + + def _make_parts(self, parent, child, pp): + parts = [] + + # We went from parent, to child, using pp + idx = len(pp) - 1 + + while idx >= 0: + if pp[idx] == '..': + parts.insert(0, '..') + else: + parts.insert(0, child.get_basename()) + child = child.get_parent() + + idx -= 1 + + return parts + + def normalize_relative(self, parts): + if not parts: + return [] + + out = self.normalize_relative(parts[:-1]) + + if parts[-1] == '..': + if not out or (out[-1] == '..') or len(out) == 1: + out.append('..') + else: + del out[-1] + else: + out.append(parts[-1]) + + return out + + def _append_to_store(self, item): + if not item in self._stored_items: + self._store.append(item) + self._stored_items[item] = True + + def _clear_store(self): + self._store.clear() + self._stored_items = {} + + def _show_virtuals(self): + for d in self._dirs: + if isinstance(d, VirtualDirectory): + for entry in d.enumerate_children("standard::*"): + self._append_to_store((entry[1].get_icon(), xml.sax.saxutils.escape(entry[1].get_name()), entry[0], entry[1].get_file_type())) + + def _remove_cursor(self): + if self._cursor: + path = self._cursor.get_path() + self._cursor = None + + self._store.row_changed(path, self._store.get_iter(path)) + + def do_search(self): + self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + self._remove_cursor() + + text = self._entry.get_text().strip() + self._clear_store() + + if text == '': + self._show_virtuals() + else: + parts = self.normalize_relative(text.split(os.sep)) + files = [] + + for d in self._dirs: + for entry in self.do_search_dir(parts, d): + pathparts = self._make_parts(d, entry[0], parts) + self._append_to_store((entry[3], self.make_markup(parts, pathparts), entry[0], entry[2])) + + piter = self._store.get_iter_first() + + if piter: + self._treeview.get_selection().select_path(self._store.get_path(piter)) + + self.window.set_cursor(None) + + def do_show(self): + gtk.Window.do_show(self) + + self._entry.grab_focus() + self._entry.set_text("") + + self.do_search() + + def on_changed(self, editable): + self.do_search() + self.on_selection_changed(self._treeview.get_selection()) + + def _shift_extend(self, towhere): + selection = self._treeview.get_selection() + + if not self._shift_start: + model, rows = selection.get_selected_rows() + start = rows[0] + + self._shift_start = gtk.TreeRowReference(self._store, start) + else: + start = self._shift_start.get_path() + + selection.unselect_all() + selection.select_range(start, towhere) + + def _select_index(self, idx, hasctrl, hasshift): + path = (idx,) + + if not (hasctrl or hasshift): + self._treeview.get_selection().unselect_all() + + if hasshift: + self._shift_extend(path) + else: + self._shift_start = None + + if not hasctrl: + self._treeview.get_selection().select_path(path) + + self._treeview.scroll_to_cell(path, None, True, 0.5, 0) + self._remove_cursor() + + if hasctrl or hasshift: + self._cursor = gtk.TreeRowReference(self._store, path) + + piter = self._store.get_iter(path) + self._store.row_changed(path, piter) + + def _move_selection(self, howmany, hasctrl, hasshift): + num = self._store.iter_n_children(None) + + if num == 0: + return True + + # Test for cursor + path = None + + if self._cursor: + path = self._cursor.get_path() + else: + model, rows = self._treeview.get_selection().get_selected_rows() + + if len(rows) == 1: + path = rows[0] + + if not path: + if howmany > 0: + self._select_index(0, hasctrl, hasshift) + else: + self._select_index(num - 1, hasctrl, hasshift) + else: + idx = path[0] + + if idx + howmany < 0: + self._select_index(0, hasctrl, hasshift) + elif idx + howmany >= num: + self._select_index(num - 1, hasctrl, hasshift) + else: + self._select_index(idx + howmany, hasctrl, hasshift) + + return True + + def _direct_file(self): + uri = self._entry.get_text() + gfile = None + + if gedit.utils.uri_is_valid(uri): + gfile = gio.File(uri) + elif os.path.isabs(uri): + f = gio.File(uri) + + if f.query_exists(): + gfile = f + + return gfile + + def _activate(self): + model, rows = self._treeview.get_selection().get_selected_rows() + ret = True + + for row in rows: + s = model.get_iter(row) + info = model.get(s, 2, 3) + + if info[1] != gio.FILE_TYPE_DIRECTORY: + ret = ret and self._handler(info[0]) + else: + text = self._entry.get_text() + + for i in range(len(text) - 1, -1, -1): + if text[i] == os.sep: + break + + self._entry.set_text(os.path.join(text[:i], os.path.basename(info[0].get_uri())) + os.sep) + self._entry.set_position(-1) + self._entry.grab_focus() + return True + + if rows and ret: + self.destroy() + + if not rows: + gfile = self._direct_file() + + if gfile and self._handler(gfile): + self.destroy() + else: + ret = False + else: + ret = False + + return ret + + def toggle_cursor(self): + if not self._cursor: + return + + path = self._cursor.get_path() + selection = self._treeview.get_selection() + + if selection.path_is_selected(path): + selection.unselect_path(path) + else: + selection.select_path(path) + + def on_key_press_event(self, widget, event): + move_mapping = { + gtk.keysyms.Down: 1, + gtk.keysyms.Up: -1, + gtk.keysyms.Page_Down: 5, + gtk.keysyms.Page_Up: -5 + } + + if event.keyval == gtk.keysyms.Escape: + self.destroy() + return True + elif event.keyval in move_mapping: + return self._move_selection(move_mapping[event.keyval], event.state & gtk.gdk.CONTROL_MASK, event.state & gtk.gdk.SHIFT_MASK) + elif event.keyval in [gtk.keysyms.Return, gtk.keysyms.KP_Enter, gtk.keysyms.Tab, gtk.keysyms.ISO_Left_Tab]: + return self._activate() + elif event.keyval == gtk.keysyms.space and event.state & gtk.gdk.CONTROL_MASK: + self.toggle_cursor() + + return False + + def on_row_activated(self, view, path, column): + self._activate() + + def do_response(self, response): + if response != gtk.RESPONSE_ACCEPT or not self._activate(): + self.destroy() + + def on_selection_changed(self, selection): + model, rows = selection.get_selected_rows() + + gfile = None + fname = None + + if not rows: + gfile = self._direct_file() + elif len(rows) == 1: + gfile = model.get(model.get_iter(rows[0]), 2)[0] + else: + fname = '' + + if gfile: + if gfile.is_native(): + fname = xml.sax.saxutils.escape(gfile.get_path()) + else: + fname = xml.sax.saxutils.escape(gfile.get_uri()) + + self._open_button.set_sensitive(fname != None) + self._info_label.set_markup(fname or '') + + def on_focus_entry(self, group, accel, keyval, modifier): + self._entry.grab_focus() + +gobject.type_register(Popup) + +# ex:ts=8:et: diff --git a/plugins/quickopen/quickopen/virtualdirs.py b/plugins/quickopen/quickopen/virtualdirs.py new file mode 100755 index 00000000..ef0b8dc4 --- /dev/null +++ b/plugins/quickopen/quickopen/virtualdirs.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2009 - 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307, USA. + +import gtk +import gio + +class VirtualDirectory: + def __init__(self, name): + self._name = name + self._children = [] + + def get_uri(self): + return 'virtual://' + self._name + + def get_parent(self): + return None + + def enumerate_children(self, attr): + return self._children + + def append(self, child): + if not child.is_native(): + return + + try: + info = child.query_info("standard::*") + + if info: + self._children.append((child, info)) + except: + pass + +class RecentDocumentsDirectory(VirtualDirectory): + def __init__(self, maxitems=10, screen=None): + VirtualDirectory.__init__(self, 'recent') + + self._maxitems = maxitems + self.fill(screen) + + def fill(self, screen): + if screen: + manager = gtk.recent_manager_get_for_screen(screen) + else: + manager = gtk.recent_manager_get_default() + + items = manager.get_items() + items.sort(lambda a, b: cmp(b.get_visited(), a.get_visited())) + + added = 0 + + for item in items: + if item.has_group('gedit'): + self.append(gio.File(item.get_uri())) + added += 1 + + if added >= self._maxitems: + break + +class CurrentDocumentsDirectory(VirtualDirectory): + def __init__(self, window): + VirtualDirectory.__init__(self, 'documents') + + self.fill(window) + + def fill(self, window): + for doc in window.get_documents(): + location = doc.get_location() + if location: + self.append(location) + +# ex:ts=8:et: diff --git a/plugins/quickopen/quickopen/windowhelper.py b/plugins/quickopen/quickopen/windowhelper.py new file mode 100755 index 00000000..70ea26f0 --- /dev/null +++ b/plugins/quickopen/quickopen/windowhelper.py @@ -0,0 +1,198 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2009 - 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 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, +# Boston, MA 02111-1307, USA. + +import gedit +import gtk +from popup import Popup +import os +import gedit.commands +import gio +import glib +from virtualdirs import RecentDocumentsDirectory +from virtualdirs import CurrentDocumentsDirectory + +ui_str = """ + + + + + + + + +""" + +class WindowHelper: + def __init__(self, window, plugin): + self._window = window + self._plugin = plugin + + self._popup = None + self._install_menu() + + def deactivate(self): + self._uninstall_menu() + self._window = None + self._plugin = None + + def update_ui(self): + pass + + def _uninstall_menu(self): + manager = self._window.get_ui_manager() + + manager.remove_ui(self._ui_id) + manager.remove_action_group(self._action_group) + + manager.ensure_update() + + def _install_menu(self): + manager = self._window.get_ui_manager() + self._action_group = gtk.ActionGroup("GeditQuickOpenPluginActions") + self._action_group.add_actions([ + ("QuickOpen", gtk.STOCK_OPEN, _("Quick open"), + 'O', _("Quickly open documents"), + self.on_quick_open_activate) + ]) + + manager.insert_action_group(self._action_group, -1) + self._ui_id = manager.add_ui_from_string(ui_str) + + def _create_popup(self): + paths = [] + + # Open documents + paths.append(CurrentDocumentsDirectory(self._window)) + + doc = self._window.get_active_document() + + # Current document directory + if doc and doc.is_local(): + gfile = doc.get_location() + paths.append(gfile.get_parent()) + + # File browser root directory + if gedit.version[0] > 2 or (gedit.version[0] == 2 and (gedit.version[1] > 26 or (gedit.version[1] == 26 and gedit.version[2] >= 2))): + bus = self._window.get_message_bus() + + try: + msg = bus.send_sync('/plugins/filebrowser', 'get_root') + + if msg: + uri = msg.get_value('uri') + + if uri: + gfile = gio.File(uri) + + if gfile.is_native(): + paths.append(gfile) + + except StandardError: + pass + + # Recent documents + paths.append(RecentDocumentsDirectory(screen=self._window.get_screen())) + + # Local bookmarks + for path in self._local_bookmarks(): + paths.append(path) + + # Desktop directory + desktopdir = self._desktop_dir() + + if desktopdir: + paths.append(gio.File(desktopdir)) + + # Home directory + paths.append(gio.File(os.path.expanduser('~'))) + + self._popup = Popup(self._window, paths, self.on_activated) + + self._popup.set_default_size(*self._plugin.get_popup_size()) + self._popup.set_transient_for(self._window) + self._popup.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + + self._window.get_group().add_window(self._popup) + + self._popup.connect('destroy', self.on_popup_destroy) + + def _local_bookmarks(self): + filename = os.path.expanduser('~/.gtk-bookmarks') + + if not os.path.isfile(filename): + return [] + + paths = [] + + for line in file(filename, 'r').xreadlines(): + uri = line.strip().split(" ")[0] + f = gio.File(uri) + + if f.is_native(): + try: + info = f.query_info("standard::type") + + if info and info.get_file_type() == gio.FILE_TYPE_DIRECTORY: + paths.append(f) + except glib.GError: + pass + + return paths + + def _desktop_dir(self): + config = os.getenv('XDG_CONFIG_HOME') + + if not config: + config = os.path.expanduser('~/.config') + + config = os.path.join(config, 'user-dirs.dirs') + desktopdir = None + + if os.path.isfile(config): + for line in file(config, 'r').xreadlines(): + line = line.strip() + + if line.startswith('XDG_DESKTOP_DIR'): + parts = line.split('=', 1) + desktopdir = os.path.expandvars(parts[1].strip('"').strip("'")) + break + + if not desktopdir: + desktopdir = os.path.expanduser('~/Desktop') + + return desktopdir + + # Callbacks + def on_quick_open_activate(self, action): + if not self._popup: + self._create_popup() + + self._popup.show() + + def on_popup_destroy(self, popup): + alloc = popup.get_allocation() + self._plugin.set_popup_size((alloc.width, alloc.height)) + + self._popup = None + + def on_activated(self, gfile): + gedit.commands.load_uri(self._window, gfile.get_uri(), None, -1) + return True + +# ex:ts=8:et: diff --git a/plugins/snippets/Makefile.am b/plugins/snippets/Makefile.am new file mode 100755 index 00000000..06f0009b --- /dev/null +++ b/plugins/snippets/Makefile.am @@ -0,0 +1,15 @@ +# Python snippets plugin +SUBDIRS = snippets data +plugindir = $(GEDIT_PLUGINS_LIBS_DIR) + +plugin_in_files = snippets.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 + +plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin) + +EXTRA_DIST = $(plugin_in_files) + +CLEANFILES = $(plugin_DATA) +DISTCLEANFILES = $(plugin_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/snippets/data/Makefile.am b/plugins/snippets/data/Makefile.am new file mode 100755 index 00000000..8ec40419 --- /dev/null +++ b/plugins/snippets/data/Makefile.am @@ -0,0 +1,33 @@ +# Python snippets plugin +SUBDIRS = lang + +snippets_DATA = \ + css.xml \ + c.xml \ + cpp.xml \ + chdr.xml \ + docbook.xml \ + fortran.xml \ + global.xml \ + haskell.xml \ + html.xml \ + idl.xml \ + javascript.xml \ + java.xml \ + latex.xml \ + mallard.xml \ + perl.xml \ + php.xml \ + python.xml \ + ruby.xml \ + sh.xml \ + snippets.xml \ + tcl.xml \ + xml.xml \ + xslt.xml + +snippetsdir = $(GEDIT_PLUGINS_DATA_DIR)/snippets + +EXTRA_DIST = $(snippets_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/snippets/data/c.xml b/plugins/snippets/data/c.xml new file mode 100755 index 00000000..61171cb8 --- /dev/null +++ b/plugins/snippets/data/c.xml @@ -0,0 +1,283 @@ + + + + ]} + * This file is part of ${2:} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '' > + * + * ${2} 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 of the License, or + * (at your option) any later version. + * + * ${2} 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 ${2}; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +$0]]> + gpl + GPL License + + + ]} + * This file is part of ${2:} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '' > + * + * ${2} is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * ${2} 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +$0]]> + lgpl + LGPL License + + + + do + do .. while + + + + for + for loop + + + + while + while loop + + + + if + if + + + + elif + else if + + + + else + else + + + +$0]]> + Inc + #include <..> + + + + inc + #include ".." + + + + main + main + + + + struct + struct + + + + #endif + period]]> + + + + td + typedef + + + + +#define $<[1]: return up_str >_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), $<[1]: return type_str >, $<[1]: return camel_str >Private)) + +struct _$<[1]: return camel_str >Private +{ +}; + +G_DEFINE_TYPE ($<[1]: return camel_str >, $<[1]: return low_str >, ${2:G_TYPE_OBJECT}) + +static void +$<[1]: return low_str>_finalize (GObject *object) +{ + G_OBJECT_CLASS ($<[1]: return low_str >_parent_class)->finalize (object); +} + +static void +$<[1]: return low_str >_class_init ($<[1]: return camel_str >Class *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = $<[1]: return low_str >_finalize; + + g_type_class_add_private (object_class, sizeof ($<[1]: return camel_str >Private)); +} + +static void +$<[1]: return low_str >_init ($<[1]: return camel_str> *self) +{ + self->priv = $<[1]: return up_str >_GET_PRIVATE (self); +} + +$<[1]: return camel_str > * +$<[1]: return low_str >_new () +{ + return g_object_new ($<[1]: return type_str >, NULL); +}]]> + gobject + GObject template + + + +/* Default implementation */ +static const gchar * +$<[1]: return low_str>_example_method_default ($<[1]: return camel_str > *self) +{ + g_return_val_if_reached (NULL); +} + +static void +$<[1]: return low_str>_init ($<[1]: return camel_str >Iface *iface) +{ + static gboolean initialized = FALSE; + + iface->example_method = $<[1]: return low_str>_example_method_default; + + if (!initialized) + { + initialized = TRUE; + } +} + +/* + * This is an method example for an interface + */ +const gchar * +$<[1]: return low_str>_example_method ($<[1]: return camel_str > *self) +{ + g_return_val_if_fail ($<[1]: return up_str> (self), NULL); + return $<[1]: return up_str>_GET_INTERFACE (self)->example_method (self); +} + +GType +$<[1]: return low_str>_get_type () +{ + static GType $<[1]: return low_str>_type_id = 0; + + if (!$<[1]: return low_str>_type_id) + { + static const GTypeInfo g_define_type_info = + { + sizeof ($<[1]: return camel_str >Iface), + (GBaseInitFunc) $<[1]: return low_str>_init, + NULL, + NULL, + NULL, + NULL, + 0, + 0, + NULL + }; + + $<[1]: return low_str>_type_id = + g_type_register_static (G_TYPE_INTERFACE, + "$<[1]: return camel_str>", + &g_define_type_info, + 0); + } + + return $<[1]: return low_str>_type_id; +}]]> + ginterface + GObject interface + + diff --git a/plugins/snippets/data/chdr.xml b/plugins/snippets/data/chdr.xml new file mode 100755 index 00000000..f71ea901 --- /dev/null +++ b/plugins/snippets/data/chdr.xml @@ -0,0 +1,241 @@ + + + + + Header Include-Guard + once + + + + #include ".." + inc + + + +$0]]> + #include <..> + Inc + + + + namespace .. + namespace + + + ]} + * This file is part of ${2:} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '' > + * + * ${2} 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 of the License, or + * (at your option) any later version. + * + * ${2} 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 ${2}; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + +$0]]> + gpl + GPL License + + + ]} + * This file is part of ${2:} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '' > + * + * ${2} is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * ${2} 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +$0]]> + lgpl + LGPL License + + + + td + typedef + + + + class .. + class + + + + struct + struct + + + ]]> + template <typename ..> + template + + + + +G_BEGIN_DECLS + +$< +global camel_str +components = $1.split('_') +type_str = '_'.join([components[0], 'TYPE'] + components[1:]) +is_str = '_'.join([components[0], 'IS'] + components[1:]) +camel_str = '' + +for t in components: + camel_str += t.capitalize() + +items = [ \ +['#define ' + type_str, '(' + $1.lower() + '_get_type ())'], \ +['#define ' + $1 + '(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + '))'], \ +['#define ' + $1 + '_CONST(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + ' const))'], \ +['#define ' + $1 + '_CLASS(klass)', '(G_TYPE_CHECK_CLASS_CAST ((klass), ' + type_str + ', ' + camel_str + 'Class))'], \ +['#define ' + is_str + '(obj)', '(G_TYPE_CHECK_INSTANCE_TYPE ((obj), ' + type_str + '))'], \ +['#define ' + is_str + '_CLASS(klass)', '(G_TYPE_CHECK_CLASS_TYPE ((klass), ' + type_str + '))'], \ +['#define ' + $1 + '_GET_CLASS(obj)', '(G_TYPE_INSTANCE_GET_CLASS ((obj), ' + type_str + ', ' + camel_str + 'Class))'] +] + +return align(items) > + +$<[1]: +items = [ \ +['typedef struct _' + camel_str, camel_str + ';'], \ +['typedef struct _' + camel_str + 'Class', camel_str + 'Class;'], \ +['typedef struct _' + camel_str + 'Private', camel_str + 'Private;'] \ +] + +return align(items) > + +struct _$<[1]: return camel_str > { + ${7:GObject} parent; + + $<[1]: return camel_str >Private *priv; +}; + +struct _$<[1]: return camel_str >Class { + $7Class parent_class; +}; + +GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST; +$<[1]: return camel_str > *$< return $1.lower()>_new (void); + +$0 +G_END_DECLS + +#endif /* __$1_H__ */]]> + gobject + GObject template + + + + +G_BEGIN_DECLS + +$< +global camel_str +components = $1.split('_') +type_str = '_'.join([components[0], 'TYPE'] + components[1:]) +is_str = '_'.join([components[0], 'IS'] + components[1:]) +camel_str = '' + +for t in components: + camel_str += t.capitalize() + +items = [ \ +['#define ' + type_str, '(' + $1.lower() + '_get_type ())'], \ +['#define ' + $1 + '(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + '))'], \ +['#define ' + is_str + '(obj)', '(G_TYPE_CHECK_INSTANCE_TYPE ((obj), ' + type_str + '))'], \ +['#define ' + $1 + '_GET_INTERFACE(obj)', '(G_TYPE_INSTANCE_GET_INTERFACE ((obj), ' + type_str + ', ' + camel_str + 'Iface))'] +] + +return align(items) > + +$<[1]: +items = [ \ +['typedef struct _' + camel_str, camel_str + ';'], \ +['typedef struct _' + camel_str + 'Iface', camel_str + 'Iface;'], \ +] + +return align(items) > + +struct _$<[1]: return camel_str >Iface +{ + ${7:GTypeInterface} parent; + + const gchar * (*example_method) ($<[1]: return camel_str > *self); +}; + +GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST; + +const gchar *$< return $1.lower()>_example_method ($<[1]: return camel_str > *self); +$0 +G_END_DECLS + +#endif /* __$1_H__ */]]> + ginterface + GObject interface + + diff --git a/plugins/snippets/data/cpp.xml b/plugins/snippets/data/cpp.xml new file mode 100755 index 00000000..7c7ccabd --- /dev/null +++ b/plugins/snippets/data/cpp.xml @@ -0,0 +1,183 @@ + + + + + main + main + + + + for loop + for + + + + $1.begin + beginend + + + + do .. while + do + + + + period]]> + #endif + + + + if .. + if + + + + #include ".." + inc + + + +$0]]> + #include <..> + Inc + + + + namespace .. + namespace + + + v; +if (FILE* fp = fopen (${1:"filename"}, "r")) +{ + uint8_t buf[1024]; + while (size_t len = fread (buf, 1, sizeof (buf), fp)) + v.insert (v.end(), buf, buf + len); + fclose(fp); +} +$0]]> + Read File Into Vector + readfile + + + ${3:map}; +$0]]> + std::map + map + + + ${2:v}; +$0]]> + std::vector + vector + + + + struct .. + struct + + + ]]> + template <typename ..> + template + + + ]} + * This file is part of ${2:} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '' > + * + * ${2} 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 of the License, or + * (at your option) any later version. + * + * ${2} 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 ${2}; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + */ + + $0]]> + gpl + GPL License + + + ]} + * This file is part of ${2:} + * + * Copyright (C) $<3: import datetime; return str(datetime.date.today().year)> - $<4: +import pwd, os +try: + return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0] +except KeyError: + return '' > + * + * ${2} is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * ${2} 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + $0]]> + lgpl + LGPL License + + + + td + typedef + + + + while + while + + diff --git a/plugins/snippets/data/css.xml b/plugins/snippets/data/css.xml new file mode 100755 index 00000000..babca912 --- /dev/null +++ b/plugins/snippets/data/css.xml @@ -0,0 +1,557 @@ + + + + + background-attachment: scroll/fixed + background + + + + background-color: color-hex + background + + + + background-color: color-name + background + + + + background-color: color-rgb + background + + + + background: color image repeat attachment position + background + + + + background-color: transparent + background + + + + background-image: none + background + + + + background-image: url + background + + + + background-position: position + background + + + + background-repeat: r/r-x/r-y/n-r + background + + + + border-bottom-color: size style color + border + + + + border-bottom: size style color + border + + + + border-bottom-style: size style color + border + + + + border-bottom-width: size style color + border + + + + border-color: color + border + + + + border-left-color: color + border + + + + border-left: size style color + border + + + + border-left-style: style + border + + + + border-left-width: size + border + + + + border-right-color: color + border + + + + border-right: size style color + border + + + + border-right-style: style + border + + + + border-right-width: size + border + + + + border: size style color + border + + + + border-style: style + border + + + + border-top-color: color + border + + + + border-top: size style color + border + + + + border-top-style: style + border + + + + border-top-width: size + border + + + + border-width: width + border + + + + clear: value + clear + + + + color: color-hex + color + + + + color: color-name + color + + + + color: color-rgb + color + + + + cursor: type + cursor + + + + cursor: url + clear + + + + direction: ltr|rtl + direction + + + + display: block + display + + + + display: common-types + display + + + + display: inline + display + + + + display: table-types + display + + + + float: left/right/none + float + + + + font-family: family + font + + + + font: size font + font + + + + font-size: size + font + + + + font-style: normal/italic/oblique + font + + + + font: style variant weight size/line-height font-family + font + + + + font-variant: normal/small-caps + font + + + + font-weight: weight + font + + + + letter-spacing: length-em + letter + + + + letter-spacing: length-px + letter + + + + list-style-image: url + list + + + + list-style-position: pos + list + + + + list-style-type: asian + list + + + + list-style-type: marker + list + + + + list-style-type: numeric + list + + + + list-style-type: other + list + + + + list-style: type position image + list + + + + list-style-type: roman-alpha-greek + list + + + + margin: all + margin + + + + margin-bottom: length + margin + + + + margin-left: length + margin + + + + margin-right: length + margin + + + + margin-top: length + margin + + + + margin: T R B L + margin + + + + margin: V H + margin + + + + marker-offset: auto + marker + + + + marker-offset: length + marker + + + + overflow: type + overflow + + + + padding: all + padding + + + + padding-bottom: length + margin + + + + padding-left: length + margin + + + + padding-right: length + margin + + + + padding-top: length + margin + + + + padding: T R B L + padding + + + + padding: V H + padding + + + + position: type + position + + + + properties { } + { + + + + text-align: left/center/right + text + + + + text-decoration: none/underline/overline/line-through/blink + text + + + + text-indent: length + text + + + + text-shadow: color-hex x y blur + text + + + + text-shadow: color-rgb x y blur + text + + + + text-shadow: none + text + + + + text-transform: capitalize/upper/lower + text + + + + text-transform: none + text + + + + vertical-align: type + vertical + + + + visibility: type + visibility + + + + white-space: normal/pre/nowrap + white + + + + word-spacing: length + word + + + + word-spacing: normal + word + + + + z-index: index + z + + diff --git a/plugins/snippets/data/docbook.xml b/plugins/snippets/data/docbook.xml new file mode 100755 index 00000000..3159b603 --- /dev/null +++ b/plugins/snippets/data/docbook.xml @@ -0,0 +1,118 @@ + + + + + $0]]> + XML tag + < + + + $1$2 +]]> + menu + menuchoice + + + + ${1:Ctrl}${2}]]> + key + keycombo + + + + + ${3} +]]> + sect + sect* + + + + &app;]]> + app + app entity + + + + $GEDIT_SELECTED_TEXT]]> + application + application tag + + + + $GEDIT_SELECTED_TEXT]]> + enclose + enclose selected text + + + + + + ${1} + +]]> + ul + itemized list + + + + + + ${1} + +]]> + ol + ordered list + + + + + ${1} +]]> + li + list item + + + + + $1 +]]> + vl + variablelist + + + + ${1} + + ${2} + +]]> + vli + variablelist entry + + + + ]]> + / + para close + + + + ]]> + p + para open + + + + $2]]> + http + ulink http + + + + $2]]> + help + ulink mate help + + + diff --git a/plugins/snippets/data/fortran.xml b/plugins/snippets/data/fortran.xml new file mode 100755 index 00000000..c64d6461 --- /dev/null +++ b/plugins/snippets/data/fortran.xml @@ -0,0 +1,164 @@ + + + + + c + character + + + + cl + close + + + + do + do ... end do + + + + func + function + + + + ifel + if ... else ... end if + + + + if + if ... end if + + + + i + integer + + + + ida + integerdimalloc + + + + id + integerdim + + + + l + logical + + + + mod + module + + + + op + open + + + + prog + program + + + + re + read + + + + r + real + + + + rda + realdimalloc + + + + rd + realdim + + + + rec + recursivfunc + + + + sel + select + + + + sub + subroutine + + + + t + type + + + + dow + while + + + + wr + write + + diff --git a/plugins/snippets/data/global.xml b/plugins/snippets/data/global.xml new file mode 100755 index 00000000..afe3c0b7 --- /dev/null +++ b/plugins/snippets/data/global.xml @@ -0,0 +1,2 @@ + + diff --git a/plugins/snippets/data/haskell.xml b/plugins/snippets/data/haskell.xml new file mode 100755 index 00000000..54a8e7d6 --- /dev/null +++ b/plugins/snippets/data/haskell.xml @@ -0,0 +1,14 @@ + + + + + module + mod + + + ${1:t}]]> + \t -> t + \ + + diff --git a/plugins/snippets/data/html.xml b/plugins/snippets/data/html.xml new file mode 100755 index 00000000..d294f934 --- /dev/null +++ b/plugins/snippets/data/html.xml @@ -0,0 +1,246 @@ + + + + +]]> + HTML — 4.01 Strict + doctype + + + +]]> + XHTML — 1.0 Frameset + doctype + + + +]]> + XHTML — 1.0 Strict + doctype + + + +]]> + XHTML — 1.0 Transitional + doctype + + + +]]> + XHTML — 1.1 + doctype + + + +]]> + HTML — 4.0 Transitional + doctype + + + +$0]]> + author + Author + + + " /> +$0]]> + date + Date + + + ${2:$GEDIT_SELECTED_TEXT} +]]> + l]]> + Wrap Selection as Link + ref + + + $GEDIT_SELECTED_TEXT]]> + w]]> + Wrap Selection in Open/Close Tag + + + ${3:email me} $0]]> + Mail Anchor + mailto + + + $0]]> + Base + base + + + + $0 +]]> + Body + body + + + +$0]]> + space]]> + Br + + + $4 +]]> + button + Button + + + + ${0:$GEDIT_SELECTED_TEXT} +]]> + Div + div + + + $0 +]]> + file + File + + + + $0 + +

+]]>
+ Form + form +
+ + ${3:$GEDIT_SELECTED_TEXT} +$0]]> + Heading + h + + + + + ${1:Page Title} + $0 +]]> + Head + head + + + $0]]> + img + Image + + + ]]> + Input + input + + + $1$0]]> + li + List Element + + + ]]> + Link + link + + + ]]> + Meta + meta + + + + space]]> + Non-Breaking Space + + + $1$0]]> + noscript + Noscript + + + $2$0]]> + option + Option + + + +// +]]> + Script + script + + + ]]> + Script With External Source + scriptsrc + + + + + $4 +$0 +]]> + select + Select + + + $2$0]]> + span + Span + + + +/* */ + +]]> + Style + style + + + + ${4:Header} + ${5:Data} + $0 +]]> + Table + table + + + $0]]> + Text Area + textarea + + + ${1:Page Title}]]> + Title + title + + + $1 +$0]]> + tr + Table Row + + + +
  • $1
  • + $2 + +$0]]>
    + ul + Unordered List +
    +
    diff --git a/plugins/snippets/data/idl.xml b/plugins/snippets/data/idl.xml new file mode 100755 index 00000000..2b6ef30d --- /dev/null +++ b/plugins/snippets/data/idl.xml @@ -0,0 +1,49 @@ + + + + + mod + Module + + + + if + Interface + + + + str + Struct + + + + exc + Exception + + + ]]> + seq + Sequence + + + ${0:newtype};]]> + tseq + Typedef Sequence + + diff --git a/plugins/snippets/data/java.xml b/plugins/snippets/data/java.xml new file mode 100755 index 00000000..043a5dd3 --- /dev/null +++ b/plugins/snippets/data/java.xml @@ -0,0 +1,91 @@ + + + + + const def + cd + + + + if .. else + ife + + + + if + if + + + + logger + log + + + + try .. catch .. finally + tcf + + + + while statement + while + + + + main + main + + + + System.out.println + sout + + + + t]]> + Wrap Selection in Try/Catch + + + + tc + try .. catch + + diff --git a/plugins/snippets/data/javascript.xml b/plugins/snippets/data/javascript.xml new file mode 100755 index 00000000..c1d498a6 --- /dev/null +++ b/plugins/snippets/data/javascript.xml @@ -0,0 +1,11 @@ + + + + + function + fun + + diff --git a/plugins/snippets/data/lang/Makefile.am b/plugins/snippets/data/lang/Makefile.am new file mode 100755 index 00000000..8a497c12 --- /dev/null +++ b/plugins/snippets/data/lang/Makefile.am @@ -0,0 +1,9 @@ +# Python snippets plugin +lang_DATA = \ + snippets.lang + +langdir = $(GEDIT_PLUGINS_DATA_DIR)/snippets/lang + +EXTRA_DIST = $(lang_DATA) + +-include $(top_srcdir)/git.mk diff --git a/plugins/snippets/data/lang/snippets.lang b/plugins/snippets/data/lang/snippets.lang new file mode 100755 index 00000000..81e57394 --- /dev/null +++ b/plugins/snippets/data/lang/snippets.lang @@ -0,0 +1,162 @@ + + +