From ba429d344132c088177e853cce8ff7181570b221 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 19:42:51 +0200 Subject: Adding upstream version 44.2. Signed-off-by: Daniel Baumann --- plugins/docinfo/docinfo.plugin.desktop.in | 8 + plugins/docinfo/gedit-docinfo-plugin.c | 638 ++++ plugins/docinfo/gedit-docinfo-plugin.h | 63 + plugins/docinfo/meson.build | 34 + .../docinfo/resources/gedit-docinfo.gresource.xml | 6 + plugins/docinfo/resources/meson.build | 8 + .../docinfo/resources/ui/gedit-docinfo-plugin.ui | 337 ++ plugins/externaltools/data/build.desktop.in | 9 + plugins/externaltools/data/build.tool.in | 15 + plugins/externaltools/data/meson.build | 46 + .../data/open-terminal-here-osx.desktop.in | 9 + .../data/open-terminal-here-osx.tool.in | 16 + .../data/open-terminal-here.desktop.in | 9 + .../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/data/send-to-fpaste.desktop.in | 11 + plugins/externaltools/data/send-to-fpaste.tool.in | 26 + .../externaltools/externaltools.plugin.desktop.in | 9 + plugins/externaltools/meson.build | 35 + ...g.gnome.gedit.plugins.externaltools.gschema.xml | 20 + plugins/externaltools/scripts/gedit-tool-merge.pl | 78 + plugins/externaltools/scripts/meson.build | 13 + plugins/externaltools/tests/meson.build | 21 + plugins/externaltools/tests/testlinkparsing.py | 203 ++ plugins/externaltools/tools/__init__.py | 26 + plugins/externaltools/tools/appactivatable.py | 178 + plugins/externaltools/tools/capture.py | 268 ++ plugins/externaltools/tools/filelookup.py | 165 + plugins/externaltools/tools/functions.py | 365 ++ plugins/externaltools/tools/library.py | 520 +++ plugins/externaltools/tools/linkparsing.py | 252 ++ plugins/externaltools/tools/manager.py | 878 +++++ plugins/externaltools/tools/meson.build | 36 + plugins/externaltools/tools/outputpanel.py | 247 ++ plugins/externaltools/tools/outputpanel.ui | 49 + plugins/externaltools/tools/tools.ui | 548 +++ plugins/externaltools/tools/windowactivatable.py | 141 + plugins/filebrowser/filebrowser.plugin.desktop.in | 12 + plugins/filebrowser/gedit-file-bookmarks-store.c | 920 +++++ plugins/filebrowser/gedit-file-bookmarks-store.h | 91 + .../gedit-file-browser-enum-register.c.template | 20 + ...gedit-file-browser-enum-types-stage1.c.template | 45 + .../gedit-file-browser-enum-types.h.template | 28 + plugins/filebrowser/gedit-file-browser-error.h | 41 + plugins/filebrowser/gedit-file-browser-messages.c | 1074 ++++++ plugins/filebrowser/gedit-file-browser-messages.h | 33 + plugins/filebrowser/gedit-file-browser-plugin.c | 943 +++++ plugins/filebrowser/gedit-file-browser-plugin.h | 70 + plugins/filebrowser/gedit-file-browser-store.c | 3672 ++++++++++++++++++++ plugins/filebrowser/gedit-file-browser-store.h | 188 + plugins/filebrowser/gedit-file-browser-utils.c | 223 ++ plugins/filebrowser/gedit-file-browser-utils.h | 46 + plugins/filebrowser/gedit-file-browser-view.c | 1364 ++++++++ plugins/filebrowser/gedit-file-browser-view.h | 84 + plugins/filebrowser/gedit-file-browser-widget.c | 3152 +++++++++++++++++ plugins/filebrowser/gedit-file-browser-widget.h | 126 + plugins/filebrowser/meson.build | 127 + plugins/filebrowser/messages.xml | 47 + .../gedit-file-browser-message-activation.c | 105 + .../gedit-file-browser-message-activation.h | 69 + .../gedit-file-browser-message-add-filter.c | 162 + .../gedit-file-browser-message-add-filter.h | 69 + ...edit-file-browser-message-extend-context-menu.c | 128 + ...edit-file-browser-message-extend-context-menu.h | 70 + .../messages/gedit-file-browser-message-get-root.c | 127 + .../messages/gedit-file-browser-message-get-root.h | 69 + .../messages/gedit-file-browser-message-get-view.c | 127 + .../messages/gedit-file-browser-message-get-view.h | 69 + .../gedit-file-browser-message-id-location.c | 190 + .../gedit-file-browser-message-id-location.h | 70 + .../messages/gedit-file-browser-message-id.c | 107 + .../messages/gedit-file-browser-message-id.h | 69 + .../gedit-file-browser-message-set-emblem.c | 142 + .../gedit-file-browser-message-set-emblem.h | 69 + .../gedit-file-browser-message-set-markup.c | 143 + .../gedit-file-browser-message-set-markup.h | 70 + .../messages/gedit-file-browser-message-set-root.c | 149 + .../messages/gedit-file-browser-message-set-root.h | 69 + plugins/filebrowser/messages/meson.build | 25 + plugins/filebrowser/messages/messages.h | 16 + ...org.gnome.gedit.plugins.filebrowser.gschema.xml | 55 + .../resources/gedit-file-browser.gresource.xml | 7 + plugins/filebrowser/resources/meson.build | 8 + .../resources/ui/gedit-file-browser-menus.ui | 84 + .../resources/ui/gedit-file-browser-widget.ui | 275 ++ plugins/generate-list.sh | 44 + plugins/list-of-gedit-plugins.md | 42 + plugins/meson.build | 31 + plugins/modelines/gedit-modeline-plugin.c | 225 ++ plugins/modelines/gedit-modeline-plugin.h | 60 + plugins/modelines/language-mappings | 14 + plugins/modelines/meson.build | 42 + plugins/modelines/modeline-parser.c | 899 +++++ plugins/modelines/modeline-parser.h | 36 + plugins/modelines/modelines.plugin.desktop.in | 8 + plugins/pythonconsole/meson.build | 31 + ...g.gnome.gedit.plugins.pythonconsole.gschema.xml | 30 + .../pythonconsole/pythonconsole.plugin.desktop.in | 12 + plugins/pythonconsole/pythonconsole/__init__.py | 75 + plugins/pythonconsole/pythonconsole/config.py | 74 + plugins/pythonconsole/pythonconsole/config.ui | 70 + plugins/pythonconsole/pythonconsole/console.py | 414 +++ plugins/pythonconsole/pythonconsole/meson.build | 24 + .../quickhighlight/gedit-quick-highlight-plugin.c | 483 +++ .../quickhighlight/gedit-quick-highlight-plugin.h | 63 + plugins/quickhighlight/meson.build | 32 + .../quickhighlight.plugin.desktop.in | 8 + plugins/quickopen/meson.build | 19 + plugins/quickopen/quickopen.plugin.desktop.in | 12 + plugins/quickopen/quickopen/__init__.py | 193 + plugins/quickopen/quickopen/popup.py | 618 ++++ plugins/quickopen/quickopen/virtualdirs.py | 87 + plugins/snippets/data/c.xml | 281 ++ plugins/snippets/data/chdr.xml | 258 ++ plugins/snippets/data/cpp.xml | 180 + plugins/snippets/data/css.xml | 557 +++ plugins/snippets/data/docbook.xml | 2645 ++++++++++++++ plugins/snippets/data/fortran.xml | 164 + plugins/snippets/data/global.xml | 2 + plugins/snippets/data/haskell.xml | 14 + plugins/snippets/data/html.xml | 252 ++ plugins/snippets/data/idl.xml | 49 + plugins/snippets/data/java.xml | 91 + plugins/snippets/data/javascript.xml | 10 + plugins/snippets/data/lang/snippets.lang | 160 + plugins/snippets/data/latex.xml | 38 + plugins/snippets/data/mallard.xml | 316 ++ plugins/snippets/data/markdown.xml | 98 + plugins/snippets/data/perl.xml | 126 + plugins/snippets/data/php.xml | 192 + plugins/snippets/data/python.xml | 112 + plugins/snippets/data/rpmspec.xml | 22 + 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/meson.build | 23 + plugins/snippets/snippets.plugin.desktop.in | 9 + plugins/snippets/snippets/__init__.py | 26 + plugins/snippets/snippets/appactivatable.py | 133 + plugins/snippets/snippets/completion.py | 187 + plugins/snippets/snippets/document.py | 1097 ++++++ plugins/snippets/snippets/exporter.py | 122 + plugins/snippets/snippets/helper.py | 204 ++ plugins/snippets/snippets/importer.py | 134 + plugins/snippets/snippets/languagemanager.py | 40 + plugins/snippets/snippets/library.py | 989 ++++++ plugins/snippets/snippets/manager.py | 1143 ++++++ plugins/snippets/snippets/meson.build | 39 + plugins/snippets/snippets/parser.py | 256 ++ plugins/snippets/snippets/placeholder.py | 714 ++++ plugins/snippets/snippets/shareddata.py | 83 + plugins/snippets/snippets/signals.py | 90 + plugins/snippets/snippets/singleton.py | 27 + plugins/snippets/snippets/snippet.py | 360 ++ plugins/snippets/snippets/snippets.ui | 417 +++ plugins/snippets/snippets/substitutionparser.py | 203 ++ plugins/snippets/snippets/windowactivatable.py | 186 + plugins/sort/gedit-sort-plugin.c | 418 +++ plugins/sort/gedit-sort-plugin.h | 59 + plugins/sort/meson.build | 34 + plugins/sort/resources/gedit-sort.gresource.xml | 6 + plugins/sort/resources/meson.build | 8 + plugins/sort/resources/ui/gedit-sort-plugin.ui | 160 + plugins/sort/sort.plugin.desktop.in | 11 + plugins/spell/gedit-spell-app-activatable.c | 184 + plugins/spell/gedit-spell-app-activatable.h | 38 + plugins/spell/gedit-spell-plugin.c | 810 +++++ plugins/spell/gedit-spell-plugin.h | 62 + plugins/spell/meson.build | 54 + .../org.gnome.gedit.plugins.spell.gschema.xml | 9 + plugins/spell/resources/gedit-spell.gresource.xml | 6 + plugins/spell/resources/meson.build | 8 + .../spell/resources/ui/gedit-spell-setup-dialog.ui | 97 + plugins/spell/spell.plugin.desktop.in | 11 + plugins/time/gedit-time-plugin.c | 1078 ++++++ plugins/time/gedit-time-plugin.h | 61 + plugins/time/meson.build | 68 + .../time/org.gnome.gedit.plugins.time.gschema.xml | 19 + plugins/time/resources/gedit-time.gresource.xml | 7 + plugins/time/resources/meson.build | 8 + plugins/time/resources/ui/gedit-time-dialog.ui | 198 ++ .../time/resources/ui/gedit-time-setup-dialog.ui | 214 ++ plugins/time/time.plugin.desktop.in | 8 + 189 files changed, 40029 insertions(+) create mode 100644 plugins/docinfo/docinfo.plugin.desktop.in create mode 100644 plugins/docinfo/gedit-docinfo-plugin.c create mode 100644 plugins/docinfo/gedit-docinfo-plugin.h create mode 100644 plugins/docinfo/meson.build create mode 100644 plugins/docinfo/resources/gedit-docinfo.gresource.xml create mode 100644 plugins/docinfo/resources/meson.build create mode 100644 plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui create mode 100644 plugins/externaltools/data/build.desktop.in create mode 100755 plugins/externaltools/data/build.tool.in create mode 100644 plugins/externaltools/data/meson.build create mode 100644 plugins/externaltools/data/open-terminal-here-osx.desktop.in create mode 100755 plugins/externaltools/data/open-terminal-here-osx.tool.in create mode 100644 plugins/externaltools/data/open-terminal-here.desktop.in create mode 100755 plugins/externaltools/data/open-terminal-here.tool.in create mode 100644 plugins/externaltools/data/remove-trailing-spaces.desktop.in create mode 100755 plugins/externaltools/data/remove-trailing-spaces.tool.in create mode 100644 plugins/externaltools/data/run-command.desktop.in create mode 100755 plugins/externaltools/data/run-command.tool.in create mode 100644 plugins/externaltools/data/send-to-fpaste.desktop.in create mode 100755 plugins/externaltools/data/send-to-fpaste.tool.in create mode 100644 plugins/externaltools/externaltools.plugin.desktop.in create mode 100644 plugins/externaltools/meson.build create mode 100644 plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml create mode 100755 plugins/externaltools/scripts/gedit-tool-merge.pl create mode 100644 plugins/externaltools/scripts/meson.build create mode 100644 plugins/externaltools/tests/meson.build create mode 100644 plugins/externaltools/tests/testlinkparsing.py create mode 100644 plugins/externaltools/tools/__init__.py create mode 100644 plugins/externaltools/tools/appactivatable.py create mode 100644 plugins/externaltools/tools/capture.py create mode 100644 plugins/externaltools/tools/filelookup.py create mode 100644 plugins/externaltools/tools/functions.py create mode 100644 plugins/externaltools/tools/library.py create mode 100644 plugins/externaltools/tools/linkparsing.py create mode 100644 plugins/externaltools/tools/manager.py create mode 100644 plugins/externaltools/tools/meson.build create mode 100644 plugins/externaltools/tools/outputpanel.py create mode 100644 plugins/externaltools/tools/outputpanel.ui create mode 100644 plugins/externaltools/tools/tools.ui create mode 100644 plugins/externaltools/tools/windowactivatable.py create mode 100644 plugins/filebrowser/filebrowser.plugin.desktop.in create mode 100644 plugins/filebrowser/gedit-file-bookmarks-store.c create mode 100644 plugins/filebrowser/gedit-file-bookmarks-store.h create mode 100644 plugins/filebrowser/gedit-file-browser-enum-register.c.template create mode 100644 plugins/filebrowser/gedit-file-browser-enum-types-stage1.c.template create mode 100644 plugins/filebrowser/gedit-file-browser-enum-types.h.template create mode 100644 plugins/filebrowser/gedit-file-browser-error.h create mode 100644 plugins/filebrowser/gedit-file-browser-messages.c create mode 100644 plugins/filebrowser/gedit-file-browser-messages.h create mode 100644 plugins/filebrowser/gedit-file-browser-plugin.c create mode 100644 plugins/filebrowser/gedit-file-browser-plugin.h create mode 100644 plugins/filebrowser/gedit-file-browser-store.c create mode 100644 plugins/filebrowser/gedit-file-browser-store.h create mode 100644 plugins/filebrowser/gedit-file-browser-utils.c create mode 100644 plugins/filebrowser/gedit-file-browser-utils.h create mode 100644 plugins/filebrowser/gedit-file-browser-view.c create mode 100644 plugins/filebrowser/gedit-file-browser-view.h create mode 100644 plugins/filebrowser/gedit-file-browser-widget.c create mode 100644 plugins/filebrowser/gedit-file-browser-widget.h create mode 100644 plugins/filebrowser/meson.build create mode 100644 plugins/filebrowser/messages.xml create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-activation.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-activation.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-get-root.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-get-root.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-get-view.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-get-view.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-id-location.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-id-location.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-id.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-id.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-set-root.c create mode 100644 plugins/filebrowser/messages/gedit-file-browser-message-set-root.h create mode 100644 plugins/filebrowser/messages/meson.build create mode 100644 plugins/filebrowser/messages/messages.h create mode 100644 plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml create mode 100644 plugins/filebrowser/resources/gedit-file-browser.gresource.xml create mode 100644 plugins/filebrowser/resources/meson.build create mode 100644 plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui create mode 100644 plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui create mode 100755 plugins/generate-list.sh create mode 100644 plugins/list-of-gedit-plugins.md create mode 100644 plugins/meson.build create mode 100644 plugins/modelines/gedit-modeline-plugin.c create mode 100644 plugins/modelines/gedit-modeline-plugin.h create mode 100644 plugins/modelines/language-mappings create mode 100644 plugins/modelines/meson.build create mode 100644 plugins/modelines/modeline-parser.c create mode 100644 plugins/modelines/modeline-parser.h create mode 100644 plugins/modelines/modelines.plugin.desktop.in create mode 100644 plugins/pythonconsole/meson.build create mode 100644 plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml create mode 100644 plugins/pythonconsole/pythonconsole.plugin.desktop.in create mode 100644 plugins/pythonconsole/pythonconsole/__init__.py create mode 100644 plugins/pythonconsole/pythonconsole/config.py create mode 100644 plugins/pythonconsole/pythonconsole/config.ui create mode 100644 plugins/pythonconsole/pythonconsole/console.py create mode 100644 plugins/pythonconsole/pythonconsole/meson.build create mode 100644 plugins/quickhighlight/gedit-quick-highlight-plugin.c create mode 100644 plugins/quickhighlight/gedit-quick-highlight-plugin.h create mode 100644 plugins/quickhighlight/meson.build create mode 100644 plugins/quickhighlight/quickhighlight.plugin.desktop.in create mode 100644 plugins/quickopen/meson.build create mode 100644 plugins/quickopen/quickopen.plugin.desktop.in create mode 100644 plugins/quickopen/quickopen/__init__.py create mode 100644 plugins/quickopen/quickopen/popup.py create mode 100644 plugins/quickopen/quickopen/virtualdirs.py create mode 100644 plugins/snippets/data/c.xml create mode 100644 plugins/snippets/data/chdr.xml create mode 100644 plugins/snippets/data/cpp.xml create mode 100644 plugins/snippets/data/css.xml create mode 100644 plugins/snippets/data/docbook.xml create mode 100644 plugins/snippets/data/fortran.xml create mode 100644 plugins/snippets/data/global.xml create mode 100644 plugins/snippets/data/haskell.xml create mode 100644 plugins/snippets/data/html.xml create mode 100644 plugins/snippets/data/idl.xml create mode 100644 plugins/snippets/data/java.xml create mode 100644 plugins/snippets/data/javascript.xml create mode 100644 plugins/snippets/data/lang/snippets.lang create mode 100644 plugins/snippets/data/latex.xml create mode 100644 plugins/snippets/data/mallard.xml create mode 100644 plugins/snippets/data/markdown.xml create mode 100644 plugins/snippets/data/perl.xml create mode 100644 plugins/snippets/data/php.xml create mode 100644 plugins/snippets/data/python.xml create mode 100644 plugins/snippets/data/rpmspec.xml create mode 100644 plugins/snippets/data/ruby.xml create mode 100644 plugins/snippets/data/sh.xml create mode 100644 plugins/snippets/data/snippets.xml create mode 100644 plugins/snippets/data/tcl.xml create mode 100644 plugins/snippets/data/xml.xml create mode 100644 plugins/snippets/data/xslt.xml create mode 100644 plugins/snippets/meson.build create mode 100644 plugins/snippets/snippets.plugin.desktop.in create mode 100644 plugins/snippets/snippets/__init__.py create mode 100644 plugins/snippets/snippets/appactivatable.py create mode 100644 plugins/snippets/snippets/completion.py create mode 100644 plugins/snippets/snippets/document.py create mode 100644 plugins/snippets/snippets/exporter.py create mode 100644 plugins/snippets/snippets/helper.py create mode 100644 plugins/snippets/snippets/importer.py create mode 100644 plugins/snippets/snippets/languagemanager.py create mode 100644 plugins/snippets/snippets/library.py create mode 100644 plugins/snippets/snippets/manager.py create mode 100644 plugins/snippets/snippets/meson.build create mode 100644 plugins/snippets/snippets/parser.py create mode 100644 plugins/snippets/snippets/placeholder.py create mode 100644 plugins/snippets/snippets/shareddata.py create mode 100644 plugins/snippets/snippets/signals.py create mode 100644 plugins/snippets/snippets/singleton.py create mode 100644 plugins/snippets/snippets/snippet.py create mode 100644 plugins/snippets/snippets/snippets.ui create mode 100644 plugins/snippets/snippets/substitutionparser.py create mode 100644 plugins/snippets/snippets/windowactivatable.py create mode 100644 plugins/sort/gedit-sort-plugin.c create mode 100644 plugins/sort/gedit-sort-plugin.h create mode 100644 plugins/sort/meson.build create mode 100644 plugins/sort/resources/gedit-sort.gresource.xml create mode 100644 plugins/sort/resources/meson.build create mode 100644 plugins/sort/resources/ui/gedit-sort-plugin.ui create mode 100644 plugins/sort/sort.plugin.desktop.in create mode 100644 plugins/spell/gedit-spell-app-activatable.c create mode 100644 plugins/spell/gedit-spell-app-activatable.h create mode 100644 plugins/spell/gedit-spell-plugin.c create mode 100644 plugins/spell/gedit-spell-plugin.h create mode 100644 plugins/spell/meson.build create mode 100644 plugins/spell/org.gnome.gedit.plugins.spell.gschema.xml create mode 100644 plugins/spell/resources/gedit-spell.gresource.xml create mode 100644 plugins/spell/resources/meson.build create mode 100644 plugins/spell/resources/ui/gedit-spell-setup-dialog.ui create mode 100644 plugins/spell/spell.plugin.desktop.in create mode 100644 plugins/time/gedit-time-plugin.c create mode 100644 plugins/time/gedit-time-plugin.h create mode 100644 plugins/time/meson.build create mode 100644 plugins/time/org.gnome.gedit.plugins.time.gschema.xml create mode 100644 plugins/time/resources/gedit-time.gresource.xml create mode 100644 plugins/time/resources/meson.build create mode 100644 plugins/time/resources/ui/gedit-time-dialog.ui create mode 100644 plugins/time/resources/ui/gedit-time-setup-dialog.ui create mode 100644 plugins/time/time.plugin.desktop.in (limited to 'plugins') diff --git a/plugins/docinfo/docinfo.plugin.desktop.in b/plugins/docinfo/docinfo.plugin.desktop.in new file mode 100644 index 0000000..b493bb4 --- /dev/null +++ b/plugins/docinfo/docinfo.plugin.desktop.in @@ -0,0 +1,8 @@ +[Plugin] +Module=docinfo +IAge=3 +Name=Document Statistics +Description=Report the number of words, lines and characters in a document. +Authors=Paolo Maggi ;Jorge Alberto Torres +Copyright=Copyright © 2002-2005 Paolo Maggi +Website=http://www.gedit.org diff --git a/plugins/docinfo/gedit-docinfo-plugin.c b/plugins/docinfo/gedit-docinfo-plugin.c new file mode 100644 index 0000000..37c8041 --- /dev/null +++ b/plugins/docinfo/gedit-docinfo-plugin.c @@ -0,0 +1,638 @@ +/* + * 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, see . + * + */ + +#include "config.h" + +#include "gedit-docinfo-plugin.h" + +#include /* For strlen (...) */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +struct _GeditDocinfoPluginPrivate +{ + GeditWindow *window; + + GSimpleAction *action; + + GtkWidget *dialog; + GtkWidget *header_bar; + GtkWidget *lines_label; + GtkWidget *words_label; + GtkWidget *chars_label; + GtkWidget *chars_ns_label; + GtkWidget *bytes_label; + GtkWidget *document_label; + GtkWidget *document_lines_label; + GtkWidget *document_words_label; + GtkWidget *document_chars_label; + GtkWidget *document_chars_ns_label; + GtkWidget *document_bytes_label; + GtkWidget *selection_label; + GtkWidget *selected_lines_label; + GtkWidget *selected_words_label; + GtkWidget *selected_chars_label; + GtkWidget *selected_chars_ns_label; + GtkWidget *selected_bytes_label; + + GeditApp *app; + GeditMenuExtension *menu_ext; +}; + +enum +{ + PROP_0, + PROP_WINDOW, + PROP_APP +}; + +static void gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface); +static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditDocinfoPlugin, + gedit_docinfo_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_APP_ACTIVATABLE, + gedit_app_activatable_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE, + gedit_window_activatable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditDocinfoPlugin)) + +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 +update_document_info (GeditDocinfoPlugin *plugin, + GeditDocument *doc) +{ + GeditDocinfoPluginPrivate *priv; + GtkTextIter start, end; + gint words = 0; + gint chars = 0; + gint white_chars = 0; + gint lines = 0; + gint bytes = 0; + gchar *doc_name; + gchar *tmp_str; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + 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 = tepl_file_get_short_name (tepl_buffer_get_file (TEPL_BUFFER (doc))); + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (priv->header_bar), doc_name); + g_free (doc_name); + + tmp_str = g_strdup_printf("%d", lines); + gtk_label_set_text (GTK_LABEL (priv->document_lines_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", words); + gtk_label_set_text (GTK_LABEL (priv->document_words_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars); + gtk_label_set_text (GTK_LABEL (priv->document_chars_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars - white_chars); + gtk_label_set_text (GTK_LABEL (priv->document_chars_ns_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", bytes); + gtk_label_set_text (GTK_LABEL (priv->document_bytes_label), tmp_str); + g_free (tmp_str); +} + +static void +update_selection_info (GeditDocinfoPlugin *plugin, + GeditDocument *doc) +{ + GeditDocinfoPluginPrivate *priv; + 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); + + priv = plugin->priv; + + 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 (priv->selection_label, TRUE); + gtk_widget_set_sensitive (priv->selected_words_label, TRUE); + gtk_widget_set_sensitive (priv->selected_bytes_label, TRUE); + gtk_widget_set_sensitive (priv->selected_lines_label, TRUE); + gtk_widget_set_sensitive (priv->selected_chars_label, TRUE); + gtk_widget_set_sensitive (priv->selected_chars_ns_label, TRUE); + } + else + { + gedit_debug_message (DEBUG_PLUGINS, "Selection empty"); + + gtk_widget_set_sensitive (priv->selection_label, FALSE); + gtk_widget_set_sensitive (priv->selected_words_label, FALSE); + gtk_widget_set_sensitive (priv->selected_bytes_label, FALSE); + gtk_widget_set_sensitive (priv->selected_lines_label, FALSE); + gtk_widget_set_sensitive (priv->selected_chars_label, FALSE); + gtk_widget_set_sensitive (priv->selected_chars_ns_label, FALSE); + } + + if (chars == 0) + lines = 0; + + tmp_str = g_strdup_printf("%d", lines); + gtk_label_set_text (GTK_LABEL (priv->selected_lines_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", words); + gtk_label_set_text (GTK_LABEL (priv->selected_words_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars); + gtk_label_set_text (GTK_LABEL (priv->selected_chars_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", chars - white_chars); + gtk_label_set_text (GTK_LABEL (priv->selected_chars_ns_label), tmp_str); + g_free (tmp_str); + + tmp_str = g_strdup_printf("%d", bytes); + gtk_label_set_text (GTK_LABEL (priv->selected_bytes_label), tmp_str); + g_free (tmp_str); +} + +static void +docinfo_dialog_response_cb (GtkDialog *widget, + gint res_id, + GeditDocinfoPlugin *plugin) +{ + GeditDocinfoPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + switch (res_id) + { + case GTK_RESPONSE_CLOSE: + { + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CLOSE"); + + gtk_widget_destroy (priv->dialog); + + break; + } + + case GTK_RESPONSE_OK: + { + GeditDocument *doc; + + gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK"); + + doc = gedit_window_get_active_document (priv->window); + + update_document_info (plugin, doc); + update_selection_info (plugin, doc); + + break; + } + } +} + +static void +create_docinfo_dialog (GeditDocinfoPlugin *plugin) +{ + GeditDocinfoPluginPrivate *priv; + GtkBuilder *builder; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + builder = gtk_builder_new (); + gtk_builder_add_from_resource (builder, "/org/gnome/gedit/plugins/docinfo/ui/gedit-docinfo-plugin.ui", NULL); + priv->dialog = GTK_WIDGET (gtk_builder_get_object (builder, "dialog")); + priv->header_bar = GTK_WIDGET (gtk_builder_get_object (builder, "header_bar")); + priv->words_label = GTK_WIDGET (gtk_builder_get_object (builder, "words_label")); + priv->bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "bytes_label")); + priv->lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "lines_label")); + priv->chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "chars_label")); + priv->chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "chars_ns_label")); + priv->document_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_label")); + priv->document_words_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_words_label")); + priv->document_bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_bytes_label")); + priv->document_lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_lines_label")); + priv->document_chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_chars_label")); + priv->document_chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "document_chars_ns_label")); + priv->selection_label = GTK_WIDGET (gtk_builder_get_object (builder, "selection_label")); + priv->selected_words_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_words_label")); + priv->selected_bytes_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_bytes_label")); + priv->selected_lines_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_lines_label")); + priv->selected_chars_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_chars_label")); + priv->selected_chars_ns_label = GTK_WIDGET (gtk_builder_get_object (builder, "selected_chars_ns_label")); + g_object_unref (builder); + + gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog), + GTK_RESPONSE_OK); + gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), + GTK_WINDOW (priv->window)); + + g_signal_connect (priv->dialog, + "destroy", + G_CALLBACK (gtk_widget_destroyed), + &priv->dialog); + g_signal_connect (priv->dialog, + "response", + G_CALLBACK (docinfo_dialog_response_cb), + plugin); + + /* We set this explictely with code since glade does not + * save the can_focus property when set to false :( + * Making sure the labels are not focusable is needed to + * prevent loosing the selection in the document when + * creating the dialog. + */ + gtk_widget_set_can_focus (priv->words_label, FALSE); + gtk_widget_set_can_focus (priv->bytes_label, FALSE); + gtk_widget_set_can_focus (priv->lines_label, FALSE); + gtk_widget_set_can_focus (priv->chars_label, FALSE); + gtk_widget_set_can_focus (priv->chars_ns_label, FALSE); + gtk_widget_set_can_focus (priv->document_label, FALSE); + gtk_widget_set_can_focus (priv->document_words_label, FALSE); + gtk_widget_set_can_focus (priv->document_bytes_label, FALSE); + gtk_widget_set_can_focus (priv->document_lines_label, FALSE); + gtk_widget_set_can_focus (priv->document_chars_label, FALSE); + gtk_widget_set_can_focus (priv->document_chars_ns_label, FALSE); + gtk_widget_set_can_focus (priv->selection_label, FALSE); + gtk_widget_set_can_focus (priv->selected_words_label, FALSE); + gtk_widget_set_can_focus (priv->selected_bytes_label, FALSE); + gtk_widget_set_can_focus (priv->selected_lines_label, FALSE); + gtk_widget_set_can_focus (priv->selected_chars_label, FALSE); + gtk_widget_set_can_focus (priv->selected_chars_ns_label, FALSE); +} + +static void +docinfo_cb (GAction *action, + GVariant *parameter, + GeditDocinfoPlugin *plugin) +{ + GeditDocinfoPluginPrivate *priv; + GeditDocument *doc; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + doc = gedit_window_get_active_document (priv->window); + + if (priv->dialog != NULL) + { + gtk_window_present (GTK_WINDOW (priv->dialog)); + gtk_widget_grab_focus (GTK_WIDGET (priv->dialog)); + } + else + { + create_docinfo_dialog (plugin); + gtk_widget_show (GTK_WIDGET (priv->dialog)); + } + + update_document_info (plugin, doc); + update_selection_info (plugin, doc); +} + +static void +gedit_docinfo_plugin_init (GeditDocinfoPlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditDocinfoPlugin initializing"); + + plugin->priv = gedit_docinfo_plugin_get_instance_private (plugin); +} + +static void +gedit_docinfo_plugin_dispose (GObject *object) +{ + GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditDocinfoPlugin dispose"); + + g_clear_object (&plugin->priv->action); + g_clear_object (&plugin->priv->window); + g_clear_object (&plugin->priv->menu_ext); + g_clear_object (&plugin->priv->app); + + G_OBJECT_CLASS (gedit_docinfo_plugin_parent_class)->dispose (object); +} + + +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 +gedit_docinfo_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value)); + break; + case PROP_APP: + plugin->priv->app = GEDIT_APP (g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_docinfo_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditDocinfoPlugin *plugin = GEDIT_DOCINFO_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, plugin->priv->window); + break; + case PROP_APP: + g_value_set_object (value, plugin->priv->app); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +update_ui (GeditDocinfoPlugin *plugin) +{ + GeditDocinfoPluginPrivate *priv; + GeditView *view; + + gedit_debug (DEBUG_PLUGINS); + + priv = plugin->priv; + + view = gedit_window_get_active_view (priv->window); + + g_simple_action_set_enabled (G_SIMPLE_ACTION (priv->action), view != NULL); + + if (priv->dialog != NULL) + { + gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog), + GTK_RESPONSE_OK, + (view != NULL)); + } +} + +static void +gedit_docinfo_plugin_app_activate (GeditAppActivatable *activatable) +{ + GeditDocinfoPluginPrivate *priv; + GMenuItem *item; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv; + + priv->menu_ext = gedit_app_activatable_extend_menu (activatable, "tools-section"); + item = g_menu_item_new (_("_Document Statistics"), "win.docinfo"); + gedit_menu_extension_append_menu_item (priv->menu_ext, item); + g_object_unref (item); +} + +static void +gedit_docinfo_plugin_app_deactivate (GeditAppActivatable *activatable) +{ + GeditDocinfoPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv; + + g_clear_object (&priv->menu_ext); +} + +static void +gedit_docinfo_plugin_window_activate (GeditWindowActivatable *activatable) +{ + GeditDocinfoPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv; + + priv->action = g_simple_action_new ("docinfo", NULL); + g_signal_connect (priv->action, "activate", + G_CALLBACK (docinfo_cb), activatable); + g_action_map_add_action (G_ACTION_MAP (priv->window), + G_ACTION (priv->action)); + + update_ui (GEDIT_DOCINFO_PLUGIN (activatable)); +} + +static void +gedit_docinfo_plugin_window_deactivate (GeditWindowActivatable *activatable) +{ + GeditDocinfoPluginPrivate *priv; + + gedit_debug (DEBUG_PLUGINS); + + priv = GEDIT_DOCINFO_PLUGIN (activatable)->priv; + + g_action_map_remove_action (G_ACTION_MAP (priv->window), "docinfo"); +} + +static void +gedit_docinfo_plugin_window_update_state (GeditWindowActivatable *activatable) +{ + gedit_debug (DEBUG_PLUGINS); + + update_ui (GEDIT_DOCINFO_PLUGIN (activatable)); +} + +static void +gedit_docinfo_plugin_class_init (GeditDocinfoPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_docinfo_plugin_dispose; + object_class->finalize = gedit_docinfo_plugin_finalize; + object_class->set_property = gedit_docinfo_plugin_set_property; + object_class->get_property = gedit_docinfo_plugin_get_property; + + g_object_class_override_property (object_class, PROP_WINDOW, "window"); + g_object_class_override_property (object_class, PROP_APP, "app"); +} + +static void +gedit_app_activatable_iface_init (GeditAppActivatableInterface *iface) +{ + iface->activate = gedit_docinfo_plugin_app_activate; + iface->deactivate = gedit_docinfo_plugin_app_deactivate; +} + +static void +gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface) +{ + iface->activate = gedit_docinfo_plugin_window_activate; + iface->deactivate = gedit_docinfo_plugin_window_deactivate; + iface->update_state = gedit_docinfo_plugin_window_update_state; +} + +static void +gedit_docinfo_plugin_class_finalize (GeditDocinfoPluginClass *klass) +{ +} + + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_docinfo_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_APP_ACTIVATABLE, + GEDIT_TYPE_DOCINFO_PLUGIN); + peas_object_module_register_extension_type (module, + GEDIT_TYPE_WINDOW_ACTIVATABLE, + GEDIT_TYPE_DOCINFO_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/docinfo/gedit-docinfo-plugin.h b/plugins/docinfo/gedit-docinfo-plugin.h new file mode 100644 index 0000000..e657c9f --- /dev/null +++ b/plugins/docinfo/gedit-docinfo-plugin.h @@ -0,0 +1,63 @@ +/* + * 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, see . + * + */ + +#ifndef GEDIT_DOCINFO_PLUGIN_H +#define GEDIT_DOCINFO_PLUGIN_H + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#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)) + +typedef struct _GeditDocinfoPlugin GeditDocinfoPlugin; +typedef struct _GeditDocinfoPluginPrivate GeditDocinfoPluginPrivate; +typedef struct _GeditDocinfoPluginClass GeditDocinfoPluginClass; + +struct _GeditDocinfoPlugin +{ + PeasExtensionBase parent; + + /*< private >*/ + GeditDocinfoPluginPrivate *priv; +}; + +struct _GeditDocinfoPluginClass +{ + PeasExtensionBaseClass parent_class; +}; + +GType gedit_docinfo_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_DOCINFO_PLUGIN_H */ + +/* ex:set ts=8 noet: */ diff --git a/plugins/docinfo/meson.build b/plugins/docinfo/meson.build new file mode 100644 index 0000000..200ace5 --- /dev/null +++ b/plugins/docinfo/meson.build @@ -0,0 +1,34 @@ +libdocinfo_sources = files( + 'gedit-docinfo-plugin.c', +) + +libdocinfo_deps = [ + libgedit_dep, +] + +subdir('resources') + +libdocinfo_sha = shared_module( + 'docinfo', + sources: libdocinfo_sources, + include_directories: root_include_dir, + dependencies: libdocinfo_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +custom_target( + 'docinfo.plugin', + input: 'docinfo.plugin.desktop.in', + output: 'docinfo.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/docinfo/resources/gedit-docinfo.gresource.xml b/plugins/docinfo/resources/gedit-docinfo.gresource.xml new file mode 100644 index 0000000..9d61659 --- /dev/null +++ b/plugins/docinfo/resources/gedit-docinfo.gresource.xml @@ -0,0 +1,6 @@ + + + + ui/gedit-docinfo-plugin.ui + + diff --git a/plugins/docinfo/resources/meson.build b/plugins/docinfo/resources/meson.build new file mode 100644 index 0000000..b1f4d4d --- /dev/null +++ b/plugins/docinfo/resources/meson.build @@ -0,0 +1,8 @@ +libdocinfo_res = gnome.compile_resources( + 'gedit-docinfo-resources', + 'gedit-docinfo.gresource.xml', +) + +libdocinfo_sources += [ + libdocinfo_res.get(0), +] diff --git a/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui b/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui new file mode 100644 index 0000000..84cf918 --- /dev/null +++ b/plugins/docinfo/resources/ui/gedit-docinfo-plugin.ui @@ -0,0 +1,337 @@ + + + + + False + 5 + Document Statistics + False + True + dialog + + + Document Statistics + True + False + True + + + True + center + True + True + + + + True + 1 + view-refresh-symbolic + + + + + start + + + + + + + True + False + vertical + 8 + + + True + False + 5 + vertical + 6 + + + True + False + 6 + 18 + + + True + False + 0 + Document + True + True + center + True + + + 1 + 0 + 1 + 1 + + + + + True + False + 0 + Selection + True + + + 2 + 0 + 1 + 1 + + + + + True + False + 0 + Lines + True + + + 0 + 1 + 1 + 1 + + + + + True + False + 0 + Words + True + + + 0 + 2 + 1 + 1 + + + + + True + False + 0 + Characters (with spaces) + True + + + 0 + 3 + 1 + 1 + + + + + True + False + 0 + Characters (no spaces) + True + + + 0 + 4 + 1 + 1 + + + + + True + False + 0 + Bytes + True + + + 0 + 5 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 1 + 1 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 1 + 2 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 1 + 3 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 1 + 4 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 1 + 5 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 2 + 1 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 2 + 2 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 2 + 3 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 2 + 4 + 1 + 1 + + + + + True + False + 1 + 0 + True + + + 2 + 5 + 1 + 1 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + + update_button + + + diff --git a/plugins/externaltools/data/build.desktop.in b/plugins/externaltools/data/build.desktop.in new file mode 100644 index 0000000..14dfebd --- /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=all +Languages= diff --git a/plugins/externaltools/data/build.tool.in b/plugins/externaltools/data/build.tool.in new file mode 100755 index 0000000..0b81d5b --- /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/meson.build b/plugins/externaltools/data/meson.build new file mode 100644 index 0000000..02d5d6c --- /dev/null +++ b/plugins/externaltools/data/meson.build @@ -0,0 +1,46 @@ +externaltools_tools = [ + 'build', + 'remove-trailing-spaces', + 'send-to-fpaste', +] + +if host_machine.system() == 'darwin' + externaltools_tools += [ + 'open-terminal-here-osx', + ] +elif host_machine.system() != 'windows' + externaltools_tools += [ + 'open-terminal-here', + 'run-command', + ] +endif + +foreach tool_name: externaltools_tools + dektop_file = custom_target( + '@0@.desktop'.format(tool_name), + input: '@0@.desktop.in'.format(tool_name), + output: '@0@.desktop'.format(tool_name), + command: msgfmt_externaltools_cmd, + install: false, + ) + + custom_target( + '@0@.tool'.format(tool_name), + input: '@0@.tool.in'.format(tool_name), + output: '@0@'.format(tool_name), + depends: dektop_file, + command: [ + merge_tool_prg, + '@INPUT@', + dektop_file.full_path(), + ], + capture: true, + install: true, + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'externaltools', + 'tools', + ) + ) +endforeach 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 100644 index 0000000..846ff9a --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here-osx.desktop.in @@ -0,0 +1,9 @@ +[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= +Shortcut=t 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 0000000..c336006 --- /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 100644 index 0000000..846ff9a --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here.desktop.in @@ -0,0 +1,9 @@ +[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= +Shortcut=t 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 0000000..9365648 --- /dev/null +++ b/plugins/externaltools/data/open-terminal-here.tool.in @@ -0,0 +1,4 @@ +#!/bin/sh + +#TODO: use "gconftool-2 -g /desktop/gnome/applications/terminal/exec" +gnome-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 100644 index 0000000..9a34de7 --- /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 0000000..83e4c19 --- /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 100644 index 0000000..ca7b7da --- /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 0000000..77ad4e6 --- /dev/null +++ b/plugins/externaltools/data/run-command.tool.in @@ -0,0 +1,4 @@ +#!/bin/sh + +#TODO: use "gconftool-2 -g /desktop/gnome/applications/terminal/exec" +eval $(zenity --entry --title="Run Command - gedit" --text="Command to run:") diff --git a/plugins/externaltools/data/send-to-fpaste.desktop.in b/plugins/externaltools/data/send-to-fpaste.desktop.in new file mode 100644 index 0000000..40282c8 --- /dev/null +++ b/plugins/externaltools/data/send-to-fpaste.desktop.in @@ -0,0 +1,11 @@ +[Gedit Tool] +Name=Send to fpaste +Comment=Paste selected text or current document to fpaste +Input=selection-document +Output=output-panel +Shortcut=p +Applicability=always +Save-files=nothing +Languages= + + diff --git a/plugins/externaltools/data/send-to-fpaste.tool.in b/plugins/externaltools/data/send-to-fpaste.tool.in new file mode 100755 index 0000000..d392173 --- /dev/null +++ b/plugins/externaltools/data/send-to-fpaste.tool.in @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +import os, urllib, json, sys, urllib.request +from gi.repository import Gtk, Gdk + +text = sys.stdin.read() + +lang = os.getenv('GEDIT_CURRENT_DOCUMENT_LANGUAGE') +if lang is None: + lang = "text" + +url_params = urllib.parse.urlencode({'paste_data': text, 'paste_lang': lang, 'mode':'json', 'api_submit':'true'}) +openfpaste = urllib.request.urlopen("http://fpaste.org", bytes(url_params, 'utf-8')).read().decode("utf-8") +if openfpaste is None: + print("Failed to send fpaste request.") + +final_data = json.loads(openfpaste) + +paste_url = "http://fpaste.org/" + final_data['result']['id'] + +disp = Gdk.Display.get_default() +clipboard = Gtk.Clipboard.get_for_display(disp, Gdk.SELECTION_CLIPBOARD) +clipboard.set_text(paste_url, len(paste_url)) +clipboard.store() + +print(paste_url + " has been copied to the clipboard.") diff --git a/plugins/externaltools/externaltools.plugin.desktop.in b/plugins/externaltools/externaltools.plugin.desktop.in new file mode 100644 index 0000000..f575818 --- /dev/null +++ b/plugins/externaltools/externaltools.plugin.desktop.in @@ -0,0 +1,9 @@ +[Plugin] +Loader=python3 +Module=externaltools +IAge=3 +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/meson.build b/plugins/externaltools/meson.build new file mode 100644 index 0000000..d46d0dd --- /dev/null +++ b/plugins/externaltools/meson.build @@ -0,0 +1,35 @@ +subdir('scripts') +subdir('tools') +subdir('data') + +externaltools_gschema_file = files('org.gnome.gedit.plugins.externaltools.gschema.xml') +install_data( + externaltools_gschema_file, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') +) + +if xmllint.found() + test( + 'validate-externaltools-gschema', + xmllint, + args: [ + '--noout', + '--dtdvalid', gschema_dtd, + externaltools_gschema_file, + ] + ) +endif + +custom_target( + 'externaltools.plugin', + input: 'externaltools.plugin.desktop.in', + output: 'externaltools.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) + +subdir('tests') diff --git a/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml b/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml new file mode 100644 index 0000000..d760de2 --- /dev/null +++ b/plugins/externaltools/org.gnome.gedit.plugins.externaltools.gschema.xml @@ -0,0 +1,20 @@ + + + + true + Whether to use the system font + + If true, the external tools will use the desktop-global standard + font if it’s monospace (and the most similar font it can + come up with otherwise). + + + + 'Monospace 10' + Font + + A Pango font name. Examples are “Sans 12” or “Monospace Bold 14”. + + + + diff --git a/plugins/externaltools/scripts/gedit-tool-merge.pl b/plugins/externaltools/scripts/gedit-tool-merge.pl new file mode 100755 index 0000000..b7cbd77 --- /dev/null +++ b/plugins/externaltools/scripts/gedit-tool-merge.pl @@ -0,0 +1,78 @@ +#!/usr/bin/env 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/scripts/meson.build b/plugins/externaltools/scripts/meson.build new file mode 100644 index 0000000..c5f50e9 --- /dev/null +++ b/plugins/externaltools/scripts/meson.build @@ -0,0 +1,13 @@ +msgfmt_externaltools_cmd = [ + find_program('msgfmt'), + '--desktop', + '--keyword=Name', + '--keyword=Comment', + '--template=@INPUT@', + '-d', join_paths(srcdir, 'po'), + '--output=@OUTPUT@' +] + +merge_tool_prg = find_program( + files('gedit-tool-merge.pl'), +) diff --git a/plugins/externaltools/tests/meson.build b/plugins/externaltools/tests/meson.build new file mode 100644 index 0000000..6cee9fe --- /dev/null +++ b/plugins/externaltools/tests/meson.build @@ -0,0 +1,21 @@ +externaltools_tests = { + 'LinkParser': files('testlinkparsing.py'), +} + +externaltools_srcdir = join_paths( + srcdir, + 'plugins', + 'externaltools', + 'tools', +) + +foreach test_name, test_script : externaltools_tests + test( + 'test-externaltools-@0@'.format(test_name), + python3, + args: [test_script], + env: [ + 'PYTHONPATH=@0@'.format(externaltools_srcdir), + ] + ) +endforeach diff --git a/plugins/externaltools/tests/testlinkparsing.py b/plugins/externaltools/tests/testlinkparsing.py new file mode 100644 index 0000000..3b8a78e --- /dev/null +++ b/plugins/externaltools/tests/testlinkparsing.py @@ -0,0 +1,203 @@ +# -*- 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 unittest +from linkparsing import LinkParser + + +class TestLinkParser(unittest.TestCase): + + def setUp(self): + self.p = LinkParser() + + def assert_link_count(self, links, expected_count): + self.assertEqual(len(links), expected_count, 'incorrect nr of links') + + def assert_link(self, actual, path, line_nr, col_nr=0): + self.assertEqual(actual.path, path, "incorrect path") + self.assertEqual(actual.line_nr, line_nr, "incorrect line nr") + self.assertEqual(actual.col_nr, col_nr, "incorrect col nr") + + def assert_link_text(self, text, link, link_text): + self.assertEqual(text[link.start:link.end], link_text, + "the expected link text does not match the text within the string") + + def test_parse_gcc_simple_test_with_real_output(self): + gcc_output = """ +test.c: In function 'f': +test.c:5:6: warning: passing argument 1 of 'f' makes integer from pointer without a cast +test.c:3:7: note: expected 'int' but argument is of type 'char *' +test.c: In function 'main': +test.c:11:10: warning: initialization makes pointer from integer without a cast +test.c:12:11: warning: initialization makes integer from pointer without a cast +test.c:13:12: error: too few arguments to function 'f' +test.c:14:13: error: expected ';' before 'return' +""" + links = self.p.parse(gcc_output) + self.assert_link_count(links, 6) + lnk = links[2] + self.assert_link(lnk, "test.c", 11, 10) + self.assert_link_text(gcc_output, lnk, "test.c:11:10") + + def test_parse_gcc_one_line(self): + line = "/tmp/myfile.c:1212:12: error: ..." + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "/tmp/myfile.c", 1212, 12) + self.assert_link_text(line, lnk, "/tmp/myfile.c:1212:12") + + def test_parse_gcc_empty_string(self): + links = self.p.parse("") + self.assert_link_count(links, 0) + + def test_parse_gcc_no_files_in_text(self): + links = self.p.parse("no file links in this string") + self.assert_link_count(links, 0) + + def test_parse_gcc_none_as_argument(self): + self.assertRaises(ValueError, self.p.parse, None) + + def test_parse_grep_one_line(self): + line = "libnautilus-private/nautilus-canvas-container.h:45:#define NAUTILUS_CANVAS_ICON_DATA(pointer)" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "libnautilus-private/nautilus-canvas-container.h", 45) + self.assert_link_text(line, lnk, "libnautilus-private/nautilus-canvas-container.h:45") + + def test_parse_python_simple_test_with_real_output(self): + output = """ +Traceback (most recent call last): + File "test.py", line 10, in + err() + File "test.py", line 7, in err + real_err() + File "test.py", line 4, in real_err + int('xxx') +ValueError: invalid literal for int() with base 10: 'xxx' +""" + links = self.p.parse(output) + self.assert_link_count(links, 3) + lnk = links[2] + self.assert_link(lnk, "test.py", 4) + self.assert_link_text(output, lnk, '"test.py", line 4') + + def test_parse_python_one_line(self): + line = " File \"test.py\", line 1\n def a()" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "test.py", 1) + self.assert_link_text(line, lnk, '"test.py", line 1') + + def test_parse_bash_one_line(self): + line = "test.sh: line 5: gerp: command not found" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "test.sh", 5) + self.assert_link_text(line, lnk, 'test.sh: line 5') + + def test_parse_javac_one_line(self): + line = "/tmp/Test.java:10: incompatible types" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "/tmp/Test.java", 10) + self.assert_link_text(line, lnk, '/tmp/Test.java:10') + + def test_parse_valac_simple_test_with_real_output(self): + output = """ +Test.vala:14.13-14.21: error: Assignment: Cannot convert from `string' to `int' + int a = "xxx"; + ^^^^^^^^^ +""" + links = self.p.parse(output) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "Test.vala", 14) + self.assert_link_text(output, lnk, 'Test.vala:14.13-14.21') + + def test_parse_ruby_simple_test_with_real_output(self): + output = """ +test.rb:5: undefined method `fake_method' for main:Object (NoMethodError) + from test.rb:3:in `each' + from test.rb:3 +""" + links = self.p.parse(output) + self.assert_link_count(links, 3) + lnk = links[0] + self.assert_link(lnk, "test.rb", 5) + self.assert_link_text(output, lnk, 'test.rb:5') + lnk = links[1] + self.assert_link(lnk, "test.rb", 3) + self.assert_link_text(output, lnk, 'test.rb:3') + + def test_parse_scalac_one_line(self): + line = "Test.scala:7: error: not found: value fakeMethod" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "Test.scala", 7) + self.assert_link_text(line, lnk, 'Test.scala:7') + + def test_parse_sbt_one_line(self): + line = "[error] /home/hank/foo/Test.scala:7: not found: value fakeMethod" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "/home/hank/foo/Test.scala", 7) + self.assert_link_text(line, lnk, '/home/hank/foo/Test.scala:7') + + def test_parse_go_6g_one_line(self): + line = "test.go:9: undefined: FakeMethod" + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "test.go", 9) + self.assert_link_text(line, lnk, 'test.go:9') + + def test_parse_perl_one_line(self): + line = 'syntax error at test.pl line 889, near "$fake_var' + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "test.pl", 889) + self.assert_link_text(line, lnk, 'test.pl line 889') + + def test_parse_mcs_one_line(self): + line = 'Test.cs(12,7): error CS0103: The name `fakeMethod' + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "Test.cs", 12) + self.assert_link_text(line, lnk, 'Test.cs(12,7)') + + def test_parse_pas_one_line(self): + line = 'hello.pas(11,1) Fatal: Syntax error, ":" expected but "BEGIN"' + links = self.p.parse(line) + self.assert_link_count(links, 1) + lnk = links[0] + self.assert_link(lnk, "hello.pas", 11) + self.assert_link_text(line, lnk, 'hello.pas(11,1)') + +if __name__ == '__main__': + unittest.main() + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py new file mode 100644 index 0000000..0cc0b4f --- /dev/null +++ b/plugins/externaltools/tools/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: UTF-8 -*- +# Gedit External Tools plugin +# Copyright (C) 2010 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 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 gi +gi.require_version('Gedit', '3.0') +gi.require_version('Gtk', '3.0') + +from .appactivatable import AppActivatable +from .windowactivatable import WindowActivatable + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/appactivatable.py b/plugins/externaltools/tools/appactivatable.py new file mode 100644 index 0000000..87e1226 --- /dev/null +++ b/plugins/externaltools/tools/appactivatable.py @@ -0,0 +1,178 @@ +# -*- 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 + +from gi.repository import GLib, Gio, GObject, Gtk, Gdk, Gedit +from .library import ToolLibrary +from .manager import Manager +import os + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class ToolMenu(object): + def __init__(self, library, menu): + super(ToolMenu, self).__init__() + self._library = library + self._menu = menu + self._action_tools = {} + + self.update() + + def deactivate(self): + self.remove() + + def remove(self): + self._menu.remove_all() + + for name, tool in self._action_tools.items(): + if tool.shortcut: + app = Gio.Application.get_default() + app.remove_accelerator(tool.shortcut) + + def _insert_directory(self, directory, menu): + for d in sorted(directory.subdirs, key=lambda x: x.name.lower()): + submenu = Gio.Menu() + menu.append_submenu(d.name.replace('_', '__'), submenu) + section = Gio.Menu() + submenu.append_section(None, section) + + self._insert_directory(d, section) + + for tool in sorted(directory.tools, key=lambda x: x.name.lower()): + # FIXME: find a better way to share the action name + action_name = 'external-tool-%X-%X' % (id(tool), id(tool.name)) + item = Gio.MenuItem.new(tool.name.replace('_', '__'), "win.%s" % action_name) + item.set_attribute_value("hidden-when", GLib.Variant.new_string("action-disabled")) + menu.append_item(item) + + if tool.shortcut: + app = Gio.Application.get_default() + app.add_accelerator(tool.shortcut, "win.%s" % action_name, None) + + def update(self): + self.remove() + self._insert_directory(self._library.tree, self._menu) + + +# FIXME: restore the launch of the manager on configure using PeasGtk.Configurable +class AppActivatable(GObject.Object, Gedit.AppActivatable): + __gtype_name__ = "ExternalToolsAppActivatable" + + app = GObject.Property(type=Gedit.App) + + def __init__(self): + GObject.Object.__init__(self) + self.menu = None + self._manager = None + self._manager_default_size = None + + def do_activate(self): + self._library = ToolLibrary() + self._library.set_locations(os.path.join(self.plugin_info.get_data_dir(), 'tools')) + + action = Gio.SimpleAction(name="manage-tools") + action.connect("activate", lambda action, parameter: self._open_dialog()) + self.app.add_action(action) + + self.css = Gtk.CssProvider() + self.css.load_from_data(""" +.gedit-tool-manager-paned { + border-style: solid; + border-color: @borders; +} + +.gedit-tool-manager-paned:dir(ltr) { + border-width: 0 1px 0 0; +} + +.gedit-tool-manager-paned:dir(rtl) { + border-width: 0 0 0 1px; +} + +.gedit-tool-manager-view { + border-width: 0 0 1px 0; +} + +.gedit-tool-manager-treeview { + border-top-width: 0; +} + +.gedit-tool-manager-treeview:dir(ltr) { + border-left-width: 0; +} + +.gedit-tool-manager-treeview:dir(rtl) { + border-right-width: 0; +} +""".encode('utf-8')) + + Gtk.StyleContext.add_provider_for_screen(Gdk.Screen.get_default(), + self.css, 600) + + self.menu_ext = self.extend_menu("preferences-section") + item = Gio.MenuItem.new(_("Manage _External Tools…"), "app.manage-tools") + self.menu_ext.append_menu_item(item) + + self.submenu_ext = self.extend_menu("tools-section-1") + external_tools_submenu = Gio.Menu() + item = Gio.MenuItem.new_submenu(_("External _Tools"), external_tools_submenu) + self.submenu_ext.append_menu_item(item) + external_tools_submenu_section = Gio.Menu() + external_tools_submenu.append_section(None, external_tools_submenu_section) + + self.menu = ToolMenu(self._library, external_tools_submenu_section) + + def do_deactivate(self): + self.menu.deactivate() + self.menu_ext = None + self.submenu_ext = None + + self.app.remove_action("manage-tools") + + Gtk.StyleContext.remove_provider_for_screen(Gdk.Screen.get_default(), + self.css) + + def _open_dialog(self): + if not self._manager: + self._manager = Manager(self.plugin_info.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) + self._manager.connect('tools-updated', self._on_manager_tools_updated) + + self._manager.run(self.app.get_active_window()) + + return self._manager.dialog + + def _on_manager_destroy(self, dialog): + self._manager_default_size = self._manager.get_final_size() + self._manager = None + + def _on_manager_tools_updated(self, manager): + for window in self.app.get_main_windows(): + window.external_tools_window_activatable.update_actions() + self.menu.update() + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py new file mode 100644 index 0000000..d7560c5 --- /dev/null +++ b/plugins/externaltools/tools/capture.py @@ -0,0 +1,268 @@ +# -*- 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 +import sys +import signal +import locale +import subprocess +import fcntl +from gi.repository import GLib, GObject + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class Capture(GObject.Object): + CAPTURE_STDOUT = 0x01 + CAPTURE_STDERR = 0x02 + CAPTURE_BOTH = 0x03 + CAPTURE_NEEDS_SHELL = 0x04 + + WRITE_BUFFER_SIZE = 0x4000 + + __gsignals__ = { + 'stdout-line': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), + 'stderr-line': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,)), + 'begin-execute': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, tuple()), + 'end-execute': (GObject.SignalFlags.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.encode("UTF-8") if text else None + + 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.in_channel = None + self.out_channel = None + self.err_channel = None + self.in_channel_id = 0 + self.out_channel_id = 0 + self.err_channel_id = 0 + + try: + self.pipe = subprocess.Popen(self.command, **popen_args) + except OSError as e: + self.pipe = None + self.emit('stderr-line', _('Could not execute command: %s') % (e, )) + return + + self.emit('begin-execute') + + if self.input_text is not None: + self.in_channel, self.in_channel_id = self.add_in_watch(self.pipe.stdin.fileno(), + self.on_in_writable) + + if self.flags & self.CAPTURE_STDOUT: + self.out_channel, self.out_channel_id = self.add_out_watch(self.pipe.stdout.fileno(), + self.on_output) + + if self.flags & self.CAPTURE_STDERR: + self.err_channel, self.err_channel_id = self.add_out_watch(self.pipe.stderr.fileno(), + self.on_err_output) + + # Wait for the process to complete + GLib.child_watch_add(GLib.PRIORITY_DEFAULT, + self.pipe.pid, + self.on_child_end) + + def add_in_watch(self, fd, io_func): + channel = GLib.IOChannel.unix_new(fd) + channel.set_flags(channel.get_flags() | GLib.IOFlags.NONBLOCK) + channel.set_encoding(None) + channel_id = GLib.io_add_watch(channel, + GLib.PRIORITY_DEFAULT, + GLib.IOCondition.OUT | GLib.IOCondition.HUP | GLib.IOCondition.ERR, + io_func) + return (channel, channel_id) + + def add_out_watch(self, fd, io_func): + channel = GLib.IOChannel.unix_new(fd) + channel.set_flags(channel.get_flags() | GLib.IOFlags.NONBLOCK) + channel_id = GLib.io_add_watch(channel, + GLib.PRIORITY_DEFAULT, + GLib.IOCondition.IN | GLib.IOCondition.HUP | GLib.IOCondition.ERR, + io_func) + return (channel, channel_id) + + def write_chunk(self, dest, condition): + if condition & (GObject.IO_OUT): + status = GLib.IOStatus.NORMAL + l = len(self.input_text) + while status == GLib.IOStatus.NORMAL: + if l == 0: + return False + m = min(l, self.WRITE_BUFFER_SIZE) + try: + (status, length) = dest.write_chars(self.input_text, m) + self.input_text = self.input_text[length:] + l -= length + except Exception as e: + return False + if status != GLib.IOStatus.AGAIN: + return False + + if condition & ~(GObject.IO_OUT): + return False + + return True + + def on_in_writable(self, dest, condition): + ret = self.write_chunk(dest, condition) + if ret is False: + self.input_text = None + try: + self.in_channel.shutdown(True) + except: + pass + self.in_channel = None + self.in_channel_id = 0 + self.cleanup_pipe() + + return ret + + def handle_source(self, source, condition, signalname): + if condition & (GObject.IO_IN | GObject.IO_PRI): + status = GLib.IOStatus.NORMAL + while status == GLib.IOStatus.NORMAL: + try: + (status, buf, length, terminator_pos) = source.read_line() + except Exception as e: + return False + if buf: + self.emit(signalname, buf) + if status != GLib.IOStatus.AGAIN: + return False + + if condition & ~(GObject.IO_IN | GObject.IO_PRI): + return False + + return True + + def on_output(self, source, condition): + ret = self.handle_source(source, condition, 'stdout-line') + if ret is False and self.out_channel: + try: + self.out_channel.shutdown(True) + except: + pass + self.out_channel = None + self.out_channel_id = 0 + self.cleanup_pipe() + + return ret + + def on_err_output(self, source, condition): + ret = self.handle_source(source, condition, 'stderr-line') + if ret is False and self.err_channel: + try: + self.err_channel.shutdown(True) + except: + pass + self.err_channel = None + self.err_channel_id = 0 + self.cleanup_pipe() + + return ret + + def cleanup_pipe(self): + if self.in_channel is None and self.out_channel is None and self.err_channel is None: + self.pipe = None + + def stop(self, error_code=-1): + if self.in_channel_id: + GLib.source_remove(self.in_channel_id) + self.in_channel.shutdown(True) + self.in_channel = None + self.in_channel_id = 0 + + if self.out_channel_id: + GLib.source_remove(self.out_channel_id) + self.out_channel.shutdown(True) + self.out_channel = None + self.out_channel_id = 0 + + if self.err_channel_id: + GLib.source_remove(self.err_channel_id) + self.err_channel.shutdown(True) + self.err_channel = None + self.err_channel = 0 + + if self.pipe is not None: + if not self.tried_killing: + os.kill(self.pipe.pid, signal.SIGTERM) + self.tried_killing = True + else: + os.kill(self.pipe.pid, signal.SIGKILL) + + self.pipe = None + + def emit_end_execute(self, error_code): + self.emit('end-execute', error_code) + return False + + def on_child_end(self, pid, error_code): + # In an idle, so it is emitted after all the std*-line signals + # have been intercepted + GLib.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 100644 index 0000000..f256eea --- /dev/null +++ b/plugins/externaltools/tools/filelookup.py @@ -0,0 +1,165 @@ +# -*- 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 +from gi.repository import Gio, Gedit +from .functions import * + + +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, window): + self.providers = [] + self.providers.append(AbsoluteFileLookupProvider()) + self.providers.append(BrowserRootFileLookupProvider(window)) + 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_new_for_path(path) + else: + return None + + +class BrowserRootFileLookupProvider(FileLookupProvider): + """ + This lookup provider tries to find a file specified by the path relative to + the file browser root. + """ + def __init__(self, window): + self.window = window + + def lookup(self, path): + root = file_browser_root(self.window) + if root: + real_path = os.path.join(root, path) + if os.path.isfile(real_path): + return Gio.file_new_for_path(real_path) + + 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_new_for_path(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 Gio.Application.get_default().get_documents(): + if doc.get_file().is_local(): + location = doc.get_file().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_new_for_path(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 Gio.Application.get_default().get_documents(): + if doc.get_file().is_local(): + location = doc.get_file().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 100644 index 0000000..bc755be --- /dev/null +++ b/plugins/externaltools/tools/functions.py @@ -0,0 +1,365 @@ +# -*- 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 +from gi.repository import Gio, Gtk, Gdk, GtkSource, Gedit +from .capture import * + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +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) + + +def file_browser_root(window): + bus = window.get_message_bus() + + if bus.is_registered('/plugins/filebrowser', 'get_root'): + msg = bus.send_sync('/plugins/filebrowser', 'get_root') + + if msg: + browser_root = msg.props.location + + if browser_root and browser_root.is_native(): + return browser_root.get_path() + + return None + + +# ==== Capture related functions ==== +def run_external_tool(window, panel, 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() + document = None + + if view is not None: + # Environment vars relative to current document + document = view.get_buffer() + location = document.get_file().get_location() + + # 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)) + + if document.get_language() is not None: + capture.set_env(GEDIT_CURRENT_DOCUMENT_LANGUAGE=document.get_language().get_id()) + + # 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 location is not None: + scheme = location.get_uri_scheme() + name = location.get_basename() + capture.set_env(GEDIT_CURRENT_DOCUMENT_URI=location.get_uri(), + GEDIT_CURRENT_DOCUMENT_NAME=name, + GEDIT_CURRENT_DOCUMENT_SCHEME=scheme) + if location.has_uri_scheme('file'): + path = location.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_location = [doc.get_file().get_location() + for doc in window.get_documents() + if doc.get_file().get_location() is not None] + documents_uri = [location.get_uri() + for location in documents_location + if location.get_uri() is not None] + documents_path = [location.get_path() + for location in documents_location + if location.has_uri_scheme('file')] + capture.set_env(GEDIT_DOCUMENTS_URI=' '.join(documents_uri), + GEDIT_DOCUMENTS_PATH=' '.join(documents_path)) + + # set file browser root env var if possible + browser_root = file_browser_root(window) + if browser_root: + capture.set_env(GEDIT_FILE_BROWSER_ROOT=browser_root) + + 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 + + # Clear the panel + 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() + 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.error_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, False) + 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.startswith('replace-'): + if output_type == 'replace-selection': + try: + start_iter, end_iter = document.get_selection_bounds() + except ValueError: + start_iter = document.get_iter_at_mark(document.get_insert()) + end_iter = start_iter.copy() + elif output_type == 'replace-document': + start_iter, end_iter = document.get_bounds() + capture.connect('stdout-line', capture_delayed_replace, + document, start_iter, end_iter) + else: + if output_type == 'insert': + pos = document.get_iter_at_mark(document.get_insert()) + 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) + + if not document is None: + 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': + if not document is None: + document.end_user_action() + +class MultipleDocumentsSaver: + def __init__(self, window, panel, all_docs, node): + self._window = window + self._panel = panel + self._node = node + + if all_docs: + docs = window.get_documents() + else: + docs = [window.get_active_document()] + + self._docs_to_save = [doc for doc in docs if doc.get_modified()] + self.save_next_document() + + def save_next_document(self): + if len(self._docs_to_save) == 0: + # The documents are saved, we can run the tool. + run_external_tool(self._window, self._panel, self._node) + else: + next_doc = self._docs_to_save[0] + self._docs_to_save.remove(next_doc) + + Gedit.commands_save_document_async(next_doc, + self._window, + None, + self.on_document_saved, + None) + + def on_document_saved(self, doc, result, user_data): + saved = Gedit.commands_save_document_finish(doc, result) + if saved: + self.save_next_document() + + +def capture_menu_action(action, parameter, window, panel, node): + if node.save_files == 'document' and window.get_active_document(): + MultipleDocumentsSaver(window, panel, False, node) + return + elif node.save_files == 'all': + MultipleDocumentsSaver(window, panel, True, node) + return + + run_external_tool(window, panel, 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): + if view: + view.get_window(Gtk.TextWindowType.TEXT).set_cursor(Gdk.Cursor.new(Gdk.CursorType.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 view: + if output_type in ('new-document', 'replace-document'): + doc = view.get_buffer() + start = doc.get_start_iter() + end = start.copy() + end.forward_chars(300) + uri = '' + + mtype, uncertain = Gio.content_type_guess(None, doc.get_text(start, end, False).encode('utf-8')) + lmanager = GtkSource.LanguageManager.get_default() + + location = doc.get_file().get_location() + if location: + uri = location.get_uri() + language = lmanager.guess_language(uri, mtype) + + if language is not None: + doc.set_language(language) + + view.get_window(Gtk.TextWindowType.TEXT).set_cursor(Gdk.Cursor.new(Gdk.CursorType.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) + + +def capture_delayed_replace(capture, line, document, start_iter, end_iter): + document.delete(start_iter, end_iter) + + # Must be done after deleting the text + pos = document.get_iter_at_mark(document.get_insert()) + + capture_stdout_line_document(capture, line, document, pos) + + capture.disconnect_by_func(capture_delayed_replace) + capture.connect('stdout-line', capture_stdout_line_document, document, pos) + +# ex:ts=4:et: diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py new file mode 100644 index 0000000..adfd943 --- /dev/null +++ b/plugins/externaltools/tools/library.py @@ -0,0 +1,520 @@ +# -*- 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 +from gi.repository import GLib + + +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', '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('GNOME22_USER_DIR') + if userdir: + toolsdir = os.path.join(userdir, 'gedit/tools') + else: + toolsdir = os.path.join(GLib.get_user_config_dir(), '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('GNOME22_USER_DIR') + if userdir: + filename = os.path.join(userdir, 'gedit/gedit-tools.xml') + else: + filename = os.path.join(GLib.get_user_config_dir(), '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 = sorted(elements.keys()) + 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 [x.strip() for x in 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 = open(filename, 'r', 1, encoding='utf-8') + 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, encoding='utf-8') + 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, encoding='utf-8') + 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.items(): + 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, encoding='utf-8') + + # 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, 0o750) + 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() + library.set_locations(os.path.expanduser("~/.config/gedit/tools")) + + 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 100644 index 0000000..d9c09a5 --- /dev/null +++ b/plugins/externaltools/tools/linkparsing.py @@ -0,0 +1,252 @@ +# -*- 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, col_nr, start, end): + """ + path -- the path of the file (that could be extracted) + line_nr -- the line nr of the specified file + col_nr -- the col nr of the specific 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.col_nr = int(col_nr) + self.start = start + self.end = end + + def __repr__(self): + return "%s[%s][%s](%s:%s)" % (self.path, self.line_nr, self.col_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): + groups = m.groups() + + path = m.group("pth") + line_nr = m.group("ln") + start = m.start("lnk") + end = m.end("lnk") + + # some regexes may have a col group + if len(groups) > 3 and groups[3] != None: + col_nr = m.group("col") + else: + col_nr = 0 + + link = Link(path, line_nr, col_nr, start, end) + links.append(link) + + return links + +# gcc 'test.c:13: warning: ...' +# grep 'test.c:5:int main(...' +# javac 'Test.java:13: ...' +# ruby 'test.rb:5: ...' +# scalac 'Test.scala:5: ...' +# sbt (scala) '[error] test.scala:4: ...' +# 6g (go) 'test.go:9: ...' +REGEXP_STANDARD = r""" +^ +(?:\[(?:error|warn)\]\ )? +(?P + (?P [^ \:\n]* ) + \: + (?P \d+) + \:? + (?P \d+)? +) +\:""" + +# 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' +# fpc (Pascal) 'hello.pas(11,1) Fatal: Syntax error, ":" expected but "BEGIN"' +REGEXP_MCS = r""" +^ +(?P + (?P \S+ ) + \( + (?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 100644 index 0000000..072286b --- /dev/null +++ b/plugins/externaltools/tools/manager.py @@ -0,0 +1,878 @@ +# -*- 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 os.path +from .library import * +from .functions import * +import hashlib +from xml.sax import saxutils +from gi.repository import Gio, GObject, Gtk, GtkSource, Gedit + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class LanguagesPopup(Gtk.Popover): + __gtype_name__ = "LanguagePopup" + + COLUMN_NAME = 0 + COLUMN_ID = 1 + COLUMN_ENABLED = 2 + + def __init__(self, widget, languages): + Gtk.Popover.__init__(self, relative_to=widget) + + self.props.can_focus = True + + self.build() + self.init_languages(languages) + + self.view.get_selection().select_path((0,)) + + def build(self): + self.model = Gtk.ListStore(str, str, bool) + + self.sw = Gtk.ScrolledWindow() + self.sw.set_size_request(-1, 200) + self.sw.show() + + self.sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) + + self.view = Gtk.TreeView(model=self.model) + self.view.show() + + self.view.set_headers_visible(False) + + column = Gtk.TreeViewColumn() + + renderer = Gtk.CellRendererToggle() + column.pack_start(renderer, False) + column.add_attribute(renderer, 'active', self.COLUMN_ENABLED) + + renderer.connect('toggled', self.on_language_toggled) + + renderer = Gtk.CellRendererText() + column.pack_start(renderer, True) + column.add_attribute(renderer, 'text', self.COLUMN_NAME) + + self.view.append_column(column) + self.view.set_row_separator_func(self.on_separator, None) + + 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.get_indices()[0] == 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, user_data=None): + val = model.get_value(piter, self.COLUMN_NAME) + return val == '-' + + def init_languages(self, languages): + manager = GtkSource.LanguageManager() + langs = [manager.get_language(x) for x in manager.get_language_ids()] + langs.sort(key=lambda x: x.get_name()) + + 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.get_indices()[0] == 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) + + +class Manager(GObject.Object): + TOOL_COLUMN = 0 # For Tree + NAME_COLUMN = 1 # For Combo + + __gsignals__ = { + 'tools-updated': (GObject.SignalFlags.RUN_LAST, None, ()) + } + + def __init__(self, datadir): + GObject.Object.__init__(self) + self.datadir = datadir + self.dialog = None + self._size = (0, 0) + self._languages = {} + self._tool_rows = {} + + self.build() + + def get_final_size(self): + return self._size + + def build(self): + callbacks = { + 'on_add_tool_button_clicked': self.on_add_tool_button_clicked, + 'on_remove_tool_button_clicked': self.on_remove_tool_button_clicked, + 'on_tool_manager_dialog_delete_event': self.on_tool_manager_dialog_delete_event, + 'on_tool_manager_dialog_focus_out': self.on_tool_manager_dialog_focus_out, + 'on_tool_manager_dialog_configure_event': self.on_tool_manager_dialog_configure_event, + '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_accelerator_backspace': self.on_accelerator_backspace, + 'on_applicability_changed': self.on_applicability_changed, + '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['view'] + + self.__init_tools_model() + self.__init_tools_view() + + # join treeview and toolbar + context = self['scrolled_window1'].get_style_context() + context.set_junction_sides(Gtk.JunctionSides.BOTTOM) + context = self['toolbar1'].get_style_context() + context.set_junction_sides(Gtk.JunctionSides.TOP) + context.set_junction_sides(Gtk.JunctionSides.BOTTOM) + + 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 is 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, GtkSource.Language): + lid = language.get_id() + else: + lid = language + + if not lid in self._languages: + piter = self.model.append(None, [language]) + + parent = Gtk.TreeRowReference.new(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.new(self.model, self.model.get_path(child))) + return child + + def add_tool(self, tool): + manager = GtkSource.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.SortType.ASCENDING) + + def sort_tools(self, model, iter1, iter2, user_data=None): + # 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) is None: + if t1 is None: + return -1 + + if t2 is None: + return 1 + + def lang_name(lang): + if isinstance(lang, GtkSource.Language): + return lang.get_name() + else: + return _('Plain Text') + + n1 = lang_name(t1) + n2 = lang_name(t2) + else: + n1 = t1.name + n2 = t2.name + + if n1.lower() < n2.lower(): + return -1 + elif n1.lower() > n2.lower(): + return 1 + else: + return 0 + + 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, None) + + 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.encode('utf-8')).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() + (start, end) = buf.get_bounds() + script = buf.get_text(start, end, False) + 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 = GtkSource.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): + self.update_accelerator_label() + + buf = self['commands'].get_buffer() + script = default(''.join(self.current_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, uncertain = Gio.content_type_guess(None, script.encode('utf-8')) + lmanager = GtkSource.LanguageManager.get_default() + language = lmanager.guess_language(None, 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(self.current_node.__getattribute__(nm.replace('-', '_')), + model.get_value(piter, self.NAME_COLUMN))) + + self.fill_languages_button() + + def update_accelerator_label(self): + if self.current_node.shortcut: + key, mods = Gtk.accelerator_parse(self.current_node.shortcut) + label = Gtk.accelerator_get_label(key, mods) + self['accelerator'].set_text(label) + else: + self['accelerator'].set_text('') + + 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-grid'].set_sensitive(True) + else: + self.clear_fields() + self['tool-grid'].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, GtkSource.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_add_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-grid'].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.set_value(self.model.get_iter(row.get_path()), + self.TOOL_COLUMN, + tool) + + 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] = [x for x in self._tool_rows[node] if x.valid()] + + 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.DialogFlags.MODAL, + Gtk.MessageType.ERROR, + Gtk.ButtonsType.CLOSE, + _('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 == Gdk.KEY_Escape: + self.update_accelerator_label() + self['commands'].grab_focus() + return True + elif event.keyval == Gdk.KEY_BackSpace: + return False + elif event.keyval in range(Gdk.KEY_F1, Gdk.KEY_F12 + 1): + # New accelerator + if self.set_accelerator(event.keyval, mask): + self.update_accelerator_label() + self['commands'].grab_focus() + + # Capture all `normal characters` + return True + elif Gdk.keyval_to_unicode(event.keyval): + if mask: + # New accelerator + if self.set_accelerator(event.keyval, mask): + self.update_accelerator_label() + 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: + self.update_accelerator_label() + self.tool_changed(self.current_node) + + def on_accelerator_backspace(self, entry): + entry.set_text('') + self.remove_accelerator(self.current_node) + self.current_node.shortcut = None + self['commands'].grab_focus() + + def on_tool_manager_dialog_delete_event(self, dialog, event): + self.save_current_tool() + return False + + def on_tool_manager_dialog_focus_out(self, dialog, event): + self.save_current_tool() + self.emit('tools-updated') + + def on_tool_manager_dialog_configure_event(self, dialog, event): + if dialog.get_realized(): + alloc = dialog.get_allocation() + self._size = (alloc.width, alloc.height) + + def on_applicability_changed(self, combo): + applicability = combo.get_model().get_value(combo.get_active_iter(), + self.NAME_COLUMN) + + if applicability == 'always': + if self.current_node is not None: + self.current_node.languages = [] + + self.fill_languages_button() + + self['languages_button'].set_sensitive(applicability != 'always') + + def get_cell_data_cb(self, column, cell, model, piter, user_data=None): + tool = model.get_value(piter, self.TOOL_COLUMN) + + if tool is None or not isinstance(tool, Tool): + if tool is None: + label = _('All Languages') + elif not isinstance(tool, GtkSource.Language): + label = _('Plain Text') + else: + label = tool.get_name() + + markup = saxutils.escape(label) + editable = False + else: + escaped = saxutils.escape(tool.name) + + if tool.shortcut: + key, mods = Gtk.accelerator_parse(tool.shortcut) + label = Gtk.accelerator_get_label(key, mods) + markup = '%s (%s)' % (escaped, label) + 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.get_indices()[0] == parent.get_indices()[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.new(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 = GtkSource.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(button, self.current_node.languages) + popup.show() + popup.connect('closed', self.update_languages) + +# ex:et:ts=4: diff --git a/plugins/externaltools/tools/meson.build b/plugins/externaltools/tools/meson.build new file mode 100644 index 0000000..bd623cf --- /dev/null +++ b/plugins/externaltools/tools/meson.build @@ -0,0 +1,36 @@ +externaltools_sources = files( + '__init__.py', + 'appactivatable.py', + 'capture.py', + 'filelookup.py', + 'functions.py', + 'library.py', + 'linkparsing.py', + 'manager.py', + 'outputpanel.py', + 'windowactivatable.py', +) + +install_data( + externaltools_sources, + install_dir: join_paths( + pkglibdir, + 'plugins', + 'externaltools', + ) +) + +externaltools_data = files( + 'outputpanel.ui', + 'tools.ui', +) + +install_data( + externaltools_data, + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'externaltools', + 'ui', + ) +) diff --git a/plugins/externaltools/tools/outputpanel.py b/plugins/externaltools/tools/outputpanel.py new file mode 100644 index 0000000..e9fc241 --- /dev/null +++ b/plugins/externaltools/tools/outputpanel.py @@ -0,0 +1,247 @@ +# -*- 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 os +from weakref import WeakKeyDictionary +from .capture import * +import re +from . import linkparsing +from . import filelookup +from gi.repository import GLib, Gio, Gdk, Gtk, Pango, Gedit + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +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 + } + + self.profile_settings = self.get_profile_settings() + self.profile_settings.connect("changed", self.font_changed) + self.system_settings = Gio.Settings.new("org.gnome.desktop.interface") + self.system_settings.connect("changed::monospace-font-name", self.font_changed) + + 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['view'].connect('button-press-event', self.on_view_button_press_event) + + self.panel = self["output-panel"] + self.font_changed() + + 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.new(Gdk.CursorType.HAND2) + self.normal_cursor = Gdk.Cursor.new(Gdk.CursorType.XTERM) + + self.process = None + + self.links = [] + + self.link_parser = linkparsing.LinkParser() + self.file_lookup = filelookup.FileLookup(window) + + def get_profile_settings(self): + #FIXME return either the gnome-terminal settings or the gedit one + return Gio.Settings.new("org.gnome.gedit.plugins.externaltools") + + def font_changed(self, settings=None, key=None): + if self.profile_settings.get_boolean("use-system-font"): + font = self.system_settings.get_string("monospace-font-name") + else: + font = self.profile_settings.get_string("font") + + font_desc = Pango.font_description_from_string(font) + + self["view"].override_font(font_desc) + + 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, False, 0.5, 0.5) + 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.props.visible_child == 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) + GLib.idle_add(self.scroll_to_end) + + def show(self): + panel = self.window.get_bottom_panel() + panel.props.visible_child = self.panel + panel.show() + + 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.TextWindowType.TEXT).set_cursor(cursor) + + def on_view_motion_notify_event(self, view, event): + if event.window == view.get_window(Gtk.TextWindowType.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.TextWindowType.TEXT): + win, x, y, flags = 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.TextWindowType.TEXT, x, y) + (over_text, iter_at_xy) = view.get_iter_at_location(buff_x, buff_y) + if not over_text: + return None + 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.EventType.BUTTON_PRESS or \ + event.window != view.get_window(Gtk.TextWindowType.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_location(self.window, gfile, None, link.line_nr, link.col_nr) + GLib.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 100644 index 0000000..4c163c2 --- /dev/null +++ b/plugins/externaltools/tools/outputpanel.ui @@ -0,0 +1,49 @@ + + + + + True + False + + + True + True + True + True + + + True + True + False + word + False + False + + + + + + + + + True + False + True + True + end + end + 2 + 2 + Stop Tool + + + + True + False + process-stop-symbolic + + + + + + diff --git a/plugins/externaltools/tools/tools.ui b/plugins/externaltools/tools/tools.ui new file mode 100644 index 0000000..a3f0ab1 --- /dev/null +++ b/plugins/externaltools/tools/tools.ui @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + Always available + always + + + All documents + all + + + All documents except untitled ones + titled + + + Local files only + local + + + Remote files only + remote + + + Untitled documents only + untitled + + + + + + + + + + + + + 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 + + + + + + + + + + + + + Nothing + nothing + + + Current document + document + + + All documents + all + + + + + False + Manage External Tools + 800 + 600 + dialog + + + + + + True + Manage External Tools + True + + + + + True + True + True + 275 + True + + + + True + False + vertical + + + True + True + in + + + + True + True + False + True + + + + + + + + True + True + 0 + + + + + True + False + icons + + 1 + + + True + False + Add a new tool + Add Tool + True + list-add-symbolic + + + + False + True + + + + + True + False + Remove selected tool + Remove Tool + True + list-remove-symbolic + + + + False + True + + + + + True + False + Revert tool + Revert Tool + True + edit-undo-symbolic + + + + False + True + + + + + False + True + 1 + + + + + False + False + + + + + True + False + vertical + 6 + + + True + False + + + True + False + + + True + False + 6 + 6 + 6 + 6 + 6 + 6 + + + True + False + 0 + Shortcut _key: + True + accelerator + True + + + 0 + 0 + 1 + 1 + + + + + True + True + True + + + + + + + 1 + 0 + 1 + 1 + + + + + True + False + 0 + _Save: + True + save-files + + + 0 + 1 + 1 + 1 + + + + + True + False + model_save_files + + + + 0 + + + + + 1 + 1 + 1 + 1 + + + + + True + False + 0 + _Input: + True + input + + + 0 + 2 + 1 + 1 + + + + + True + False + model_input + + + + 0 + + + + + 1 + 2 + 1 + 1 + + + + + True + False + 0 + _Output: + True + output + + + 0 + 3 + 1 + 1 + + + + + True + False + model_output + + + + 0 + + + + + 1 + 3 + 1 + 1 + + + + + True + False + 0 + _Applicability: + True + applicability + + + 0 + 4 + 1 + 1 + + + + + True + False + 6 + + + True + False + model_applicability + + + + + 0 + + + + + False + True + 0 + + + + + False + True + True + False + + + + True + False + 0 + All Languages + end + 13 + + + + + True + True + 1 + + + + + 1 + 4 + 1 + 1 + + + + + 0 + 1 + 1 + 1 + + + + + True + True + True + True + in + + + + commands_buffer + True + + + + + 0 + 0 + 1 + 1 + + + + + False + True + 0 + + + + + True + True + 1 + + + + + True + False + + + + + + diff --git a/plugins/externaltools/tools/windowactivatable.py b/plugins/externaltools/tools/windowactivatable.py new file mode 100644 index 0000000..5949598 --- /dev/null +++ b/plugins/externaltools/tools/windowactivatable.py @@ -0,0 +1,141 @@ +# -*- 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', 'OutputPanel', 'Capture', 'UniqueById') + +from gi.repository import GLib, Gio, GObject, Gtk, Gedit +from .library import ToolLibrary +from .outputpanel import OutputPanel +from .capture import Capture +from .functions import * + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class ToolActions(object): + def __init__(self, library, window, panel): + super(ToolActions, self).__init__() + self._library = library + self._window = window + self._panel = panel + self._action_tools = {} + + self.update() + + def deactivate(self): + self.remove() + + def remove(self): + for name, tool in self._action_tools.items(): + self._window.remove_action(name) + self._action_tools = {} + + def _insert_directory(self, directory): + for tool in sorted(directory.tools, key=lambda x: x.name.lower()): + # FIXME: find a better way to share the action name + action_name = 'external-tool-%X-%X' % (id(tool), id(tool.name)) + self._action_tools[action_name] = tool + + action = Gio.SimpleAction(name=action_name) + action.connect('activate', capture_menu_action, self._window, self._panel, tool) + self._window.add_action(action) + + def update(self): + self.remove() + self._insert_directory(self._library.tree) + 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: + titled = False + remote = False + language = None + else: + titled = document.get_file().get_location() is not None + remote = not document.get_file().is_local() + language = document.get_language() + + states = { + 'always': True, + 'all': document is not None, + 'local': titled and not remote, + 'remote': titled and remote, + 'titled': titled, + 'untitled': not titled, + } + + for name, tool in self._action_tools.items(): + action = self._window.lookup_action(name) + if action: + action.set_enabled(states[tool.applicability] and + self.filter_language(language, tool)) + + +class WindowActivatable(GObject.Object, Gedit.WindowActivatable): + __gtype_name__ = "ExternalToolsWindowActivatable" + + window = GObject.Property(type=Gedit.Window) + + def __init__(self): + GObject.Object.__init__(self) + self.actions = None + + def do_activate(self): + self.window.external_tools_window_activatable = self + + self._library = ToolLibrary() + + # Create output console + self._output_buffer = OutputPanel(self.plugin_info.get_data_dir(), self.window) + + self.actions = ToolActions(self._library, self.window, self._output_buffer) + + bottom = self.window.get_bottom_panel() + bottom.add_titled(self._output_buffer.panel, "GeditExternalToolsShellOutput", _("Tool Output")) + + def do_update_state(self): + if self.actions is not None: + self.actions.filter(self.window.get_active_document()) + + def do_deactivate(self): + self.actions.deactivate() + bottom = self.window.get_bottom_panel() + bottom.remove(self._output_buffer.panel) + self.window.external_tools_window_activatable = None + + def update_actions(self): + self.actions.update() + +# ex:ts=4:et: diff --git a/plugins/filebrowser/filebrowser.plugin.desktop.in b/plugins/filebrowser/filebrowser.plugin.desktop.in new file mode 100644 index 0000000..706168d --- /dev/null +++ b/plugins/filebrowser/filebrowser.plugin.desktop.in @@ -0,0 +1,12 @@ +[Plugin] +Loader=C +Module=filebrowser +IAge=3 +Name=File Browser Panel +Description=Easy file access from the side panel. +# TRANSLATORS: Do NOT translate or transliterate this text! +# This is an icon file name. +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 100644 index 0000000..df0968d --- /dev/null +++ b/plugins/filebrowser/gedit-file-bookmarks-store.c @@ -0,0 +1,920 @@ +/* + * 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, see . + */ + +#include +#include +#include +#include + +#include "gedit-file-bookmarks-store.h" +#include "gedit-file-browser-utils.h" + +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); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBookmarksStore, + gedit_file_bookmarks_store, + GTK_TYPE_TREE_STORE, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBookmarksStore)) + +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; + } + + g_clear_object (&obj->priv->bookmarks_monitor); + + G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->dispose (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; +} + +static void +gedit_file_bookmarks_store_class_finalize (GeditFileBookmarksStoreClass *klass) +{ +} + +static void +gedit_file_bookmarks_store_init (GeditFileBookmarksStore *obj) +{ + obj->priv = gedit_file_bookmarks_store_get_instance_private (obj); +} + +/* Private */ +static void +add_node (GeditFileBookmarksStore *model, + GdkPixbuf *pixbuf, + const gchar *icon_name, + 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_ICON_NAME, icon_name, + 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) +{ + gboolean native = g_file_is_native (file); + gchar *icon_name = NULL; + gchar *newname; + + if (native && !g_file_query_exists (file, NULL)) + return FALSE; + + if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_HOME) + icon_name = g_strdup ("user-home-symbolic"); + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP) + icon_name = g_strdup ("user-desktop-symbolic"); + else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT) + icon_name = g_strdup ("drive-harddisk-symbolic"); + else + { + /* getting the icon is a sync get_info call, so we just do it for local files */ + if (native) + icon_name = gedit_file_browser_utils_symbolic_icon_name_from_file (file); + else + icon_name = g_strdup ("folder-symbolic"); + } + + if (name == NULL) + newname = gedit_file_browser_utils_file_basename (file); + else + newname = g_strdup (name); + + add_node (model, NULL, icon_name, newname, G_OBJECT (file), flags, iter); + + g_free (icon_name); + g_free (newname); + + return TRUE; +} + +static void +check_mount_separator (GeditFileBookmarksStore *model, + guint flags, + gboolean added) +{ + GtkTreeIter iter; + gboolean 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, 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 = g_get_home_dir (); + GFile *file; + + if (path != NULL) + { + file = g_file_new_for_path (path); + add_file (model, + file, + _("Home"), + GEDIT_FILE_BOOKMARKS_STORE_IS_HOME | GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, + NULL); + g_object_unref (file); + } + +#if defined(G_OS_WIN32) || defined(OS_OSX) + 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); + } +#endif + + 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, + gchar **icon_name, + guint *flags) +{ + GIcon *icon = NULL; + + *flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS; + *name = NULL; + *icon_name = NULL; + + if (G_IS_DRIVE (fs)) + { + icon = g_drive_get_symbolic_icon (G_DRIVE (fs)); + *name = g_drive_get_name (G_DRIVE (fs)); + *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE; + } + else if (G_IS_VOLUME (fs)) + { + icon = g_volume_get_symbolic_icon (G_VOLUME (fs)); + *name = g_volume_get_name (G_VOLUME (fs)); + *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME; + } + else if (G_IS_MOUNT (fs)) + { + icon = g_mount_get_symbolic_icon (G_MOUNT (fs)); + *name = g_mount_get_name (G_MOUNT (fs)); + *icon_name = gedit_file_browser_utils_name_from_themed_icon (icon); + + *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT; + } + + if (icon) + g_object_unref (icon); +} + +static void +add_fs (GeditFileBookmarksStore *model, + gpointer fs, + guint flags, + GtkTreeIter *iter) +{ + gchar *icon_name = NULL; + gchar *name = NULL; + guint fsflags; + + get_fs_properties (fs, &name, &icon_name, &fsflags); + add_node (model, NULL, icon_name, name, fs, flags | fsflags, iter); + + g_free (name); + g_free (icon_name); + check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_FS, TRUE); +} + +static void +process_volume_cb (GVolume *volume, + GeditFileBookmarksStore *model) +{ + GMount *mount = g_volume_get_mount (volume); + guint flags = GEDIT_FILE_BOOKMARKS_STORE_NONE; + + /* 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 = 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 = g_volume_monitor_get_connected_drives (model->priv->volume_monitor); + + g_list_foreach (drives, (GFunc)process_drive_cb, model); + g_list_free_full (drives, g_object_unref); +} + +static void +process_volume_nodrive_cb (GVolume *volume, + GeditFileBookmarksStore *model) +{ + GDrive *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 = g_volume_monitor_get_volumes (model->priv->volume_monitor); + + g_list_foreach (volumes, (GFunc)process_volume_nodrive_cb, model); + g_list_free_full (volumes, g_object_unref); +} + +static void +process_mount_novolume_cb (GMount *mount, + GeditFileBookmarksStore *model) +{ + GVolume *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 = g_volume_monitor_get_mounts (model->priv->volume_monitor); + + g_list_foreach (mounts, (GFunc)process_mount_novolume_cb, model); + g_list_free_full (mounts, g_object_unref); +} + +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 != NULL; ++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 = g_file_new_for_uri (uri); + guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK; + GtkTreeIter iter; + gboolean ret; + + 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 gchar * +get_bookmarks_file (void) +{ + return g_build_filename (g_get_user_config_dir (), "gtk-3.0", "bookmarks", NULL); +} + +static gchar * +get_legacy_bookmarks_file (void) +{ + return g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL); +} + +static gboolean +parse_bookmarks_file (GeditFileBookmarksStore *model, + const gchar *bookmarks, + gboolean *added) +{ + GError *error = NULL; + gchar *contents; + gchar **lines; + gchar **line; + + if (!g_file_get_contents (bookmarks, &contents, NULL, &error)) + { + /* The bookmarks file doesn't exist (which is perfectly fine) */ + g_error_free (error); + + return FALSE; + } + + lines = g_strsplit (contents, "\n", 0); + + for (line = lines; *line; ++line) + { + if (**line) + { + GFile *location; + + 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 */ + location = g_file_new_for_uri (*line); + if (gedit_utils_is_valid_location (location)) + { + *added |= add_bookmark (model, name, *line); + } + g_object_unref (location); + } + } + + g_strfreev (lines); + g_free (contents); + + /* Add a watch */ + if (model->priv->bookmarks_monitor == NULL) + { + GFile *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", + G_CALLBACK (on_bookmarks_file_changed), + model); + } + + return TRUE; +} + +static void +init_bookmarks (GeditFileBookmarksStore *model) +{ + gchar *bookmarks = get_bookmarks_file (); + gboolean added = FALSE; + + if (!parse_bookmarks_file (model, bookmarks, &added)) + { + g_free (bookmarks); + + /* try the old location (gtk <= 3.4) */ + bookmarks = get_legacy_bookmarks_file (); + parse_bookmarks_file (model, bookmarks, &added); + } + + if (added) + { + /* Bookmarks separator */ + add_node (model, NULL, 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 nautilus */ + 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 sep = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR; + guint f1; + guint f2; + gint *flags; + + 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 = 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) && + 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_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; +} + +GFile * +gedit_file_bookmarks_store_get_location (GeditFileBookmarksStore *model, + GtkTreeIter *iter) +{ + GObject *obj; + GFile *file = NULL; + guint flags; + GFile * 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 = (GFile *)g_object_ref (obj); + + g_object_unref (obj); + + if (file) + { + ret = g_file_dup (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; + /* FIXME: shouldn't we also monitor the directory? */ + case G_FILE_MONITOR_EVENT_DELETED: + /* Remove bookmarks */ + remove_bookmarks (model); + g_object_unref (monitor); + model->priv->bookmarks_monitor = NULL; + break; + default: + break; + } +} + +void +_gedit_file_bookmarks_store_register_type (GTypeModule *type_module) +{ + gedit_file_bookmarks_store_register_type (type_module); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.h b/plugins/filebrowser/gedit-file-bookmarks-store.h new file mode 100644 index 0000000..19de53b --- /dev/null +++ b/plugins/filebrowser/gedit-file-bookmarks-store.h @@ -0,0 +1,91 @@ +/* + * 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, see . + */ + +#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_ICON_NAME, + 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; + +GeditFileBookmarksStore *gedit_file_bookmarks_store_new (void); +GFile *gedit_file_bookmarks_store_get_location (GeditFileBookmarksStore *model, + GtkTreeIter *iter); +void gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore *model); + +void _gedit_file_bookmarks_store_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BOOKMARKS_STORE_H */ +/* ex:set 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 100644 index 0000000..c97ffff --- /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 "@basename@" */ + +/*** 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-stage1.c.template b/plugins/filebrowser/gedit-file-browser-enum-types-stage1.c.template new file mode 100644 index 0000000..fd58866 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-types-stage1.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 "@basename@" + +/*** 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 100644 index 0000000..c13167a --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-enum-types.h.template @@ -0,0 +1,28 @@ +/*** 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 "@basename@" */ + +/*** 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 100644 index 0000000..89e6ae3 --- /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, see . + */ + +#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 */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-messages.c b/plugins/filebrowser/gedit-file-browser-messages.c new file mode 100644 index 0000000..7f25424 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-messages.c @@ -0,0 +1,1074 @@ +/* + * gedit-file-browser-messages.c + * + * 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, see . + */ + +#include "gedit-file-browser-messages.h" +#include "gedit-file-browser-store.h" +#include "messages/messages.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) +#define BUS_DISCONNECT(bus, name, data) gedit_message_bus_disconnect_by_func(bus, MESSAGE_OBJECT_PATH, #name, (GeditMessageCallback) message_##name##_cb, data) + +typedef struct +{ + GeditWindow *window; + GeditMessage *message; +} MessageCacheData; + +typedef struct +{ + guint row_inserted_id; + guint before_row_deleted_id; + guint root_changed_id; + guint begin_loading_id; + guint end_loading_id; + + 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); + + 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); + + 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); + + g_hash_table_destroy (data->row_tracking); + g_hash_table_destroy (data->filters); + + 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; + GFile *location; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + location = gedit_file_browser_store_get_virtual_root (store); + + if (location) + { + g_object_set (message, "location", location, NULL); + g_object_unref (location); + } +} + +static void +message_set_root_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GFile *root; + GFile *virtual = NULL; + + g_object_get (message, "location", &root, NULL); + + if (!root) + { + return; + } + + g_object_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); + } +} + +static void +message_set_emblem_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gchar *id = NULL; + gchar *emblem = NULL; + GtkTreePath *path; + GeditFileBrowserStore *store; + + g_object_get (message, "id", &id, "emblem", &emblem, NULL); + + if (!id) + { + g_free (id); + g_free (emblem); + + return; + } + + path = track_row_lookup (data, id); + + if (path != NULL) + { + GtkTreeIter iter; + GValue value = G_VALUE_INIT; + GdkPixbuf *pixbuf = NULL; + + if (emblem != NULL) + { + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + emblem, + 10, + GTK_ICON_LOOKUP_FORCE_SIZE, + NULL); + } + + 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); + } + + if (pixbuf) + { + g_object_unref (pixbuf); + } + } + + g_free (id); + g_free (emblem); +} + +static void +message_set_markup_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + gchar *id = NULL; + gchar *markup = NULL; + GtkTreePath *path; + GeditFileBrowserStore *store; + + g_object_get (message, "id", &id, "markup", &markup, NULL); + + if (!id) + { + g_free (id); + g_free (markup); + + return; + } + + path = track_row_lookup (data, id); + + if (path != NULL) + { + GtkTreeIter iter; + GValue value = G_VALUE_INIT; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path)) + { + if (markup == NULL) + { + gchar *name; + + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name, + -1); + markup = g_markup_escape_text (name, -1); + + g_free (name); + } + + g_value_init (&value, G_TYPE_STRING); + g_value_set_string (&value, markup); + + gedit_file_browser_store_set_value (store, + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, + &value); + + g_value_unset (&value); + } + + gtk_tree_path_free (path); + } + + g_free (id); + g_free (markup); +} + +static gchar * +item_id (const gchar *path, + GFile *location) +{ + gchar *uri; + gchar *id; + + uri = g_file_get_uri (location); + id = g_strconcat (path, "::", uri, NULL); + g_free (uri); + + return id; +} + +static gchar * +track_row (WindowData *data, + GeditFileBrowserStore *store, + GtkTreePath *path, + GFile *location) +{ + GtkTreeRowReference *ref; + gchar *id; + gchar *pathstr; + + pathstr = gtk_tree_path_to_string (path); + id = item_id (pathstr, location); + + 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 *name; + GFile *location; + guint flags = 0; + + store = gedit_file_browser_widget_get_browser_store (data->widget); + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (location) + { + gchar *track_id; + + if (path && gtk_tree_path_get_depth (path) != 0) + { + track_id = track_row (data, store, path, location); + } + else + { + track_id = NULL; + } + + g_object_set (message, + "id", track_id, + "location", location, + NULL); + + if (gedit_message_has (message, "name")) + { + g_object_set (message, + "name", name, + NULL); + } + + if (gedit_message_has (message, "is_directory")) + { + g_object_set (message, + "is_directory", + FILE_IS_DIR (flags), + NULL); + } + + g_free (track_id); + g_object_unref (location); + } + + g_free (name); +} + +static gboolean +custom_message_filter_func (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GtkTreeIter *iter, + FilterData *data) +{ + WindowData *wdata = get_window_data (data->window); + GFile *location; + guint flags = 0; + gboolean filter = FALSE; + GtkTreePath *path; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!location || FILE_IS_DUMMY (flags)) + 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); + + g_object_set (data->message, "filter", filter, NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + g_object_get (data->message, "filter", &filter, NULL); + + g_object_unref (location); + + return !filter; +} + +static void +message_add_filter_cb (GeditMessageBus *bus, + GeditMessage *message, + GeditWindow *window) +{ + const gchar *object_path = NULL; + const gchar *method = NULL; + gulong id; + GeditMessage *cbmessage; + FilterData *filter_data; + WindowData *data; + GType message_type; + + data = get_window_data (window); + + object_path = gedit_message_get_object_path (message); + method = gedit_message_get_method (message); + + message_type = gedit_message_bus_lookup (bus, object_path, method); + + if (message_type == G_TYPE_INVALID) + { + return; + } + + /* Check if the message type has the correct arguments */ + if (!gedit_message_type_check (message_type, "id", G_TYPE_STRING) || + !gedit_message_type_check (message_type, "location", G_TYPE_FILE) || + !gedit_message_type_check (message_type, "is-directory", G_TYPE_BOOLEAN) || + !gedit_message_type_check (message_type, "filter", G_TYPE_BOOLEAN)) + { + return; + } + + cbmessage = g_object_new (message_type, + "object-path", object_path, + "method", method, + "id", NULL, + "location", 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; + + g_object_get (message, "id", &id, NULL); + + if (!id) + return; + + gedit_file_browser_widget_remove_filter (data->widget, id); +} + +static void +message_extend_context_menu_cb (GeditMessageBus *bus, + GeditMessage *message, + GeditWindow *window) +{ + WindowData *data; + GeditMenuExtension *ext; + + data = get_window_data (window); + + ext = gedit_file_browser_widget_extend_context_menu (data->widget); + + g_object_set (message, "extension", ext, NULL); + g_object_unref (ext); +} + +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; + + g_object_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; + + g_object_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_get_view_cb (GeditMessageBus *bus, + GeditMessage *message, + WindowData *data) +{ + GeditFileBrowserView *view; + view = gedit_file_browser_widget_get_browser_view (data->widget); + + g_object_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, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT, + MESSAGE_OBJECT_PATH, + "get_root"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT, + MESSAGE_OBJECT_PATH, + "set_root"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM, + MESSAGE_OBJECT_PATH, + "set_emblem"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP, + MESSAGE_OBJECT_PATH, + "set_markup"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER, + MESSAGE_OBJECT_PATH, + "add_filter"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID, + MESSAGE_OBJECT_PATH, + "remove_filter"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU, + MESSAGE_OBJECT_PATH, + "extend_context_menu"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "up"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "history_back"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "history_forward"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "refresh"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION, + MESSAGE_OBJECT_PATH, + "set_show_hidden"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION, + MESSAGE_OBJECT_PATH, + "set_show_binary"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "show_bookmarks"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_MESSAGE, + MESSAGE_OBJECT_PATH, + "show_files"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW, + MESSAGE_OBJECT_PATH, + "get_view"); + + BUS_CONNECT (bus, get_root, data); + BUS_CONNECT (bus, set_root, data); + BUS_CONNECT (bus, set_emblem, data); + BUS_CONNECT (bus, set_markup, data); + BUS_CONNECT (bus, add_filter, window); + BUS_CONNECT (bus, remove_filter, data); + BUS_CONNECT (bus, extend_context_menu, window); + + 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) +{ + guint flags = 0; + + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, + 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); + } +} + +static void +store_before_row_deleted (GeditFileBrowserStore *store, + GtkTreePath *path, + MessageCacheData *data) +{ + GtkTreeIter iter; + 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_FLAGS, &flags, + -1); + + if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags)) + { + WindowData *wdata = get_window_data (data->window); + gchar *id; + + set_item_message (wdata, &iter, path, data->message); + + /* Must get the ID before the plugin can modify it */ + g_object_get (data->message, "id", &id, NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + + g_hash_table_remove (wdata->row_tracking, id); + g_free (id); + } +} + +static void +store_virtual_root_changed (GeditFileBrowserStore *store, + GParamSpec *spec, + MessageCacheData *data) +{ + WindowData *wdata = get_window_data (data->window); + GFile *vroot; + + vroot = gedit_file_browser_store_get_virtual_root (store); + + if (!vroot) + { + return; + } + + g_object_set (data->message, + "location", vroot, + NULL); + + gedit_message_bus_send_message_sync (wdata->bus, data->message); + + g_object_unref (vroot); +} + +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; + + GeditMessage *message; + WindowData *data; + + /* Register signals */ + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "root_changed"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "begin_loading"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "end_loading"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "inserted"); + + gedit_message_bus_register (bus, + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + MESSAGE_OBJECT_PATH, + "deleted"); + + store = gedit_file_browser_widget_get_browser_store (widget); + + message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "inserted", + 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 = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "deleted", + NULL); + + data->before_row_deleted_id = + g_signal_connect_data (store, + "before-row-deleted", + G_CALLBACK (store_before_row_deleted), + message_cache_data_new (window, message), + (GClosureNotify)message_cache_data_free, + 0); + + message = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "root_changed", + 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 = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "begin_loading", + 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 = g_object_new (GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION, + "object-path", MESSAGE_OBJECT_PATH, + "method", "end_loading", + 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, + const gchar *object_path, + const gchar *method, + GeditWindow *window) +{ + gchar *identifier; + FilterData *data; + WindowData *wdata; + + wdata = get_window_data (window); + + identifier = gedit_message_type_identifier (object_path, method); + + 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->before_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); + WindowData *data = get_window_data (window); + + cleanup_signals (window); + + BUS_DISCONNECT (bus, get_root, data); + BUS_DISCONNECT (bus, set_root, data); + BUS_DISCONNECT (bus, set_emblem, data); + BUS_DISCONNECT (bus, set_markup, data); + BUS_DISCONNECT (bus, add_filter, window); + BUS_DISCONNECT (bus, remove_filter, data); + + BUS_DISCONNECT (bus, up, data); + BUS_DISCONNECT (bus, history_back, data); + BUS_DISCONNECT (bus, history_forward, data); + + BUS_DISCONNECT (bus, refresh, data); + + BUS_DISCONNECT (bus, set_show_hidden, data); + BUS_DISCONNECT (bus, set_show_binary, data); + + BUS_DISCONNECT (bus, show_bookmarks, data); + BUS_DISCONNECT (bus, show_files, data); + + BUS_DISCONNECT (bus, get_view, data); + + gedit_message_bus_unregister_all (bus, MESSAGE_OBJECT_PATH); + + window_data_free (window); +} +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-messages.h b/plugins/filebrowser/gedit-file-browser-messages.h new file mode 100644 index 0000000..9fcb316 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-messages.h @@ -0,0 +1,33 @@ +/* + * 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, see . + */ + +#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:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-plugin.c b/plugins/filebrowser/gedit-file-browser-plugin.c new file mode 100644 index 0000000..2e7b79f --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-plugin.c @@ -0,0 +1,943 @@ +/* + * 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, see . + */ + +#include "config.h" + +#include +#include +#include +#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 FILEBROWSER_BASE_SETTINGS "org.gnome.gedit.plugins.filebrowser" +#define FILEBROWSER_TREE_VIEW "tree-view" +#define FILEBROWSER_ROOT "root" +#define FILEBROWSER_VIRTUAL_ROOT "virtual-root" +#define FILEBROWSER_ENABLE_REMOTE "enable-remote" +#define FILEBROWSER_OPEN_AT_FIRST_DOC "open-at-first-doc" +#define FILEBROWSER_FILTER_MODE "filter-mode" +#define FILEBROWSER_FILTER_PATTERN "filter-pattern" +#define FILEBROWSER_BINARY_PATTERNS "binary-patterns" + +#define NAUTILUS_BASE_SETTINGS "org.gnome.nautilus.preferences" +#define NAUTILUS_FALLBACK_SETTINGS "org.gnome.gedit.plugins.filebrowser.nautilus" +#define NAUTILUS_CLICK_POLICY_KEY "click-policy" + +#define TERMINAL_BASE_SETTINGS "org.gnome.desktop.default-applications.terminal" +#define TERMINAL_EXEC_KEY "exec" + +struct _GeditFileBrowserPluginPrivate +{ + GSettings *settings; + GSettings *nautilus_settings; + GSettings *terminal_settings; + + GeditWindow *window; + + GeditFileBrowserWidget *tree_widget; + gboolean auto_root; + gulong end_loading_handle; + + guint click_policy_handle; +}; + +enum +{ + PROP_0, + PROP_WINDOW +}; + +static void gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface); + +static void on_location_activated_cb (GeditFileBrowserWidget *widget, + GFile *location, + GeditWindow *window); +static void on_error_cb (GeditFileBrowserWidget *widget, + guint code, + gchar const *message, + GeditFileBrowserPlugin *plugin); +static void on_model_set_cb (GeditFileBrowserView *widget, + GParamSpec *param, + GeditFileBrowserPlugin *plugin); +static void on_virtual_root_changed_cb (GeditFileBrowserStore *model, + GParamSpec *param, + GeditFileBrowserPlugin *plugin); +static void on_rename_cb (GeditFileBrowserStore *model, + GFile *oldfile, + GFile *newfile, + GeditWindow *window); +static void on_tab_added_cb (GeditWindow *window, + GeditTab *tab, + GeditFileBrowserPlugin *plugin); +static gboolean on_confirm_delete_cb (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GList *rows, + GeditFileBrowserPlugin *plugin); +static gboolean on_confirm_no_trash_cb (GeditFileBrowserWidget *widget, + GList *files, + GeditWindow *window); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserPlugin, + gedit_file_browser_plugin, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserPlugin) + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_WINDOW_ACTIVATABLE, + gedit_window_activatable_iface_init) \ + \ + gedit_file_browser_enum_and_flag_register_type (type_module); \ + _gedit_file_bookmarks_store_register_type (type_module); \ + _gedit_file_browser_store_register_type (type_module); \ + _gedit_file_browser_view_register_type (type_module); \ + _gedit_file_browser_widget_register_type (type_module); \ +) + +static GSettings * +create_nautilus_gsettings (void) +{ + GSettings *nautilus_settings = NULL; + + if (tepl_utils_can_use_gsettings_schema (NAUTILUS_BASE_SETTINGS)) + { + nautilus_settings = g_settings_new (NAUTILUS_BASE_SETTINGS); + + if (tepl_utils_can_use_gsettings_key (nautilus_settings, NAUTILUS_CLICK_POLICY_KEY)) + { + return nautilus_settings; + } + } + + g_clear_object (&nautilus_settings); + return g_settings_new (NAUTILUS_FALLBACK_SETTINGS); +} + +static void +gedit_file_browser_plugin_init (GeditFileBrowserPlugin *plugin) +{ + plugin->priv = gedit_file_browser_plugin_get_instance_private (plugin); + + plugin->priv->settings = g_settings_new (FILEBROWSER_BASE_SETTINGS); + plugin->priv->terminal_settings = g_settings_new (TERMINAL_BASE_SETTINGS); + plugin->priv->nautilus_settings = create_nautilus_gsettings (); +} + +static void +gedit_file_browser_plugin_dispose (GObject *object) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object); + + g_clear_object (&plugin->priv->settings); + g_clear_object (&plugin->priv->nautilus_settings); + g_clear_object (&plugin->priv->terminal_settings); + g_clear_object (&plugin->priv->window); + + G_OBJECT_CLASS (gedit_file_browser_plugin_parent_class)->dispose (object); +} + +static void +gedit_file_browser_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + plugin->priv->window = GEDIT_WINDOW (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_file_browser_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (object); + + switch (prop_id) + { + case PROP_WINDOW: + g_value_set_object (value, plugin->priv->window); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_end_loading_cb (GeditFileBrowserStore *store, + GtkTreeIter *iter, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + + /* Disconnect the signal */ + g_signal_handler_disconnect (store, priv->end_loading_handle); + priv->end_loading_handle = 0; + priv->auto_root = FALSE; +} + +static void +prepare_auto_root (GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GeditFileBrowserStore *store; + + priv->auto_root = TRUE; + + store = gedit_file_browser_widget_get_browser_store (priv->tree_widget); + + if (priv->end_loading_handle != 0) + { + g_signal_handler_disconnect (store, priv->end_loading_handle); + priv->end_loading_handle = 0; + } + + priv->end_loading_handle = g_signal_connect (store, + "end-loading", + G_CALLBACK (on_end_loading_cb), + plugin); +} + +static void +restore_default_location (GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gchar *root; + gchar *virtual_root; + gboolean bookmarks; + gboolean remote; + + bookmarks = !g_settings_get_boolean (priv->settings, + FILEBROWSER_TREE_VIEW); + + if (bookmarks) + { + gedit_file_browser_widget_show_bookmarks (priv->tree_widget); + return; + } + + root = g_settings_get_string (priv->settings, + FILEBROWSER_ROOT); + virtual_root = g_settings_get_string (priv->settings, + FILEBROWSER_VIRTUAL_ROOT); + + remote = g_settings_get_boolean (priv->settings, + FILEBROWSER_ENABLE_REMOTE); + + if (root != NULL && *root != '\0') + { + GFile *rootfile; + GFile *vrootfile; + + rootfile = g_file_new_for_uri (root); + vrootfile = g_file_new_for_uri (virtual_root); + + if (remote || g_file_is_native (rootfile)) + { + if (virtual_root != NULL && *virtual_root != '\0') + { + prepare_auto_root (plugin); + gedit_file_browser_widget_set_root_and_virtual_root (priv->tree_widget, + rootfile, + vrootfile); + } + else + { + prepare_auto_root (plugin); + gedit_file_browser_widget_set_root (priv->tree_widget, + rootfile, + TRUE); + } + } + + g_object_unref (rootfile); + g_object_unref (vrootfile); + } + + g_free (root); + g_free (virtual_root); +} + +static void +on_click_policy_changed (GSettings *settings, + const gchar *key, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GeditFileBrowserViewClickPolicy policy; + GeditFileBrowserView *view; + + policy = g_settings_get_enum (settings, key); + + view = gedit_file_browser_widget_get_browser_view (priv->tree_widget); + gedit_file_browser_view_set_click_policy (view, policy); +} + +static void +install_nautilus_prefs (GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GeditFileBrowserViewClickPolicy policy; + GeditFileBrowserView *view; + + /* Get click_policy */ + policy = g_settings_get_enum (priv->nautilus_settings, + NAUTILUS_CLICK_POLICY_KEY); + + view = gedit_file_browser_widget_get_browser_view (priv->tree_widget); + gedit_file_browser_view_set_click_policy (view, policy); + + priv->click_policy_handle = + g_signal_connect (priv->nautilus_settings, + "changed::" NAUTILUS_CLICK_POLICY_KEY, + G_CALLBACK (on_click_policy_changed), + plugin); +} + +static void +set_root_from_doc (GeditFileBrowserPlugin *plugin, + GeditDocument *doc) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GtkSourceFile *file; + GFile *location; + GFile *parent; + + if (doc == NULL) + { + return; + } + + file = gedit_document_get_file (doc); + location = gtk_source_file_get_location (file); + if (location == NULL) + { + return; + } + + parent = g_file_get_parent (location); + + if (parent != NULL) + { + gedit_file_browser_widget_set_root (priv->tree_widget, + parent, + TRUE); + + g_object_unref (parent); + } +} + +static void +set_active_root (GeditFileBrowserWidget *widget, + GeditFileBrowserPlugin *plugin) +{ + set_root_from_doc (plugin, + gedit_window_get_active_document (plugin->priv->window)); +} + +static gchar * +get_terminal (GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gchar *terminal; + + terminal = g_settings_get_string (priv->terminal_settings, + TERMINAL_EXEC_KEY); + + 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 +open_in_terminal (GeditFileBrowserWidget *widget, + GFile *location, + GeditFileBrowserPlugin *plugin) +{ + if (location) + { + gchar *terminal; + gchar *local; + gchar *argv[2]; + + terminal = get_terminal (plugin); + local = g_file_get_path (location); + + 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 (local); + } +} + +static void +gedit_file_browser_plugin_update_state (GeditWindowActivatable *activatable) +{ + GeditFileBrowserPluginPrivate *priv = GEDIT_FILE_BROWSER_PLUGIN (activatable)->priv; + GeditDocument *doc; + GFile *location = NULL; + + doc = gedit_window_get_active_document (priv->window); + + if (doc != NULL) + { + TeplFile *file; + + file = tepl_buffer_get_file (TEPL_BUFFER (doc)); + location = tepl_file_get_location (file); + } + + gedit_file_browser_widget_set_active_root_enabled (priv->tree_widget, location != NULL); +} + +static void +gedit_file_browser_plugin_activate (GeditWindowActivatable *activatable) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (activatable); + GeditFileBrowserPluginPrivate *priv; + GtkWidget *panel; + GeditFileBrowserStore *store; + + priv = plugin->priv; + + priv->tree_widget = GEDIT_FILE_BROWSER_WIDGET (gedit_file_browser_widget_new ()); + + g_signal_connect (priv->tree_widget, + "location-activated", + G_CALLBACK (on_location_activated_cb), priv->window); + + g_signal_connect (priv->tree_widget, + "error", G_CALLBACK (on_error_cb), plugin); + + g_signal_connect (priv->tree_widget, + "confirm-delete", + G_CALLBACK (on_confirm_delete_cb), + plugin); + + g_signal_connect (priv->tree_widget, + "confirm-no-trash", + G_CALLBACK (on_confirm_no_trash_cb), + priv->window); + + g_signal_connect (priv->tree_widget, + "open-in-terminal", + G_CALLBACK (open_in_terminal), + plugin); + + g_signal_connect (priv->tree_widget, + "set-active-root", + G_CALLBACK (set_active_root), + plugin); + + g_settings_bind (priv->settings, + FILEBROWSER_FILTER_PATTERN, + priv->tree_widget, + FILEBROWSER_FILTER_PATTERN, + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET); + + panel = gedit_window_get_side_panel (priv->window); + + gtk_stack_add_titled (GTK_STACK (panel), + GTK_WIDGET (priv->tree_widget), + "GeditFileBrowserPanel", + _("File Browser")); + + gtk_widget_show (GTK_WIDGET (priv->tree_widget)); + + /* Install nautilus preferences */ + install_nautilus_prefs (plugin); + + /* Connect signals to store the last visited location */ + g_signal_connect (gedit_file_browser_widget_get_browser_view (priv->tree_widget), + "notify::model", + G_CALLBACK (on_model_set_cb), + plugin); + + store = gedit_file_browser_widget_get_browser_store (priv->tree_widget); + + g_settings_bind (priv->settings, + FILEBROWSER_FILTER_MODE, + store, + FILEBROWSER_FILTER_MODE, + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET); + + g_settings_bind (priv->settings, + FILEBROWSER_BINARY_PATTERNS, + store, + FILEBROWSER_BINARY_PATTERNS, + G_SETTINGS_BIND_GET | G_SETTINGS_BIND_SET); + + g_signal_connect (store, + "notify::virtual-root", + G_CALLBACK (on_virtual_root_changed_cb), + plugin); + + g_signal_connect (store, + "rename", + G_CALLBACK (on_rename_cb), + priv->window); + + g_signal_connect (priv->window, + "tab-added", + G_CALLBACK (on_tab_added_cb), + plugin); + + /* Register messages on the bus */ + gedit_file_browser_messages_register (priv->window, priv->tree_widget); + + gedit_file_browser_plugin_update_state (activatable); +} + +static void +gedit_file_browser_plugin_deactivate (GeditWindowActivatable *activatable) +{ + GeditFileBrowserPlugin *plugin = GEDIT_FILE_BROWSER_PLUGIN (activatable); + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GtkWidget *panel; + + + /* Unregister messages from the bus */ + gedit_file_browser_messages_unregister (priv->window); + + /* Disconnect signals */ + g_signal_handlers_disconnect_by_func (priv->window, + G_CALLBACK (on_tab_added_cb), + plugin); + + if (priv->click_policy_handle) + { + g_signal_handler_disconnect (priv->nautilus_settings, + priv->click_policy_handle); + } + + panel = gedit_window_get_side_panel (priv->window); + gtk_container_remove (GTK_CONTAINER (panel), GTK_WIDGET (priv->tree_widget)); +} + +static void +gedit_file_browser_plugin_class_init (GeditFileBrowserPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_file_browser_plugin_dispose; + object_class->set_property = gedit_file_browser_plugin_set_property; + object_class->get_property = gedit_file_browser_plugin_get_property; + + g_object_class_override_property (object_class, PROP_WINDOW, "window"); +} + +static void +gedit_window_activatable_iface_init (GeditWindowActivatableInterface *iface) +{ + iface->activate = gedit_file_browser_plugin_activate; + iface->deactivate = gedit_file_browser_plugin_deactivate; + iface->update_state = gedit_file_browser_plugin_update_state; +} + +static void +gedit_file_browser_plugin_class_finalize (GeditFileBrowserPluginClass *klass) +{ +} + +/* Callbacks */ +static void +on_location_activated_cb (GeditFileBrowserWidget *tree_widget, + GFile *location, + GeditWindow *window) +{ + gedit_commands_load_location (window, location, NULL, 0, 0); +} + +static void +on_error_cb (GeditFileBrowserWidget *tree_widget, + guint code, + gchar const *message, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gchar *title; + GtkWidget *dlg; + + /* Do not show the error when the root has been set automatically */ + if (priv->auto_root && (code == GEDIT_FILE_BROWSER_ERROR_SET_ROOT || + code == GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY)) + { + /* Show bookmarks */ + gedit_file_browser_widget_show_bookmarks (priv->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 (priv->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 *param, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (priv->tree_widget))); + + if (model == NULL) + return; + + g_settings_set_boolean (priv->settings, + FILEBROWSER_TREE_VIEW, + GEDIT_IS_FILE_BROWSER_STORE (model)); +} + +static void +on_rename_cb (GeditFileBrowserStore *store, + GFile *oldfile, + GFile *newfile, + GeditWindow *window) +{ + GList *documents; + GList *item; + + /* Find all documents and set its uri to newuri where it matches olduri */ + documents = gedit_app_get_documents (GEDIT_APP (g_application_get_default ())); + + for (item = documents; item; item = item->next) + { + GeditDocument *doc; + GtkSourceFile *source_file; + GFile *docfile; + + doc = GEDIT_DOCUMENT (item->data); + source_file = gedit_document_get_file (doc); + docfile = gtk_source_file_get_location (source_file); + + if (docfile == NULL) + { + continue; + } + + if (g_file_equal (docfile, oldfile)) + { + gtk_source_file_set_location (source_file, newfile); + } + else + { + gchar *relative; + + relative = g_file_get_relative_path (oldfile, docfile); + + if (relative != NULL) + { + /* Relative now contains the part in docfile without + the prefix oldfile */ + + docfile = g_file_get_child (newfile, relative); + + gtk_source_file_set_location (source_file, docfile); + + g_object_unref (docfile); + } + + g_free (relative); + } + } + + g_list_free (documents); +} + +static void +on_virtual_root_changed_cb (GeditFileBrowserStore *store, + GParamSpec *param, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + GFile *root; + GFile *virtual_root; + gchar *uri_root = NULL; + + root = gedit_file_browser_store_get_root (store); + + if (!root) + { + return; + } + else + { + uri_root = g_file_get_uri (root); + g_object_unref (root); + } + + g_settings_set_string (priv->settings, + FILEBROWSER_ROOT, + uri_root); + + virtual_root = gedit_file_browser_store_get_virtual_root (store); + + if (!virtual_root) + { + /* Set virtual to same as root then */ + g_settings_set_string (priv->settings, + FILEBROWSER_VIRTUAL_ROOT, + uri_root); + } + else + { + gchar *uri_vroot; + + uri_vroot = g_file_get_uri (virtual_root); + + g_settings_set_string (priv->settings, + FILEBROWSER_VIRTUAL_ROOT, + uri_vroot); + g_free (uri_vroot); + g_object_unref (virtual_root); + } + + g_signal_handlers_disconnect_by_func (priv->window, + G_CALLBACK (on_tab_added_cb), + plugin); + g_free (uri_root); +} + +static void +on_tab_added_cb (GeditWindow *window, + GeditTab *tab, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gboolean open; + gboolean load_default = TRUE; + + open = g_settings_get_boolean (priv->settings, + FILEBROWSER_OPEN_AT_FIRST_DOC); + + if (open) + { + GeditDocument *doc; + GtkSourceFile *file; + GFile *location; + + doc = gedit_tab_get_document (tab); + file = gedit_document_get_file (doc); + location = gtk_source_file_get_location (file); + + if (location != NULL) + { + if (g_file_has_uri_scheme (location, "file")) + { + prepare_auto_root (plugin); + set_root_from_doc (plugin, doc); + load_default = FALSE; + } + } + } + + if (load_default) + restore_default_location (plugin); + + /* Disconnect this signal, it's only called once */ + g_signal_handlers_disconnect_by_func (window, + G_CALLBACK (on_tab_added_cb), + plugin); +} + +static gchar * +get_filename_from_path (GtkTreeModel *model, + GtkTreePath *path) +{ + GtkTreeIter iter; + GFile *location; + gchar *ret = NULL; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + { + return NULL; + } + + gtk_tree_model_get (model, &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (location) + { + ret = gedit_file_browser_utils_file_basename (location); + g_object_unref (location); + } + + return ret; +} + +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, + _("_Delete")); + g_free (secondary); + + return result; +} + +static gboolean +on_confirm_delete_cb (GeditFileBrowserWidget *widget, + GeditFileBrowserStore *store, + GList *paths, + GeditFileBrowserPlugin *plugin) +{ + GeditFileBrowserPluginPrivate *priv = plugin->priv; + gchar *normal; + gchar *message; + gchar *secondary; + gboolean result; + + 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 (priv->window, + GTK_MESSAGE_QUESTION, + message, + secondary, + _("_Delete")); + + g_free (message); + + return result; +} + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_file_browser_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_WINDOW_ACTIVATABLE, + GEDIT_TYPE_FILE_BROWSER_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-plugin.h b/plugins/filebrowser/gedit-file-browser-plugin.h new file mode 100644 index 0000000..27fe706 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-plugin.h @@ -0,0 +1,70 @@ +/* + * 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, see . + */ + +#ifndef GEDIT_FILE_BROWSER_PLUGIN_H +#define GEDIT_FILE_BROWSER_PLUGIN_H + +#include +#include +#include +#include + +G_BEGIN_DECLS +/* + * Type checking and casting macros + */ +#define GEDIT_TYPE_FILE_BROWSER_PLUGIN (gedit_file_browser_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 +{ + GObject parent_instance; + + /* < private > */ + GeditFileBrowserPluginPrivate *priv; +}; + +struct _GeditFileBrowserPluginClass +{ + GObjectClass parent_class; +}; + +/* + * Public methods + */ +GType gedit_file_browser_plugin_get_type (void) G_GNUC_CONST; + +/* All the plugins must implement this function */ +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_PLUGIN_H */ + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-store.c b/plugins/filebrowser/gedit-file-browser-store.c new file mode 100644 index 0000000..ef19b55 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.c @@ -0,0 +1,3672 @@ +/* + * 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, see . + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "gedit-file-browser-store.h" +#include "gedit-file-browser-enum-types.h" +#include "gedit-file-browser-error.h" +#include "gedit-file-browser-utils.h" + +#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; + GFile *virtual_root; + GMountOperation *operation; + GCancellable *cancellable; +} MountInfo; + +struct _FileBrowserNode +{ + GFile *file; + guint flags; + gchar *icon_name; + gchar *name; + gchar *markup; + + GdkPixbuf *icon; + GdkPixbuf *emblem; + + FileBrowserNode *parent; + gint pos; + gboolean inserted; +}; + +struct _FileBrowserNodeDir +{ + FileBrowserNode node; + GSList *children; + + 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; + + gchar **binary_patterns; + GPtrArray *binary_pattern_specs; + + 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); + +static void delete_files (AsyncData *data); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserStore, gedit_file_browser_store, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserStore) + G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_TREE_MODEL, + gedit_file_browser_store_iface_init) + G_IMPLEMENT_INTERFACE_DYNAMIC (GTK_TYPE_TREE_DRAG_SOURCE, + gedit_file_browser_store_drag_source_init)) + +/* Properties */ +enum { + PROP_0, + + PROP_ROOT, + PROP_VIRTUAL_ROOT, + PROP_FILTER_MODE, + PROP_BINARY_PATTERNS +}; + +/* Signals */ +enum +{ + BEGIN_LOADING, + END_LOADING, + ERROR, + NO_TRASH, + RENAME, + BEGIN_REFRESH, + END_REFRESH, + UNLOAD, + BEFORE_ROW_DELETED, + 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); + + /* Free all the nodes */ + file_browser_node_free (obj, obj->priv->root); + + if (obj->priv->binary_patterns != NULL) + { + g_strfreev (obj->priv->binary_patterns); + g_ptr_array_unref (obj->priv->binary_pattern_specs); + } + + /* Cancel any asynchronous operations */ + for (GSList *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) +{ + if (node == NULL) + g_value_set_object (value, NULL); + else + g_value_set_object (value, node->file); +} + +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; + case PROP_BINARY_PATTERNS: + g_value_set_boxed (value, obj->priv->binary_patterns); + 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_ROOT: + gedit_file_browser_store_set_root (obj, G_FILE (g_value_get_object (value))); + break; + case PROP_FILTER_MODE: + gedit_file_browser_store_set_filter_mode (obj, g_value_get_flags (value)); + break; + case PROP_BINARY_PATTERNS: + gedit_file_browser_store_set_binary_patterns (obj, g_value_get_boxed (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_object ("root", + "Root", + "The root location", + G_TYPE_FILE, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_VIRTUAL_ROOT, + g_param_spec_object ("virtual-root", + "Virtual Root", + "The virtual root location", + G_TYPE_FILE, + 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)); + + g_object_class_install_property (object_class, PROP_BINARY_PATTERNS, + g_param_spec_boxed ("binary-patterns", + "Binary Patterns", + "The binary patterns", + G_TYPE_STRV, + 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, NULL, + 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, NULL, + 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, NULL, + 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, NULL, + 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, NULL, + G_TYPE_NONE, 2, G_TYPE_FILE, G_TYPE_FILE); + 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, NULL, + 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, NULL, + 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, NULL, + G_TYPE_NONE, 1, G_TYPE_FILE); + model_signals[BEFORE_ROW_DELETED] = + g_signal_new ("before-row-deleted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserStoreClass, before_row_deleted), + NULL, NULL, NULL, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_PATH | G_SIGNAL_TYPE_STATIC_SCOPE); +} + +static void +gedit_file_browser_store_class_finalize (GeditFileBrowserStoreClass *klass) +{ +} + +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_instance_private (obj); + + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION] = G_TYPE_FILE; + obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP] = 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_ICON_NAME] = 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_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) +{ + GeditFileBrowserStore *model; + FileBrowserNode *node; + gint *indices, depth; + + 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 (guint i = 0; i < depth; ++i) + { + GSList *item; + gint num = 0; + + if (node == NULL) + return FALSE; + + if (!NODE_IS_DIR (node)) + return FALSE; + + for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) + { + FileBrowserNode *child = (FileBrowserNode *)(item->data); + + if (model_node_inserted (model, child)) + { + if (num == indices[i]) + 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 = gtk_tree_path_new (); + gint num = 0; + + while (node != model->priv->virtual_root) + { + if (node->parent == NULL) + { + gtk_tree_path_free (path); + return NULL; + } + + num = 0; + + for (GSList *item = FILE_BROWSER_NODE_DIR (node->parent)->children; item; item = item->next) + { + FileBrowserNode *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_LOCATION: + set_gvalue_from_node (value, node); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP: + g_value_set_string (value, node->markup); + 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_ICON_NAME: + g_value_set_string (value, node->icon_name); + break; + case GEDIT_FILE_BROWSER_STORE_COLUMN_NAME: + g_value_set_string (value, node->name); + 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 *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 (GSList *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; + + 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 (GSList *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) +{ + if (!NODE_IS_DIR (node)) + return FALSE; + + for (GSList *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; + 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 (GSList *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; + 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 (GSList *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; + GFile *location; + 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_LOCATION, &location, + -1); + + g_assert (location); + + uris[0] = g_file_get_uri (location); + ret = gtk_selection_data_set_uris (selection_data, uris); + + g_free (uris[0]); + g_object_unref (location); + + 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; + return; + } + + if (FILTER_BINARY (model->priv->filter_mode) && !NODE_IS_DIR (node)) + { + if (!NODE_IS_TEXT (node)) + { + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + return; + } + else if (model->priv->binary_patterns != NULL) + { + gssize name_length = strlen (node->name); + gchar *name_reversed = g_utf8_strreverse (node->name, name_length); + + for (guint i = 0; i < model->priv->binary_pattern_specs->len; ++i) + { + GPatternSpec *spec = g_ptr_array_index (model->priv->binary_pattern_specs, i); + + if (g_pattern_spec_match (spec, name_length, node->name, name_reversed)) + { + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED; + g_free (name_reversed); + return; + } + } + + g_free (name_reversed); + } + } + + 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 = g_utf8_collate_key_for_filename (node1->name, -1); + gchar *k2 = g_utf8_collate_key_for_filename (node2->name, -1); + gint result = strcmp (k1, k2); + + g_free (k1); + g_free (k2); + + return result; + } +} + +static gint +model_sort_default (FileBrowserNode *node1, + FileBrowserNode *node2) +{ + gint f1 = NODE_IS_DUMMY (node1); + gint 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; + + return collate_nodes (node1, node2); +} + +static void +model_resort_node (GeditFileBrowserStore *model, + FileBrowserNode *node) +{ + FileBrowserNodeDir *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 + { + GtkTreeIter iter; + GtkTreePath *path; + gint *neworder; + gint pos = 0; + + /* Store current positions */ + for (GSList *item = dir->children; item; item = item->next) + { + FileBrowserNode *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 (GSList *item = dir->children; item; item = item->next) + { + FileBrowserNode *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, + FileBrowserNode *node, + const GtkTreePath *path) +{ + gboolean hidden; + GtkTreePath *copy; + + /* We should always be called when the row is still inserted */ + g_return_if_fail (node->inserted == TRUE || NODE_IS_DUMMY (node)); + + hidden = FILE_IS_HIDDEN (node->flags); + node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + /* Create temporary copies of the path as the signals may alter it */ + + copy = gtk_tree_path_copy (path); + g_signal_emit (model, model_signals[BEFORE_ROW_DELETED], 0, copy); + gtk_tree_path_free (copy); + + node->inserted = FALSE; + + if (hidden) + node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN; + + copy = gtk_tree_path_copy (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) + { + row_deleted (model, node, *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); + g_free (node->markup); + + if (node->file) + node->name = gedit_file_browser_utils_file_basename (node->file); + else + node->name = NULL; + + if (node->name) + node->markup = g_markup_escape_text (node->name, -1); + else + node->markup = 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) +{ + if (node == NULL || !NODE_IS_DIR (node)) + return; + + for (GSList *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) +{ + if (node == NULL) + return; + + if (NODE_IS_DIR (node)) + { + FileBrowserNodeDir *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 (node->file) + { + g_signal_emit (model, model_signals[UNLOAD], 0, node->file); + g_object_unref (node->file); + } + + if (node->icon) + g_object_unref (node->icon); + + if (node->emblem) + g_object_unref (node->emblem); + + g_free (node->icon_name); + g_free (node->name); + g_free (node->markup); + + 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; + + 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 (GSList *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) + row_deleted (model, node, path); + + if (free_path) + gtk_tree_path_free (path); + + parent = node->parent; + + /* Remove the node from the parents children list */ + if (free_nodes && 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 = 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) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (model->priv->virtual_root); + + if (dir->children != NULL) + { + FileBrowserNode *dummy = (FileBrowserNode *)(dir->children->data); + + if (NODE_IS_DUMMY (dummy) && model_node_visibility (model, dummy)) + { + path = gtk_tree_path_new_first (); + row_deleted (model, dummy, 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, FALSE); + } + + /* Fallback to the same icon as the file browser */ + if (!icon) + icon = gedit_file_browser_utils_pixbuf_from_theme ("text-x-generic", 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->markup = g_markup_escape_text (dummy->name, -1); + + 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)) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node); + FileBrowserNode *dummy; + GtkTreeIter iter; + GtkTreePath *path; + guint flags; + + 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; + + row_deleted (model, dummy, path); + gtk_tree_path_free (path); + } + } +} + +static void +insert_node_sorted (GeditFileBrowserStore *model, + FileBrowserNode *child, + FileBrowserNode *parent) +{ + FileBrowserNodeDir *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)) + { + GtkTreePath *path = gedit_file_browser_store_get_path_real (model, child); + GtkTreeIter iter; + + iter.user_data = 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) +{ + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + GSList *sorted_children = g_slist_sort (children, (GCompareFunc)model->priv->sort_func); + GSList *child = sorted_children; + GSList *prev = NULL; + GSList *l = dir->children; + + 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 gboolean +content_type_is_text (gchar const *content_type) +{ +#ifdef G_OS_WIN32 + gchar *mime; + gboolean ret; +#endif + + if (!content_type || g_content_type_is_unknown (content_type)) + return TRUE; + +#ifndef G_OS_WIN32 + return (g_content_type_is_a (content_type, "text/plain") || + g_content_type_equals (content_type, "application/x-zerosize")); +#else + if (g_content_type_is_a (content_type, "text")) + return TRUE; + + /* This covers a rare case in which on Windows the PerceivedType is + not set to "text" but the Content Type is set to text/plain */ + mime = g_content_type_get_mime_type (content_type); + ret = g_strcmp0 (mime, "text/plain") == 0; + g_free (mime); + + return ret; +#endif +} + +static void +file_browser_node_set_from_info (GeditFileBrowserStore *model, + FileBrowserNode *node, + GFileInfo *info, + gboolean isadded) +{ + gchar const *content; + 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; + } + + if (g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info)) + 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_type_is_text (content)) + 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) +{ + for (GSList *item = children; item; item = item->next) + { + FileBrowserNode *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) +{ + GSList *nodes = NULL; + + for (GList *item = files; item; item = item->next) + { + GFileInfo *info = G_FILE_INFO (item->data); + GFileType type = g_file_info_get_file_type (info); + gchar const *name; + GFile *file; + FileBrowserNode *node; + + /* 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)) + { + g_object_unref (info); + continue; + } + + file = g_file_get_child (parent->file, name); + if (!(node = node_list_contains_file (original_children, file))) + { + if (type == 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); + + node->icon_name = g_strdup ("folder-symbolic"); + + model_add_node (model, node, parent); + } + + return node; +} + +static void +on_directory_monitor_event (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + FileBrowserNode *parent) +{ + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent); + FileBrowserNode *node; + + 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_slice_free (AsyncNode, async); +} + +static void +model_iterate_next_files_cb (GFileEnumerator *enumerator, + GAsyncResult *result, + AsyncNode *async) +{ + GError *error = NULL; + GList *files = g_file_enumerator_next_files_finish (enumerator, result, &error); + FileBrowserNodeDir *dir = async->dir; + FileBrowserNode *parent = (FileBrowserNode *)dir; + + if (files == NULL) + { + g_file_enumerator_close (enumerator, NULL, NULL); + g_object_unref (enumerator); + 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.gnome.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); + g_object_unref (enumerator); + 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; + } + + if (!(enumerator = g_file_enumerate_children_finish (file, result, &error))) + { + /* 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); + + dir->cancellable = g_cancellable_new (); + + async = g_slice_new (AsyncNode); + 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 *prev = node; + FileBrowserNode *next = prev->parent; + FileBrowserNode *check; + FileBrowserNodeDir *dir; + GSList *copy; + GtkTreePath *empty = NULL; + + /* 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 (GSList *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 (GSList *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; + 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 (GList *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; + + if (!NODE_IS_DIR (parent)) + return NULL; + + dir = FILE_BROWSER_NODE_DIR (parent); + + for (GSList *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, + GFile *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_location + (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); + + if (mount_info->virtual_root) + g_object_unref (mount_info->virtual_root); + + g_slice_free (MountInfo, mount_info); +} + +static GeditFileBrowserStoreResult +model_mount_root (GeditFileBrowserStore *model, + GFile *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_slice_new (MountInfo); + mount_info->model = model; + mount_info->virtual_root = g_file_dup (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 (GFile *root) +{ + return GEDIT_FILE_BROWSER_STORE (g_object_new (GEDIT_TYPE_FILE_BROWSER_STORE, + "root", root, + NULL)); +} + +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 (iter != NULL); + g_return_if_fail (iter->user_data != NULL); + + node = (FileBrowserNode *)(iter->user_data); + + if (column == GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP) + { + g_return_if_fail (G_VALUE_HOLDS_STRING (value)); + + data = g_value_dup_string (value); + + if (!data) + data = g_strdup (node->name); + + g_free (node->markup); + node->markup = data; + } + else if (column == GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM) + { + g_return_if_fail (G_VALUE_HOLDS_OBJECT (value)); + + data = g_value_get_object (value); + + g_return_if_fail (GDK_IS_PIXBUF (data) || data == NULL); + + 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); + } + else + { + g_return_if_fail (column == GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP || + column == GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM); + } + + 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_location (GeditFileBrowserStore *model, + GFile *root) +{ + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE); + + if (root == NULL) + { + gchar *uri = g_file_get_uri (root); + + g_warning ("Invalid uri (%s)", uri); + g_free (uri); + 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, root)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + + /* Check if uri is the root itself */ + if (g_file_equal (model->priv->root->file, root)) + { + /* 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 (root, model->priv->root->file)) + { + gchar *str = g_file_get_parse_name (model->priv->root->file); + gchar *str1 = g_file_get_parse_name (root); + + g_warning ("Virtual root (%s) is not below actual root (%s)", str1, str); + + g_free (str); + g_free (str1); + + return GEDIT_FILE_BROWSER_STORE_RESULT_ERROR; + } + + set_virtual_root_from_file (model, root); + + 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, + GFile *root, + GFile *virtual_root) +{ + 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 && model->priv->root != NULL) + { + equal = g_file_equal (root, model->priv->root->file); + + if (equal && virtual_root == NULL) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + if (virtual_root) + { + if (equal && g_file_equal (virtual_root, model->priv->virtual_root->file)) + return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE; + } + + /* 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 (root != NULL) + { + /* Create the root node */ + node = file_browser_node_dir_new (model, root, NULL); + + 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, + GFile *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); +} + +GFile * +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_dup (model->priv->root->file); +} + +GFile * +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_dup (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; + + 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 (GSList *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); +} + +const gchar * const * +gedit_file_browser_store_get_binary_patterns (GeditFileBrowserStore *model) +{ + return (const gchar * const *)model->priv->binary_patterns; +} + +void +gedit_file_browser_store_set_binary_patterns (GeditFileBrowserStore *model, + const gchar **binary_patterns) +{ + g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model)); + + if (model->priv->binary_patterns != NULL) + { + g_strfreev (model->priv->binary_patterns); + g_ptr_array_unref (model->priv->binary_pattern_specs); + } + + model->priv->binary_patterns = g_strdupv ((gchar **)binary_patterns); + + if (binary_patterns == NULL) + { + model->priv->binary_pattern_specs = NULL; + } + else + { + gssize n_patterns = g_strv_length ((gchar **) binary_patterns); + + model->priv->binary_pattern_specs = g_ptr_array_sized_new (n_patterns); + g_ptr_array_set_free_func (model->priv->binary_pattern_specs, (GDestroyNotify) g_pattern_spec_free); + + for (guint i = 0; binary_patterns[i] != NULL; ++i) + g_ptr_array_add (model->priv->binary_pattern_specs, g_pattern_spec_new (binary_patterns[i])); + } + + model_refilter (model); + + g_object_notify (G_OBJECT (model), "binary-patterns"); +} + +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) +{ + if (!node->file) + return; + + if (reparent) + { + GFile *parent = node->parent->file; + gchar *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)) + { + FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (node); + + for (GSList *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; + 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; + } + + g_signal_emit (model, model_signals[RENAME], 0, previous, node->file); + + g_object_unref (previous); + + 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_free_full (data->files, g_object_unref); + + if (!data->removed) + data->model->priv->async_handles = g_slist_remove (data->model->priv->async_handles, data); + + g_slice_free (AsyncData, 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; +} + +static void +delete_file_finished (GFile *file, + GAsyncResult *res, + AsyncData *data) +{ + GError *error = NULL; + gboolean ok; + + if (data->trash) + ok = g_file_trash_finish (file, res, &error); + else + ok = g_file_delete_finish (file, res, &error); + + if (ok) + { + /* Remove the file from the model */ + FileBrowserNode *node = model_find_node (data->model, NULL, file); + + if (node != NULL) + model_remove_node (data->model, node, NULL, TRUE); + + /* Process the next file */ + data->iter = data->iter->next; + } + else if (!ok && error != NULL) + { + gint code = error->code; + g_error_free (error); + + if (data->trash && code == G_IO_ERROR_NOT_SUPPORTED) + { + /* Trash is not supported on this system. Ask the user + * if he wants to delete completely the files instead. + */ + if (emit_no_trash (data)) + { + /* Changes this into a delete job */ + data->trash = FALSE; + data->iter = data->files; + } + else + { + /* End the job */ + async_data_free (data); + return; + } + } + else if (code == G_IO_ERROR_CANCELLED) + { + /* Job has been cancelled, end the job */ + async_data_free (data); + return; + } + } + + /* Continue the job */ + delete_files (data); +} + +static void +delete_files (AsyncData *data) +{ + GFile *file; + + /* Check if our job is done */ + if (data->iter == NULL) + { + async_data_free (data); + return; + } + + file = G_FILE (data->iter->data); + + if (data->trash) + { + g_file_trash_async (file, + G_PRIORITY_DEFAULT, + data->cancellable, + (GAsyncReadyCallback)delete_file_finished, + data); + } + else + { + g_file_delete_async (file, + G_PRIORITY_DEFAULT, + data->cancellable, + (GAsyncReadyCallback)delete_file_finished, + data); + } +} + +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_slice_new (AsyncData); + + 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); + + delete_files (data); + 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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + 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, _("Untitled 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, _("Untitled Folder")); + + 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; +} + +void +_gedit_file_browser_store_register_type (GTypeModule *type_module) +{ + gedit_file_browser_store_register_type (type_module); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-store.h b/plugins/filebrowser/gedit-file-browser-store.h new file mode 100644 index 0000000..02df0cb --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-store.h @@ -0,0 +1,188 @@ +/* + * 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, see . + */ + +#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_ICON_NAME, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, + + /* Columns not in common with GeditFileBookmarksStore */ + GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, + 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, + GFile *oldfile, + GFile *newfile); + void (* begin_refresh) (GeditFileBrowserStore *model); + void (* end_refresh) (GeditFileBrowserStore *model); + void (* unload) (GeditFileBrowserStore *model, + GFile *location); + void (* before_row_deleted) (GeditFileBrowserStore *model, + GtkTreePath *path); +}; + +GType gedit_file_browser_store_get_type (void) G_GNUC_CONST; + +GeditFileBrowserStore *gedit_file_browser_store_new (GFile *root); +GeditFileBrowserStoreResult gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore *model, + GFile *root, + GFile *virtual_root); +GeditFileBrowserStoreResult gedit_file_browser_store_set_root (GeditFileBrowserStore *model, + GFile *root); +GeditFileBrowserStoreResult gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore *model, + GtkTreeIter *iter); +GeditFileBrowserStoreResult gedit_file_browser_store_set_virtual_root_from_location (GeditFileBrowserStore *model, + GFile *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); +GFile *gedit_file_browser_store_get_root (GeditFileBrowserStore *model); +GFile *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); +const gchar * const *gedit_file_browser_store_get_binary_patterns (GeditFileBrowserStore *model); +void gedit_file_browser_store_set_binary_patterns (GeditFileBrowserStore *model, + const gchar **binary_patterns); +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); + +void _gedit_file_browser_store_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_STORE_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-utils.c b/plugins/filebrowser/gedit-file-browser-utils.c new file mode 100644 index 0000000..dbca26a --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-utils.c @@ -0,0 +1,223 @@ +/* + * gedit-file-bookmarks-utils.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, see . + */ + +#include "config.h" + +#include +#include + +#include "gedit-file-browser-utils.h" + +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); + g_object_unref (info); + + return ret; +} + +GdkPixbuf * +gedit_file_browser_utils_pixbuf_from_file (GFile *file, + GtkIconSize size, + gboolean use_symbolic) +{ + GIcon *icon; + GFileInfo *info; + GdkPixbuf *ret = NULL; + const char *attribute; + + attribute = use_symbolic ? G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON : + G_FILE_ATTRIBUTE_STANDARD_ICON; + + info = g_file_query_info (file, + attribute, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (!info) + return NULL; + + icon = use_symbolic ? g_file_info_get_symbolic_icon (info) : + 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_symbolic_icon_name_from_file (GFile *file) +{ + GFileInfo *info; + GIcon *icon; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + + if (!info) + return NULL; + + if ((icon = g_file_info_get_symbolic_icon (info)) && G_IS_THEMED_ICON (icon)) + { + const gchar * const *names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + return g_strdup (names[0]); + } + + g_object_unref (info); + return NULL; +} + +gchar * +gedit_file_browser_utils_name_from_themed_icon (GIcon *icon) +{ + GtkIconTheme *theme; + const gchar * const *names; + + if (!G_IS_THEMED_ICON (icon)) + return NULL; + + theme = gtk_icon_theme_get_default (); + names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + + if (gtk_icon_theme_has_icon (theme, names[0])) + return g_strdup (names[0]); + + return NULL; +} + +gchar * +gedit_file_browser_utils_file_basename (GFile *file) +{ + return gedit_utils_basename_for_display (file); +} + +gboolean +gedit_file_browser_utils_confirmation_dialog (GeditWindow *window, + GtkMessageType type, + gchar const *message, + gchar const *secondary, + gchar const *button_label) +{ + GtkWidget *dlg; + gint ret; + + 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); + } + + gtk_dialog_add_buttons (GTK_DIALOG (dlg), + _("_Cancel"), GTK_RESPONSE_CANCEL, + button_label, GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_RESPONSE_CANCEL); + + ret = gtk_dialog_run (GTK_DIALOG (dlg)); + gtk_widget_destroy (dlg); + + return (ret == GTK_RESPONSE_OK); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-utils.h b/plugins/filebrowser/gedit-file-browser-utils.h new file mode 100644 index 0000000..165f513 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-utils.h @@ -0,0 +1,46 @@ +/* + * gedit-file-browser-utils.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, see . + */ + +#ifndef GEDIT_FILE_BROWSER_UTILS_H +#define GEDIT_FILE_BROWSER_UTILS_H + +#include +#include + +gchar *gedit_file_browser_utils_name_from_themed_icon (GIcon *icon); +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, + gboolean use_symbolic); +gchar *gedit_file_browser_utils_symbolic_icon_name_from_file (GFile *file); +gchar *gedit_file_browser_utils_file_basename (GFile *file); + +gboolean gedit_file_browser_utils_confirmation_dialog (GeditWindow *window, + GtkMessageType type, + gchar const *message, + gchar const *secondary, + gchar const *button_label); + +#endif /* GEDIT_FILE_BROWSER_UTILS_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-view.c b/plugins/filebrowser/gedit-file-browser-view.c new file mode 100644 index 0000000..a13f4d7 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-view.c @@ -0,0 +1,1364 @@ +/* + * 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, see . + */ + +#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-enum-types.h" + +struct _GeditFileBrowserViewPrivate +{ + GtkTreeViewColumn *column; + GtkCellRenderer *pixbuf_renderer; + GtkCellRenderer *text_renderer; + + GtkTreeModel *model; + + /* Used when renaming */ + gchar *orig_markup; + GtkTreeRowReference *editable; + + /* Click policy */ + GeditFileBrowserViewClickPolicy click_policy; + /* Both clicks in a double click need to be on the same row */ + GtkTreePath *double_click_path[2]; + 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 } +}; + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserView, + gedit_file_browser_view, + GTK_TYPE_TREE_VIEW, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserView)) + +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, + GFile *location, + 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) + g_object_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; + } + + G_OBJECT_CLASS (gedit_file_browser_view_parent_class)->finalize (object); +} + +static void +add_expand_state (GeditFileBrowserView *view, + GFile *location) +{ + if (!location) + return; + + if (view->priv->expand_state) + g_hash_table_insert (view->priv->expand_state, location, g_object_ref (location)); +} + +static void +remove_expand_state (GeditFileBrowserView *view, + GFile *location) +{ + if (!location) + return; + + if (view->priv->expand_state) + g_hash_table_remove (view->priv->expand_state, location); +} + +static void +row_expanded (GtkTreeView *tree_view, + GtkTreeIter *iter, + GtkTreePath *path) +{ + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view); + + 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) + { + GFile *location; + + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + add_expand_state (view, location); + + if (location) + g_object_unref (location); + } + + _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); + + 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) + { + GFile *location; + + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + remove_expand_state (view, location); + + if (location) + g_object_unref (location); + } + + _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) +{ + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (obj)); + + 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_from_name (display, "pointer"); + } + else if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE) + { + if (obj->priv->hover_path != NULL) + { + GtkTreeIter iter; + + 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_get_realized (GTK_WIDGET (obj))) + { + GdkWindow *win = gtk_widget_get_window (GTK_WIDGET (obj)); + + gdk_window_set_cursor (win, NULL); + + if (display != NULL) + gdk_display_flush (display); + } + + if (obj->priv->hand_cursor) + { + g_object_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 = gtk_tree_selection_get_selected_rows (selection, &view->priv->model); + GList *row; + GtkTreePath *directory = NULL; + GtkTreePath *path; + GtkTreeIter iter; + GeditFileBrowserStoreFlag flags; + + 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) && directory == NULL) + directory = path; + else if (!FILE_IS_DUMMY (flags)) + g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter); + } + + if (directory != NULL && + gtk_tree_model_get_iter (view->priv->model, &iter, directory)) + { + g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter); + } + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); +} + +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 +expand_or_collapse_selected_item (GeditFileBrowserView *view, + gboolean collapse) +{ + GtkTreeView *tree_view = GTK_TREE_VIEW (view); + GtkTreePath *path = NULL; + + gtk_tree_view_get_cursor (tree_view, &path, NULL); + + if (path == NULL) + { + return; + } + + if (collapse) + { + if (!gtk_tree_view_collapse_row (tree_view, path) && + gtk_tree_path_get_depth (path) > 1 && + gtk_tree_path_up (path)) + { + gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE); + } + } + else + { + gtk_tree_view_expand_row (tree_view, path, FALSE); + } + + gtk_tree_path_free (path); +} + +static void +row_activated (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + + /* Make sure the activated row is the only one selected */ + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + + activate_selected_items (GEDIT_FILE_BROWSER_VIEW (tree_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 = GTK_TREE_VIEW (view); + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + GtkTreePath *path; + + 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) +{ + GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class); + GtkTreeView *tree_view = GTK_TREE_VIEW (widget); + GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget); + GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view); + int double_click_time; + static int click_count = 0; + static guint32 last_click_time = 0; + GtkTreePath *path; + int expander_size; + int horizontal_separator; + gboolean on_expander; + gboolean call_parent; + gboolean selected; + + /* 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) + { + /* Do not chain up. The row-activated signal is normally + * already sent, which will activate the selected item + * and open the file. + */ + } + 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 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 == GDK_BUTTON_SECONDARY && 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 = GEDIT_FILE_BROWSER_VIEW (widget); + guint modifiers = gtk_accelerator_get_default_mod_mask (); + gboolean handled = FALSE; + + switch (event->keyval) + { + case GDK_KEY_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_KEY_Return: + case GDK_KEY_KP_Enter: + activate_selected_items (view); + handled = TRUE; + break; + + case GDK_KEY_h: + if ((event->state & modifiers) == GDK_CONTROL_MASK) + { + toggle_hidden_filter (view); + handled = TRUE; + } + break; + + case GDK_KEY_Left: + expand_or_collapse_selected_item (view, TRUE); + handled = TRUE; + break; + + case GDK_KEY_Right: + expand_or_collapse_selected_item (view, FALSE); + handled = TRUE; + break; + + default: + handled = FALSE; + break; + } + + /* 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; + + 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)) + { + GFile *location; + + gtk_tree_model_get (view->priv->model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + add_expand_state (view, location); + + if (location) + g_object_unref (location); + } + + 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_activated = row_activated; + 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, NULL, + 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, NULL, + 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, NULL, + 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, NULL, + G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER); +} + +static void +gedit_file_browser_view_class_finalize (GeditFileBrowserViewClass *klass) +{ +} + +static void +cell_data_cb (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GeditFileBrowserView *obj) +{ + GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter); + PangoUnderline underline = PANGO_UNDERLINE_NONE; + gboolean editable = FALSE; + + if (obj->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE && + 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) && + 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 (edpath); + } + + gtk_tree_path_free (path); + g_object_set (cell, "editable", editable, "underline", underline, NULL); +} + +static void +icon_renderer_cb (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GeditFileBrowserView *obj) +{ + GdkPixbuf *pixbuf; + gchar *icon_name; + gboolean set_pixbuf = FALSE; + + gtk_tree_model_get (tree_model, + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON_NAME, &icon_name, + GEDIT_FILE_BROWSER_STORE_COLUMN_ICON, &pixbuf, + -1); + + if (pixbuf != NULL && (GEDIT_IS_FILE_BROWSER_STORE (tree_model) || icon_name == NULL)) + set_pixbuf = TRUE; + + if (set_pixbuf) + g_object_set (cell, "pixbuf", pixbuf, NULL); + else + g_object_set (cell, "icon-name", icon_name, NULL); + + g_clear_object (&pixbuf); + g_free (icon_name); +} + +static void +gedit_file_browser_view_init (GeditFileBrowserView *obj) +{ + obj->priv = gedit_file_browser_view_get_instance_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_set_cell_data_func (obj->priv->column, + obj->priv->pixbuf_renderer, + (GtkTreeCellDataFunc)icon_renderer_cb, + obj, + NULL); + + 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, + "markup", + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP); + + 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); +} + +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; + gint search_column; + + 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); + search_column = GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME; + } + 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); + search_column = GEDIT_FILE_BROWSER_STORE_COLUMN_NAME; + + 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) && + 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); + gtk_tree_view_set_search_column (GTK_TREE_VIEW (tree_view), search_column); +} + +void +gedit_file_browser_view_start_rename (GeditFileBrowserView *tree_view, + GtkTreeIter *iter) +{ + gchar *name; + gchar *markup; + guint flags; + GValue name_escaped = G_VALUE_INIT; + 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_NAME, &name, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &markup, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + -1); + + if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags))) + { + g_free (name); + g_free (markup); + return; + } + + /* Restore the markup to the original + * name, a plugin might have changed the markup. + */ + g_value_init (&name_escaped, G_TYPE_STRING); + g_value_take_string (&name_escaped, g_markup_escape_text (name, -1)); + gedit_file_browser_store_set_value (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model), + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &name_escaped); + + 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->orig_markup = markup; + tree_view->priv->editable = rowref; + + /* grab focus on the text cell which is editable */ + gtk_tree_view_column_focus_cell (tree_view->priv->column, tree_view->priv->text_renderer); + + path = gtk_tree_row_reference_get_path (tree_view->priv->editable), + gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view), path, tree_view->priv->column, TRUE); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), + path, tree_view->priv->column, + FALSE, 0.0, 0.0); + + gtk_tree_path_free (path); + g_value_unset (&name_escaped); + g_free (name); +} + +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 = gtk_tree_path_new_from_string (path); + GtkTreeIter iter; + gboolean ret; + GValue orig_markup = G_VALUE_INIT; + GError *error = NULL; + + ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath); + gtk_tree_path_free (treepath); + + if (ret) + { + /* Restore the original markup */ + g_value_init (&orig_markup, G_TYPE_STRING); + g_value_set_string (&orig_markup, tree_view->priv->orig_markup); + gedit_file_browser_store_set_value (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model), &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_MARKUP, &orig_markup); + + if (new_text != NULL && *new_text != '\0' && + 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); + } + + g_value_unset (&orig_markup); + } + + g_free (tree_view->priv->orig_markup); + tree_view->priv->orig_markup = NULL; + + gtk_tree_row_reference_free (tree_view->priv->editable); + tree_view->priv->editable = NULL; +} + +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, + GFile *location, + 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, location); +} + +static void +restore_expand_state (GeditFileBrowserView *view, + GeditFileBrowserStore *model, + GtkTreeIter *iter) +{ + GFile *location; + + gtk_tree_model_get (GTK_TREE_MODEL (model), + iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (location) + { + GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + + if (g_hash_table_lookup (view->priv->expand_state, location)) + gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, FALSE); + + gtk_tree_path_free (path); + g_object_unref (location); + } +} + +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); +} + +void +_gedit_file_browser_view_register_type (GTypeModule *type_module) +{ + gedit_file_browser_view_register_type (type_module); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-view.h b/plugins/filebrowser/gedit-file-browser-view.h new file mode 100644 index 0000000..8c2efc8 --- /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, see . + */ + +#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_SINGLE, + GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE +} 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; + +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); + +void _gedit_file_browser_view_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_VIEW_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-widget.c b/plugins/filebrowser/gedit-file-browser-widget.c new file mode 100644 index 0000000..00585b4 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget.c @@ -0,0 +1,3152 @@ +/* + * 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, see . + */ + +#include "config.h" + +#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-enum-types.h" + +#define LOCATION_DATA_KEY "gedit-file-browser-widget-location" + +enum +{ + BOOKMARKS_ID, + SEPARATOR_CUSTOM_ID, + SEPARATOR_ID, + PATH_ID, + NUM_DEFAULT_IDS +}; + +enum +{ + COLUMN_ICON, + COLUMN_ICON_NAME, + COLUMN_NAME, + COLUMN_FILE, + COLUMN_ID, + N_COLUMNS +}; + +/* Properties */ +enum +{ + PROP_0, + + PROP_FILTER_PATTERN, +}; + +/* Signals */ +enum +{ + LOCATION_ACTIVATED, + ERROR, + CONFIRM_DELETE, + CONFIRM_NO_TRASH, + OPEN_IN_TERMINAL, + SET_ACTIVE_ROOT, + 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; + gchar *icon_name; + GdkPixbuf *icon; +} NameIcon; + +struct _GeditFileBrowserWidgetPrivate +{ + GeditFileBrowserView *treeview; + GeditFileBrowserStore *file_store; + GeditFileBookmarksStore *bookmarks_store; + + GHashTable *bookmarks_hash; + + GMenuModel *dir_menu; + GMenuModel *bookmarks_menu; + + GtkWidget *previous_button; + GtkWidget *next_button; + + GtkWidget *locations_button; + GtkWidget *locations_popover; + GtkWidget *locations_treeview; + GtkTreeViewColumn *treeview_icon_column; + GtkCellRenderer *treeview_icon_renderer; + GtkTreeSelection *locations_treeview_selection; + GtkWidget *locations_button_arrow; + GtkWidget *locations_cellview; + GtkListStore *locations_model; + + GtkWidget *location_entry; + + GtkWidget *filter_entry_revealer; + GtkWidget *filter_entry; + + GSimpleActionGroup *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; + + GCancellable *cancellable; + + GdkCursor *busy_cursor; +}; + +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 gboolean on_location_button_press_event (GtkWidget *button, + GdkEventButton *event, + GeditFileBrowserWidget *obj); +static void on_location_entry_activate (GtkEntry *entry, + GeditFileBrowserWidget *obj); +static gboolean + on_location_entry_focus_out_event (GtkWidget *entry, + GdkEvent *event, + GeditFileBrowserWidget *obj); +static gboolean + on_location_entry_key_press_event (GtkWidget *entry, + GdkEventKey *event, + 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 previous_location_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void next_location_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void up_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void home_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void new_folder_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void open_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void new_file_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void rename_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void delete_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void move_to_trash_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void refresh_view_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void view_folder_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void change_show_hidden_state (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void change_show_binary_state (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void change_show_match_filename (GSimpleAction *action, + GVariant *state, + gpointer user_data); +static void open_in_terminal_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void set_active_root_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data); +static void on_locations_treeview_selection_changed (GtkTreeSelection *treeselection, + GeditFileBrowserWidget *obj); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditFileBrowserWidget, + gedit_file_browser_widget, + GTK_TYPE_GRID, + 0, + G_ADD_PRIVATE_DYNAMIC (GeditFileBrowserWidget)) + +static void +free_name_icon (gpointer data) +{ + NameIcon *item = (NameIcon *)(data); + + if (item == NULL) + return; + + g_free (item->icon_name); + g_free (item->name); + + if (item->icon) + g_object_unref (item->icon); + + g_slice_free (NameIcon, item); +} + +static FilterFunc * +filter_func_new (GeditFileBrowserWidget *obj, + GeditFileBrowserWidgetFilterFunc func, + gpointer user_data, + GDestroyNotify notify) +{ + FilterFunc *result = g_slice_new (FilterFunc); + + 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_slice_free (Location, loc); +} + +static gboolean +locations_find_by_id (GeditFileBrowserWidget *obj, + guint id, + GtkTreeIter *iter) +{ + GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->locations_model); + guint checkid; + + 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 +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 +filter_func_free (FilterFunc *func) +{ + g_slice_free (FilterFunc, func); +} + +static void +gedit_file_browser_widget_finalize (GObject *object) +{ + GeditFileBrowserWidgetPrivate *priv = GEDIT_FILE_BROWSER_WIDGET (object)->priv; + + g_free (priv->filter_pattern_str); + + G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->finalize (object); +} + +static void +clear_signals (GeditFileBrowserWidget *obj) +{ + GSList *item = obj->priv->signal_pool; + SignalNode *node; + + while (item != NULL) + { + node = (SignalNode *) (item->data); + item = g_slist_delete_link (item, item); + + g_signal_handler_disconnect (node->object, node->id); + g_slice_free (SignalNode, node); + } + + obj->priv->signal_pool = NULL; +} + +static void +gedit_file_browser_widget_dispose (GObject *object) +{ + GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object); + GeditFileBrowserWidgetPrivate *priv = obj->priv; + + clear_signals (obj); + g_clear_object (&priv->file_store); + g_clear_object (&priv->bookmarks_store); + + g_slist_free_full (priv->filter_funcs, (GDestroyNotify)filter_func_free); + g_list_free_full (priv->locations, (GDestroyNotify)location_free); + + if (priv->bookmarks_hash != NULL) + { + g_hash_table_unref (priv->bookmarks_hash); + priv->bookmarks_hash = NULL; + } + + cancel_async_operation (obj); + + g_clear_object (&obj->priv->current_location_menu_item); + g_clear_object (&priv->busy_cursor); + g_clear_object (&priv->dir_menu); + g_clear_object (&priv->bookmarks_menu); + + G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->dispose (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; + 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; + 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); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gedit_file_browser_widget_finalize; + object_class->dispose = gedit_file_browser_widget_dispose; + + 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", + "", + G_PARAM_READWRITE)); + + signals[LOCATION_ACTIVATED] = + g_signal_new ("location-activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, location_activated), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_FILE); + + signals[ERROR] = + g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, error), + NULL, NULL, NULL, + 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, NULL, + 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, NULL, + G_TYPE_BOOLEAN, 1, G_TYPE_POINTER); + + signals[OPEN_IN_TERMINAL] = + g_signal_new ("open-in-terminal", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, open_in_terminal), + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_FILE); + + signals[SET_ACTIVE_ROOT] = + g_signal_new ("set-active-root", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GeditFileBrowserWidgetClass, set_active_root), + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + /* Bind class to template */ + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/gedit/plugins/file-browser/ui/gedit-file-browser-widget.ui"); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, previous_button); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, next_button); + + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_button); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_popover); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_treeview); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_treeview_selection); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview_icon_column); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview_icon_renderer); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_cellview); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_button_arrow); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, locations_model); + + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_entry); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, treeview); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, filter_entry_revealer); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, filter_entry); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_previous_menu); + gtk_widget_class_bind_template_child_private (widget_class, GeditFileBrowserWidget, location_next_menu); +} + +static void +gedit_file_browser_widget_class_finalize (GeditFileBrowserWidgetClass *klass) +{ +} + +static void +add_signal (GeditFileBrowserWidget *obj, + gpointer object, + gulong id) +{ + SignalNode *node = g_slice_new (SignalNode); + + node->object = G_OBJECT (object); + node->id = id; + + obj->priv->signal_pool = g_slist_prepend (obj->priv->signal_pool, node); +} + +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, + gchar **icon_name, + GdkPixbuf **icon) +{ + NameIcon *item = (NameIcon *)g_hash_table_lookup (obj->priv->bookmarks_hash, file); + + if (item == NULL) + return FALSE; + + *name = g_strdup (item->name); + *icon_name = g_strdup (item->icon_name); + + if (icon != NULL && item->icon != NULL) + { + *icon = g_object_ref (item->icon); + } + + return TRUE; +} + +static void +insert_path_item (GeditFileBrowserWidget *obj, + GFile *file, + GtkTreeIter *after, + GtkTreeIter *iter) +{ + gchar *unescape = NULL; + gchar *icon_name = NULL; + GdkPixbuf *icon = NULL; + + /* Try to get the icon and name from the bookmarks hash */ + if (!get_from_bookmark_file (obj, file, &unescape, &icon_name, &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_name = gedit_file_browser_utils_symbolic_icon_name_from_file (file); + } + + gtk_list_store_insert_after (obj->priv->locations_model, iter, after); + + gtk_list_store_set (obj->priv->locations_model, + iter, + COLUMN_ICON, icon, + COLUMN_ICON_NAME, icon_name, + COLUMN_NAME, unescape, + COLUMN_FILE, file, + COLUMN_ID, PATH_ID, + -1); + + if (icon) + g_object_unref (icon); + + g_free (icon_name); + g_free (unescape); +} + +static void +insert_separator_item (GeditFileBrowserWidget *obj) +{ + GtkTreeIter iter; + + gtk_list_store_insert (obj->priv->locations_model, &iter, 1); + gtk_list_store_set (obj->priv->locations_model, &iter, + COLUMN_ICON, NULL, + COLUMN_ICON_NAME, NULL, + COLUMN_NAME, NULL, + COLUMN_ID, SEPARATOR_ID, -1); +} + +static void +insert_location_path (GeditFileBrowserWidget *obj) +{ + GeditFileBrowserWidgetPrivate *priv = obj->priv; + Location *loc; + GFile *current = NULL; + GFile *tmp; + GtkTreeIter separator; + GtkTreeIter iter; + + if (!priv->current_location) + { + g_message ("insert_location_path: no current location"); + return; + } + + loc = (Location *)(priv->current_location->data); + + current = loc->virtual_root; + locations_find_by_id (obj, SEPARATOR_ID, &separator); + + while (current != NULL) + { + insert_path_item (obj, current, &separator, &iter); + + if (current == loc->virtual_root) + { + + g_signal_handlers_block_by_func (priv->locations_treeview, + on_locations_treeview_selection_changed, + obj); + + gtk_tree_selection_select_iter (priv->locations_treeview_selection, &iter); + + g_signal_handlers_unblock_by_func (priv->locations_treeview, + on_locations_treeview_selection_changed, + obj); + } + + if (g_file_equal (current, loc->root) || + !g_file_has_parent (current, NULL)) + { + 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 +remove_path_items (GeditFileBrowserWidget *obj) +{ + GtkTreeIter iter; + + while (locations_find_by_id (obj, PATH_ID, &iter)) + { + gtk_list_store_remove (obj->priv->locations_model, &iter); + } +} + +static void +check_current_item (GeditFileBrowserWidget *obj, + gboolean show_path) +{ + GtkTreeIter separator; + gboolean has_sep; + + remove_path_items (obj); + has_sep = locations_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_list_store_remove (obj->priv->locations_model, &separator); + } +} + +static void +fill_locations_model (GeditFileBrowserWidget *obj) +{ + GeditFileBrowserWidgetPrivate *priv = obj->priv; + GtkTreeIter iter; + + gtk_list_store_append (priv->locations_model, &iter); + gtk_list_store_set (priv->locations_model, + &iter, + COLUMN_ICON, NULL, + COLUMN_ICON_NAME, "user-bookmarks-symbolic", + COLUMN_NAME, _("Bookmarks"), + COLUMN_ID, BOOKMARKS_ID, -1); + + gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (priv->locations_treeview), + separator_func, + obj, + NULL); + + gtk_tree_selection_select_iter (priv->locations_treeview_selection, &iter); + + on_locations_treeview_selection_changed (priv->locations_treeview_selection, obj); + gedit_file_browser_widget_show_bookmarks (obj); +} + +static gboolean +filter_real (GeditFileBrowserStore *model, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + GSList *item; + + for (item = obj->priv->filter_funcs; item; item = item->next) + { + FilterFunc *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 = GTK_TREE_MODEL (obj->priv->bookmarks_store); + GdkPixbuf *pixbuf; + gchar *name; + gchar *icon_name; + GFile *location; + NameIcon *item; + + if (!(location = gedit_file_bookmarks_store_get_location (obj->priv->bookmarks_store, iter))) + return; + + gtk_tree_model_get (model, + iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, &pixbuf, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON_NAME, &icon_name, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &name, + -1); + + item = g_slice_new (NameIcon); + item->name = name; + item->icon_name = icon_name; + item->icon = pixbuf; + + g_hash_table_insert (obj->priv->bookmarks_hash, + location, + item); +} + +static void +init_bookmarks_hash (GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->bookmarks_store); + GtkTreeIter iter; + + 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 +on_locations_treeview_row_activated (GtkTreeView *locations_treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + GeditFileBrowserWidget *obj) +{ + GeditFileBrowserWidgetPrivate *priv = obj->priv; + GtkTreeIter iter; + guint id = G_MAXUINT; + GFile *file; + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->locations_model), &iter, path)) + { + gtk_tree_model_get (GTK_TREE_MODEL (priv->locations_model), &iter, COLUMN_ID, &id, -1); + } + + if (id == BOOKMARKS_ID) + { + gedit_file_browser_widget_show_bookmarks (obj); + } + else if (id == PATH_ID) + { + gtk_tree_model_get (GTK_TREE_MODEL (priv->locations_model), + &iter, + COLUMN_FILE, &file, + -1); + + gedit_file_browser_store_set_virtual_root_from_location (priv->file_store, file); + + g_object_unref (file); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (priv->locations_cellview), path); + } + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->locations_button), FALSE); +} + +static GActionEntry browser_entries[] = { + { "open", open_activated }, + { "set_active_root", set_active_root_activated }, + { "new_folder", new_folder_activated }, + { "new_file", new_file_activated }, + { "rename", rename_activated }, + { "move_to_trash", move_to_trash_activated }, + { "delete", delete_activated }, + { "refresh_view", refresh_view_activated }, + { "view_folder", view_folder_activated }, + { "open_in_terminal", open_in_terminal_activated }, + { "show_hidden", NULL, NULL, "false", change_show_hidden_state }, + { "show_binary", NULL, NULL, "false", change_show_binary_state }, + { "show_match_filename", NULL, NULL, "false", change_show_match_filename }, + { "previous_location", previous_location_activated }, + { "next_location", next_location_activated }, + { "up", up_activated }, + { "home", home_activated } +}; + +static void +locations_icon_renderer_cb (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + GeditFileBrowserWidget *obj) +{ + GdkPixbuf *pixbuf; + gchar *icon_name; + + gtk_tree_model_get (tree_model, + iter, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON_NAME, &icon_name, + GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, &pixbuf, + -1); + + if (icon_name != NULL) + g_object_set (cell, "icon-name", icon_name, NULL); + else + g_object_set (cell, "pixbuf", pixbuf, NULL); + + g_clear_object (&pixbuf); + g_free (icon_name); +} + +static void +gedit_file_browser_widget_init (GeditFileBrowserWidget *obj) +{ + GtkBuilder *builder; + GdkDisplay *display; + GAction *action; + GError *error = NULL; + + obj->priv = gedit_file_browser_widget_get_instance_private (obj); + + obj->priv->filter_pattern_str = g_strdup (""); + obj->priv->bookmarks_hash = g_hash_table_new_full (g_file_hash, + (GEqualFunc)g_file_equal, + g_object_unref, + free_name_icon); + + display = gtk_widget_get_display (GTK_WIDGET (obj)); + obj->priv->busy_cursor = gdk_cursor_new_from_name (display, "progress"); + + builder = gtk_builder_new (); + if (!gtk_builder_add_from_resource (builder, + "/org/gnome/gedit/plugins/file-browser/ui/gedit-file-browser-menus.ui", + &error)) + { + g_warning ("loading menu builder file: %s", error->message); + g_error_free (error); + } + else + { + obj->priv->dir_menu = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "dir-menu"))); + obj->priv->bookmarks_menu = G_MENU_MODEL (g_object_ref (gtk_builder_get_object (builder, "bookmarks-menu"))); + } + + g_object_unref (builder); + + obj->priv->action_group = g_simple_action_group_new (); + g_action_map_add_action_entries (G_ACTION_MAP (obj->priv->action_group), + browser_entries, + G_N_ELEMENTS (browser_entries), + obj); + + /* set initial sensitivity */ + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "previous_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "next_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + gtk_widget_insert_action_group (GTK_WIDGET (obj), + "browser", + G_ACTION_GROUP (obj->priv->action_group)); + + gtk_widget_init_template (GTK_WIDGET (obj)); + + g_signal_connect (obj->priv->previous_button, "button-press-event", + G_CALLBACK (on_location_button_press_event), obj); + g_signal_connect (obj->priv->next_button, "button-press-event", + G_CALLBACK (on_location_button_press_event), obj); + + /* locations popover */ + gtk_tree_selection_set_mode (obj->priv->locations_treeview_selection, GTK_SELECTION_SINGLE); + gtk_tree_view_column_set_cell_data_func (obj->priv->treeview_icon_column, + obj->priv->treeview_icon_renderer, + (GtkTreeCellDataFunc)locations_icon_renderer_cb, + obj, + NULL); + fill_locations_model (obj); + + g_signal_connect (obj->priv->locations_treeview_selection, "changed", + G_CALLBACK (on_locations_treeview_selection_changed), obj); + g_signal_connect (obj->priv->locations_treeview, "row-activated", + G_CALLBACK (on_locations_treeview_row_activated), obj); + + g_signal_connect (obj->priv->location_entry, "activate", + G_CALLBACK (on_location_entry_activate), obj); + g_signal_connect (obj->priv->location_entry, "focus-out-event", + G_CALLBACK (on_location_entry_focus_out_event), obj); + g_signal_connect (obj->priv->location_entry, "key-press-event", + G_CALLBACK (on_location_entry_key_press_event), obj); + + /* tree view */ + obj->priv->file_store = gedit_file_browser_store_new (NULL); + obj->priv->bookmarks_store = gedit_file_bookmarks_store_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); + + 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); + + /* filter */ + g_signal_connect_swapped (obj->priv->filter_entry, "activate", + G_CALLBACK (on_entry_filter_activate), + obj); + g_signal_connect_swapped (obj->priv->filter_entry, "focus_out_event", + G_CALLBACK (on_entry_filter_activate), + obj); +} + +/* Private */ + +static void +update_sensitivity (GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GAction *action; + gint mode; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + { + mode = gedit_file_browser_store_get_filter_mode (GEDIT_FILE_BROWSER_STORE (model)); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_hidden"); + + g_action_change_state (action, + g_variant_new_boolean (!(mode & + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN))); + + /* sensitivity */ + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "up"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "home"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_hidden"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_binary"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_match_filename"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + } + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + { + /* Set the filter toggle to normal up state, just for visual pleasure */ + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_hidden"); + + g_action_change_state (action, g_variant_new_boolean (FALSE)); + + /* sensitivity */ + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "up"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "home"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_hidden"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_binary"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), + "show_match_filename"); + g_simple_action_set_enabled (G_SIMPLE_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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return result; +} + +static gboolean +popup_menu (GeditFileBrowserWidget *obj, + GtkTreeView *treeview, + GdkEventButton *event, + GtkTreeModel *model) +{ + GtkWidget *menu; + GMenuModel *menu_model; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + menu_model = obj->priv->dir_menu; + else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) + menu_model = obj->priv->bookmarks_menu; + else + return FALSE; + + menu = gtk_menu_new_from_model (menu_model); + gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (obj), NULL); + + if (event != NULL) + { + GtkTreeSelection *selection; + selection = gtk_tree_view_get_selection (treeview); + + if (gtk_tree_selection_count_selected_rows (selection) <= 1) + { + GtkTreePath *path; + + if (gtk_tree_view_get_path_at_pos (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_at_pointer (GTK_MENU (menu), (GdkEvent *)event); + } + else + { + GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (treeview)); + GdkGravity rect_gravity = GDK_GRAVITY_EAST; + GdkGravity menu_gravity = GDK_GRAVITY_NORTH_WEST; + GdkRectangle rect; + + if (gedit_utils_menu_position_under_tree_view (treeview, &rect)) + { + if (gtk_widget_get_direction (GTK_WIDGET (treeview)) == GTK_TEXT_DIR_RTL) + { + rect_gravity = GDK_GRAVITY_WEST; + menu_gravity = GDK_GRAVITY_NORTH_EAST; + } + + gtk_menu_popup_at_rect (GTK_MENU (menu), window, &rect, rect_gravity, menu_gravity, NULL); + 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_spec_match_string (obj->priv->filter_pattern, name); + + g_free (name); + return result; +} + +static void +rename_selected_file (GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeIter iter; + + 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) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + GList *rows = gtk_tree_selection_get_selected_rows (selection, &model); + GList *row; + GList *paths = NULL; + + for (row = rows; row; row = row->next) + { + GtkTreePath *path = (GtkTreePath *)(row->data); + GtkTreeIter iter; + guint flags; + + 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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return paths; +} + +static gboolean +delete_selected_files (GeditFileBrowserWidget *obj, + gboolean trash) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + gboolean confirm; + GeditFileBrowserStoreResult result; + GList *rows; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return FALSE; + + if (!(rows = get_deletable_files (obj))) + 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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return result == GEDIT_FILE_BROWSER_STORE_RESULT_OK; +} + +static void +show_location_entry (GeditFileBrowserWidget *obj, + const gchar *location) +{ + g_warn_if_fail (location != NULL); + + gtk_entry_set_text (GTK_ENTRY (obj->priv->location_entry), location); + + gtk_widget_show (obj->priv->location_entry); + gtk_widget_grab_focus (obj->priv->location_entry); + + /* grab_focus() causes the entry's text to become + * selected so, unselect it and move the cursor to the end. + */ + gtk_editable_set_position (GTK_EDITABLE (obj->priv->location_entry), -1); +} + +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 *current = g_object_ref (file); + GFile *tmp; + + 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) +{ + Location *loc = (Location *) (item->data); + GtkWidget *result; + gchar *icon_name = NULL; + gchar *unescape = NULL; + + if (!get_from_bookmark_file (obj, loc->virtual_root, &unescape, &icon_name, NULL)) + unescape = gedit_file_browser_utils_file_basename (loc->virtual_root); + + 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 (icon_name); + 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; + + 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 */ + gedit_file_browser_widget_set_root_and_virtual_root (obj, + loc->root, + loc->virtual_root); + + obj->priv->changing_location = FALSE; +} + +static void +clear_next_locations (GeditFileBrowserWidget *obj) +{ + GAction *action; + 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); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); +} + +static void +update_filter_mode (GeditFileBrowserWidget *obj, + GSimpleAction *action, + GVariant *state, + GeditFileBrowserStoreFilterMode mode) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + { + gint now = gedit_file_browser_store_get_filter_mode (GEDIT_FILE_BROWSER_STORE (model)); + + if (g_variant_get_boolean(state)) + now &= ~mode; + else + now |= mode; + + gedit_file_browser_store_set_filter_mode (GEDIT_FILE_BROWSER_STORE (model), now); + + } + + g_simple_action_set_state (action, state); +} + +static void +set_filter_pattern_real (GeditFileBrowserWidget *obj, + gchar const *pattern, + gboolean update_entry) +{ + GtkTreeModel *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 == '\0') + return; + + if (pattern != NULL && strcmp (pattern, obj->priv->filter_pattern_str) == 0) + return; + + /* Free the old pattern */ + g_free (obj->priv->filter_pattern_str); + + if (pattern == NULL) + obj->priv->filter_pattern_str = g_strdup (""); + else + 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) + gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry), obj->priv->filter_pattern_str); + + 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 (void) +{ + GeditFileBrowserWidget *obj = g_object_new (GEDIT_TYPE_FILE_BROWSER_WIDGET, NULL); + + gedit_file_browser_widget_show_bookmarks (obj); + + return GTK_WIDGET (obj); +} + +void +gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget *obj) +{ + GtkTreePath *path; + GtkTreeIter iter; + + gtk_widget_set_sensitive (obj->priv->locations_button, FALSE); + gtk_widget_hide (obj->priv->locations_button_arrow); + locations_find_by_id (obj, BOOKMARKS_ID, &iter); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (obj->priv->locations_model), &iter); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (obj->priv->locations_cellview), path); + gtk_tree_path_free (path); + + 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) +{ + gtk_widget_set_sensitive (obj->priv->locations_button, TRUE); + gtk_widget_show (obj->priv->locations_button_arrow); + + 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, + GFile *root, + GFile *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, + GFile *root, + gboolean virtual_root) +{ + GFile *parent; + + if (!virtual_root) + { + gedit_file_browser_widget_set_root_and_virtual_root (obj, + root, + NULL); + return; + } + + if (!root) + return; + + parent = get_topmost_file (root); + gedit_file_browser_widget_set_root_and_virtual_root (obj, parent, root); + 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; +} + +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 = filter_func_new (obj, func, user_data, notify);; + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + + 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; + + for (item = obj->priv->filter_funcs; item; item = item->next) + { + FilterFunc *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); + + filter_func_free (func); + break; + } + } +} + +void +gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget *obj, + gchar const *pattern) +{ + gboolean show; + GAction *action; + + /* if pattern is not null, reveal the entry */ + show = pattern != NULL && *pattern != '\0'; + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_match_filename"); + g_action_change_state (action, g_variant_new_boolean (show)); + + 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) && + !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; +} + +void +gedit_file_browser_widget_set_active_root_enabled (GeditFileBrowserWidget *widget, + gboolean enabled) +{ + GAction *action; + + g_return_if_fail (GEDIT_IS_FILE_BROWSER_WIDGET (widget)); + + action = g_action_map_lookup_action (G_ACTION_MAP (widget->priv->action_group), "set_active_root"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); +} + +static guint +gedit_file_browser_widget_get_num_selected_files_or_directories (GeditFileBrowserWidget *obj, + guint *files, + guint *dirs) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview)); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GList *rows, *row; + guint result = 0; + + 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) + { + GtkTreePath *path = (GtkTreePath *)(row->data); + GeditFileBrowserStoreFlag flags; + GtkTreeIter iter; + + /* 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_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return result; +} + +typedef struct +{ + GeditFileBrowserWidget *widget; + GCancellable *cancellable; +} AsyncData; + +static AsyncData * +async_data_new (GeditFileBrowserWidget *widget) +{ + AsyncData *ret = g_slice_new (AsyncData); + + 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_slice_free (AsyncData, async); +} + +static void +set_busy (GeditFileBrowserWidget *obj, + gboolean busy) +{ + GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview)); + + if (!GDK_IS_WINDOW (window)) + return; + + if (busy) + { + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (obj)); + GdkCursor *cursor= gdk_cursor_new_from_name (display, "progress"); + + gdk_window_set_cursor (window, cursor); + g_clear_object (&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; + + if (!mount) + { + gchar *name = g_volume_get_name (volume); + gchar *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); + + gedit_file_browser_widget_set_root (widget, root, FALSE); + + g_object_unref (root); +} + +static void +try_activate_drive (GeditFileBrowserWidget *widget, + GDrive *drive) +{ + GList *volumes = g_drive_get_volumes (drive); + GVolume *volume = G_VOLUME (volumes->data); + GMount *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_free_full (volumes, g_object_unref); +} + +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 *name = g_drive_get_name (drive); + gchar *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 = g_volume_get_mount (volume); + + activate_mount (async->widget, volume, mount); + + if (mount) + g_object_unref (mount); + } + else + { + gchar *name = g_volume_get_name (volume); + gchar *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 = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (widget)))); + AsyncData *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)); + } +} + +GeditMenuExtension * +gedit_file_browser_widget_extend_context_menu (GeditFileBrowserWidget *obj) +{ + guint i, n_items; + GMenuModel *section = NULL; + + g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_WIDGET (obj), NULL); + + n_items = g_menu_model_get_n_items (obj->priv->dir_menu); + + for (i = 0; i < n_items && !section; i++) + { + gchar *id = NULL; + + if (g_menu_model_get_item_attribute (obj->priv->dir_menu, i, "id", "s", &id) && + strcmp (id, "extension-section") == 0) + { + section = g_menu_model_get_item_link (obj->priv->dir_menu, i, G_MENU_LINK_SECTION); + } + + g_free (id); + } + + return section != NULL ? gedit_menu_extension_new (G_MENU (section)) : NULL; +} + +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) +{ + GFile *location; + 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; + } + + if ((location = gedit_file_bookmarks_store_get_location (GEDIT_FILE_BOOKMARKS_STORE (model), iter))) + { + /* 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, location, FALSE); + } + else + { + gedit_file_browser_widget_set_root (obj, location, TRUE); + } + + g_object_unref (location); + } + else + { + g_warning ("No uri!"); + } +} + +static void +file_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + GFile *location; + gint flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (!FILE_IS_DIR (flags) && !FILE_IS_DUMMY (flags)) + g_signal_emit (obj, signals[LOCATION_ACTIVATED], 0, location); + + if (location) + g_object_unref (location); +} + +static gboolean +directory_open (GeditFileBrowserWidget *obj, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gboolean result = FALSE; + GError *error = NULL; + GFile *location; + GeditFileBrowserStoreFlag flags; + + gtk_tree_model_get (model, iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (FILE_IS_DIR (flags) && location) + { + gchar *uri = g_file_get_uri (location); + GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (obj))); + + result = TRUE; + + if (!gtk_show_uri_on_window (window, 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); + g_object_unref (location); + } + + 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; + + 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)) + { + GFile *location; + GtkTreeIter root; + + gtk_tree_model_get (GTK_TREE_MODEL (model), + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, + -1); + + if (gedit_file_browser_store_get_iter_root (model, &root)) + { + GAction *action; + + if (!obj->priv->changing_location) + { + Location *loc; + + /* Remove all items from obj->priv->current_location on */ + if (obj->priv->current_location) + clear_next_locations (obj); + + loc = g_slice_new (Location); + loc->root = gedit_file_browser_store_get_root (model); + loc->virtual_root = g_object_ref (location); + + 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); + + obj->priv->current_location = obj->priv->locations; + obj->priv->current_location_menu_item = create_goto_menu_item (obj, + obj->priv->current_location); + + g_object_ref_sink (obj->priv->current_location_menu_item); + } + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "up"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), !virtual_root_is_root (obj, model)); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + obj->priv->current_location != NULL && + obj->priv->current_location->next != NULL); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + obj->priv->current_location != NULL && + obj->priv->current_location->prev != NULL); + } + + check_current_item (obj, TRUE); + + if (location) + g_object_unref (location); + } + 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) + { + GAction *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 = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + } + + gtk_widget_hide (obj->priv->filter_entry_revealer); + + 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_show (obj->priv->filter_entry_revealer); + } + + 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 gboolean +on_location_button_press_event (GtkWidget *button, + GdkEventButton *event, + GeditFileBrowserWidget *obj) +{ + GtkWidget *menu; + + if (event->button != GDK_BUTTON_SECONDARY) + return FALSE; + + if (button == obj->priv->previous_button) + menu = obj->priv->location_previous_menu; + else + menu = obj->priv->location_next_menu; + + gtk_menu_popup_at_pointer (GTK_MENU (menu), (GdkEvent *)event); + + return TRUE; +} + +static void +on_locations_treeview_selection_changed (GtkTreeSelection *treeview_selection, + GeditFileBrowserWidget *obj) +{ + GeditFileBrowserWidgetPrivate *priv = obj->priv; + GtkTreeModel *model = GTK_TREE_MODEL (priv->locations_model); + GtkTreePath *path; + GtkTreeIter iter; + + if (!gtk_tree_selection_get_selected (treeview_selection, &model, &iter)) + return; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (obj->priv->locations_model), &iter); + gtk_cell_view_set_displayed_row (GTK_CELL_VIEW (obj->priv->locations_cellview), path); + gtk_tree_path_free (path); +} + +static void +on_location_entry_activate (GtkEntry *entry, + GeditFileBrowserWidget *obj) +{ + gchar *location = g_strdup (gtk_entry_get_text (entry)); + GFile *root; + gchar *cwd; + GFile *new_root; + + if (g_str_has_prefix (location, "~/")) + { + gchar *tmp = location; + + location = g_strdup_printf ("%s/%s", g_get_home_dir (), tmp + strlen ("~/")); + + g_free (tmp); + } + + root = gedit_file_browser_store_get_virtual_root (obj->priv->file_store); + cwd = g_file_get_path (root); + + if (cwd == NULL) + cwd = g_file_get_uri (root); + + new_root = g_file_new_for_commandline_arg_and_cwd (location, cwd); + + if (g_file_query_file_type (new_root, G_FILE_QUERY_INFO_NONE, NULL) != G_FILE_TYPE_DIRECTORY) + { + gchar *display_name = g_file_get_parse_name (new_root); + gchar *msg = g_strdup_printf (_("Error when loading “%s”: No such directory"), display_name); + + g_signal_emit (obj, signals[ERROR], 0, GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY, msg); + + g_free (msg); + g_free (display_name); + } + else + { + gtk_widget_grab_focus (GTK_WIDGET (obj->priv->treeview)); + gtk_widget_hide (obj->priv->location_entry); + + gedit_file_browser_widget_set_root (obj, new_root, TRUE); + } + + g_object_unref (new_root); + g_free (cwd); + g_object_unref (root); + g_free (location); +} + +static gboolean +on_location_entry_focus_out_event (GtkWidget *entry, + GdkEvent *event, + GeditFileBrowserWidget *obj) +{ + gtk_widget_hide (entry); + return FALSE; +} + +static gboolean +on_location_entry_key_press_event (GtkWidget *entry, + GdkEventKey *event, + GeditFileBrowserWidget *obj) +{ + guint modifiers = gtk_accelerator_get_default_mod_mask (); + + if (event->keyval == GDK_KEY_Escape && + (event->state & modifiers) == 0) + { + gtk_widget_grab_focus (GTK_WIDGET (obj->priv->treeview)); + gtk_widget_hide (entry); + return TRUE; + } + + return FALSE; +} + +static gboolean +on_treeview_popup_menu (GeditFileBrowserView *treeview, + GeditFileBrowserWidget *obj) +{ + return popup_menu (obj, GTK_TREE_VIEW (treeview), 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 == GDK_BUTTON_SECONDARY) + return popup_menu (obj, + GTK_TREE_VIEW (treeview), + event, + gtk_tree_view_get_model (GTK_TREE_VIEW (treeview))); + + return FALSE; +} + +static gboolean +do_change_directory (GeditFileBrowserWidget *obj, + GdkEventKey *event) +{ + GAction *action = NULL; + + if ((event->state & + (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK & ~GDK_MOD1_MASK)) == + event->state && event->keyval == GDK_KEY_BackSpace) + { + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location"); + } + else if (!((event->state & GDK_MOD1_MASK) && + (event->state & (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK)) == event->state)) + { + return FALSE; + } + + switch (event->keyval) + { + case GDK_KEY_Home: + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "home"); + break; + case GDK_KEY_Left: + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "previous_location"); + break; + case GDK_KEY_Right: + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "next_location"); + break; + case GDK_KEY_Up: + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "up"); + break; + default: + break; + } + + if (action != NULL) + { + g_action_activate (action, NULL); + return TRUE; + } + + return FALSE; +} + +static gboolean +on_treeview_key_press_event (GeditFileBrowserView *treeview, + GdkEventKey *event, + GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model; + guint modifiers; + + if (do_change_directory (obj, event)) + return TRUE; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return FALSE; + + modifiers = gtk_accelerator_get_default_mod_mask (); + + if (event->keyval == GDK_KEY_Delete || + event->keyval == GDK_KEY_KP_Delete) + { + + if ((event->state & modifiers) == GDK_SHIFT_MASK) + { + delete_selected_files (obj, FALSE); + return TRUE; + } + else if ((event->state & modifiers) == 0) + { + delete_selected_files (obj, TRUE); + return TRUE; + } + } + + if ((event->keyval == GDK_KEY_F2) && (event->state & modifiers) == 0) + { + rename_selected_file (obj); + return TRUE; + } + + if (event->keyval == GDK_KEY_l && + (event->state & modifiers) == GDK_CONTROL_MASK) + { + show_location_entry (obj, ""); + return TRUE; + } + + if (event->keyval == GDK_KEY_slash || + event->keyval == GDK_KEY_KP_Divide || +#ifdef G_OS_WIN32 + event->keyval == GDK_KEY_backslash || +#endif + event->keyval == GDK_KEY_asciitilde) + { + gchar location[2] = {'\0', '\0'}; + + location[0] = gdk_keyval_to_unicode (event->keyval); + + show_location_entry (obj, location); + return TRUE; + } + + return FALSE; +} + +static void +on_selection_changed (GtkTreeSelection *selection, + GeditFileBrowserWidget *obj) +{ + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)); + GAction *action; + guint selected = 0; + guint files = 0; + guint dirs = 0; + + if (GEDIT_IS_FILE_BROWSER_STORE (model)) + selected = gedit_file_browser_widget_get_num_selected_files_or_directories (obj, &files, &dirs); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "move_to_trash"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "delete"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected > 0); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "open"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), (selected > 0) && (selected == files)); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "rename"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected == 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "open_in_terminal"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected == 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "new_folder"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected <= 1); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "new_file"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), selected <= 1); +} + +static gboolean +on_entry_filter_activate (GeditFileBrowserWidget *obj) +{ + gchar const *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 = 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; + GFile *location; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + + if (!(location = gedit_file_bookmarks_store_get_location (obj->priv->bookmarks_store, &iter))) + return; + + g_hash_table_remove (obj->priv->bookmarks_hash, location); + + g_object_unref (location); +} + +static void +on_filter_mode_changed (GeditFileBrowserStore *model, + GParamSpec *param, + GeditFileBrowserWidget *obj) +{ + gint mode = gedit_file_browser_store_get_filter_mode (model); + GAction *action; + GVariant *variant; + gboolean active; + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_hidden"); + active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN); + variant = g_action_get_state (action); + + if (active != g_variant_get_boolean (variant)) + g_action_change_state (action, g_variant_new_boolean (active)); + + g_variant_unref (variant); + + action = g_action_map_lookup_action (G_ACTION_MAP (obj->priv->action_group), "show_binary"); + active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); + variant = g_action_get_state (action); + + if (active != g_variant_get_boolean (variant)) + g_action_change_state (action, g_variant_new_boolean (active)); + + g_variant_unref (variant); +} + +static void +next_location_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gedit_file_browser_widget_history_forward (GEDIT_FILE_BROWSER_WIDGET (user_data)); +} + +static void +previous_location_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gedit_file_browser_widget_history_back (GEDIT_FILE_BROWSER_WIDGET (user_data)); +} + +static void +up_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->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 +home_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GFile *home_location; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + home_location = g_file_new_for_path (g_get_home_dir ()); + gedit_file_browser_widget_set_root (widget, home_location, TRUE); + + g_object_unref (home_location); +} + +static void +new_folder_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeIter parent; + GtkTreeIter iter; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (!gedit_file_browser_widget_get_selected_directory (widget, &parent)) + return; + + if (gedit_file_browser_store_new_directory (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) + gedit_file_browser_view_start_rename (widget->priv->treeview, &iter); +} + +static void +open_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->treeview)); + GList *rows; + GList *row; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + for (row = rows; row; row = row->next) + { + GtkTreePath *path = (GtkTreePath *)(row->data); + GtkTreeIter iter; + + if (gtk_tree_model_get_iter (model, &iter, path)) + file_open (widget, model, &iter); + + gtk_tree_path_free (path); + } + + g_list_free (rows); +} + +static void +new_file_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeIter parent; + GtkTreeIter iter; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + if (!gedit_file_browser_widget_get_selected_directory (widget, &parent)) + return; + + if (gedit_file_browser_store_new_file (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) + gedit_file_browser_view_start_rename (widget->priv->treeview, &iter); +} + +static void +rename_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + rename_selected_file (widget); +} + +static void +delete_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + delete_selected_files (widget, FALSE); +} + +static void +move_to_trash_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + delete_selected_files (widget, TRUE); +} + +static void +refresh_view_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + gedit_file_browser_widget_refresh (widget); +} + +static void +view_folder_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget->priv->treeview)); + GtkTreeIter iter; + GList *rows; + GList *row; + gboolean directory_opened = FALSE; + + if (!GEDIT_IS_FILE_BROWSER_STORE (model)) + return; + + rows = gtk_tree_selection_get_selected_rows (selection, &model); + for (row = rows; row; row = row->next) + { + GtkTreePath *path = (GtkTreePath *)(row->data); + + if (gtk_tree_model_get_iter (model, &iter, path)) + directory_opened |= directory_open (widget, model, &iter); + + gtk_tree_path_free (path); + } + + if (!directory_opened && gedit_file_browser_widget_get_selected_directory (widget, &iter)) + directory_open (widget, model, &iter); + + g_list_free (rows); +} + +static void +change_show_hidden_state (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + update_filter_mode (widget, + action, + state, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN); +} + +static void +change_show_binary_state (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + update_filter_mode (widget, + action, + state, + GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY); +} + +static void +change_show_match_filename (GSimpleAction *action, + GVariant *state, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + gboolean visible = g_variant_get_boolean (state); + + gtk_revealer_set_reveal_child (GTK_REVEALER (widget->priv->filter_entry_revealer), visible); + + if (visible) + gtk_widget_grab_focus (widget->priv->filter_entry); + else + /* clear the filter */ + set_filter_pattern_real (widget, NULL, TRUE); + + g_simple_action_set_state (action, state); +} + +static void +open_in_terminal_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + GtkTreeIter iter; + GFile *file; + + /* Get the current directory */ + if (!gedit_file_browser_widget_get_selected_directory (widget, &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (widget->priv->file_store), + &iter, + GEDIT_FILE_BROWSER_STORE_COLUMN_LOCATION, &file, + -1); + + g_signal_emit (widget, signals[OPEN_IN_TERMINAL], 0, file); + + g_object_unref (file); +} + +static void +set_active_root_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + GeditFileBrowserWidget *widget = GEDIT_FILE_BROWSER_WIDGET (user_data); + + g_signal_emit (widget, signals[SET_ACTIVE_ROOT], 0); +} + +void +_gedit_file_browser_widget_register_type (GTypeModule *type_module) +{ + gedit_file_browser_widget_register_type (type_module); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/gedit-file-browser-widget.h b/plugins/filebrowser/gedit-file-browser-widget.h new file mode 100644 index 0000000..8da8312 --- /dev/null +++ b/plugins/filebrowser/gedit-file-browser-widget.h @@ -0,0 +1,126 @@ +/* + * 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, see . + */ + +#ifndef GEDIT_FILE_BROWSER_WIDGET_H +#define GEDIT_FILE_BROWSER_WIDGET_H + +#include +#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 +{ + GtkBox parent; + + GeditFileBrowserWidgetPrivate *priv; +}; + +struct _GeditFileBrowserWidgetClass +{ + GtkBoxClass parent_class; + + /* Signals */ + void (* location_activated) (GeditFileBrowserWidget *widget, + GFile *location); + 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); + void (* open_in_terminal) (GeditFileBrowserWidget *widget, + GFile *location); + void (* set_active_root) (GeditFileBrowserWidget *widget); +}; + +GType gedit_file_browser_widget_get_type (void) G_GNUC_CONST; + +GtkWidget *gedit_file_browser_widget_new (void); + +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, + GFile *root, + gboolean virtual_root); +void gedit_file_browser_widget_set_root_and_virtual_root + (GeditFileBrowserWidget *obj, + GFile *root, + GFile *virtual_root); + +gboolean gedit_file_browser_widget_get_selected_directory + (GeditFileBrowserWidget *obj, + GtkTreeIter *iter); + +void gedit_file_browser_widget_set_active_root_enabled (GeditFileBrowserWidget *widget, + gboolean enabled); + +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); + +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); +GeditMenuExtension * + gedit_file_browser_widget_extend_context_menu (GeditFileBrowserWidget *obj); +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); + +void _gedit_file_browser_widget_register_type (GTypeModule *type_module); + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_WIDGET_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/filebrowser/meson.build b/plugins/filebrowser/meson.build new file mode 100644 index 0000000..ba66652 --- /dev/null +++ b/plugins/filebrowser/meson.build @@ -0,0 +1,127 @@ +libfilebrowser_public_h = files( + 'gedit-file-bookmarks-store.h', + 'gedit-file-browser-error.h', + 'gedit-file-browser-store.h', + 'gedit-file-browser-view.h', + 'gedit-file-browser-widget.h', + 'gedit-file-browser-utils.h', + 'gedit-file-browser-plugin.h', + 'gedit-file-browser-messages.h', +) + +libfilebrowser_sources = files( + 'gedit-file-bookmarks-store.c', + 'gedit-file-browser-messages.c', + 'gedit-file-browser-plugin.c', + 'gedit-file-browser-store.c', + 'gedit-file-browser-utils.c', + 'gedit-file-browser-view.c', + 'gedit-file-browser-widget.c', +) + +libfilebrowser_deps = [ + libgedit_dep, +] + +subdir('messages') + +libfilebrowser_register_enums = gnome.mkenums( + 'gedit-file-browser-enum-register', + sources: libfilebrowser_public_h, + c_template: 'gedit-file-browser-enum-register.c.template', +) + +libfilebrowser_type_enums = gnome.mkenums( + 'gedit-file-browser-enum-types', + depends : [libfilebrowser_register_enums], + sources: libfilebrowser_public_h, + h_template: 'gedit-file-browser-enum-types.h.template', + c_template: 'gedit-file-browser-enum-types-stage1.c.template', +) + +# cat won't work on Windows so this +# will need to be reimplemented as a script +cat = find_program('cat') + +# Combine the 2 C mkenums templates together before compiling +libfilebrowser_enums_c = custom_target('libfilebrowser_enums_c', + input: [libfilebrowser_type_enums.get(0), + libfilebrowser_register_enums], + output: 'gedit-file-browser-enum-types.c', + command: [cat, '@INPUT0@', '@INPUT1@'], + # redirects the command output since we can't use >> here + capture: true, +) + +libfilebrowser_sources += [ + libfilebrowser_enums_c, + libfilebrowser_type_enums.get(1), +] + +subdir('resources') + +libfilebrowser_sha = shared_module( + 'filebrowser', + sources: libfilebrowser_sources, + include_directories: root_include_dir, + dependencies: libfilebrowser_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +# FIXME: https://github.com/mesonbuild/meson/issues/1687 +custom_target( + 'org.gnome.gedit.plugins.filebrowser.enums.xml', + input : libfilebrowser_sources + libfilebrowser_public_h, + output: 'org.gnome.gedit.plugins.filebrowser.enums.xml', + capture: true, + command: [ + 'glib-mkenums', + '--comments', '', + '--fhead', '', + '--vhead', ' <@type@ id="org.gnome.gedit.plugins.filebrowser.@EnumName@">', + '--vprod', ' ', + '--vtail', ' ', + '--ftail', '', + '@INPUT@' + ], + install: true, + install_dir: join_paths( + glibdir, + 'schemas', + ) +) + +filebrowser_gschema_file = files('org.gnome.gedit.plugins.filebrowser.gschema.xml') +install_data( + filebrowser_gschema_file, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') +) + +if xmllint.found() + test( + 'validate-filebrowser-gschema', + xmllint, + args: [ + '--noout', + '--dtdvalid', gschema_dtd, + filebrowser_gschema_file, + ] + ) +endif + +custom_target( + 'filebrowser.plugin', + input: 'filebrowser.plugin.desktop.in', + output: 'filebrowser.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/filebrowser/messages.xml b/plugins/filebrowser/messages.xml new file mode 100644 index 0000000..e2b7137 --- /dev/null +++ b/plugins/filebrowser/messages.xml @@ -0,0 +1,47 @@ + + + + gio/gio.h + + + + gio/gio.h + + + + + + + + + + + + + + + + + + + + + gedit/gedit-menu-extension.h + + + + + + + plugins/filebrowser/gedit-file-browser-view.h + + + + gio/gio.h + + + + + + + diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-activation.c b/plugins/filebrowser/messages/gedit-file-browser-message-activation.c new file mode 100644 index 0000000..0138e0d --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-activation.c @@ -0,0 +1,105 @@ + +/* + * gedit-file-browser-message-activation.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-activation.h" + +enum +{ + PROP_0, + + PROP_ACTIVE, +}; + +struct _GeditFileBrowserMessageActivationPrivate +{ + gboolean active; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageActivation, + gedit_file_browser_message_activation, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageActivation)) + +static void +gedit_file_browser_message_activation_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageActivation *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION (obj); + + switch (prop_id) + { + case PROP_ACTIVE: + g_value_set_boolean (value, msg->priv->active); + break; + } +} + +static void +gedit_file_browser_message_activation_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageActivation *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION (obj); + + switch (prop_id) + { + case PROP_ACTIVE: + msg->priv->active = g_value_get_boolean (value); + break; + } +} + +static void +gedit_file_browser_message_activation_class_init (GeditFileBrowserMessageActivationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = gedit_file_browser_message_activation_get_property; + object_class->set_property = gedit_file_browser_message_activation_set_property; + + g_object_class_install_property (object_class, + PROP_ACTIVE, + g_param_spec_boolean ("active", + "Active", + "Active", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_activation_init (GeditFileBrowserMessageActivation *message) +{ + message->priv = gedit_file_browser_message_activation_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-activation.h b/plugins/filebrowser/messages/gedit-file-browser-message-activation.h new file mode 100644 index 0000000..bd3b1ed --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-activation.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-activation.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION (gedit_file_browser_message_activation_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\ + GeditFileBrowserMessageActivation)) +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\ + GeditFileBrowserMessageActivation const)) +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\ + GeditFileBrowserMessageActivationClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ACTIVATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ACTIVATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION)) +#define GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ACTIVATION,\ + GeditFileBrowserMessageActivationClass)) + +typedef struct _GeditFileBrowserMessageActivation GeditFileBrowserMessageActivation; +typedef struct _GeditFileBrowserMessageActivationClass GeditFileBrowserMessageActivationClass; +typedef struct _GeditFileBrowserMessageActivationPrivate GeditFileBrowserMessageActivationPrivate; + +struct _GeditFileBrowserMessageActivation +{ + GeditMessage parent; + + GeditFileBrowserMessageActivationPrivate *priv; +}; + +struct _GeditFileBrowserMessageActivationClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_activation_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_ACTIVATION_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c new file mode 100644 index 0000000..f66a32d --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.c @@ -0,0 +1,162 @@ + +/* + * gedit-file-browser-message-add-filter.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-add-filter.h" + +enum +{ + PROP_0, + + PROP_OBJECT_PATH, + PROP_METHOD, + PROP_ID, +}; + +struct _GeditFileBrowserMessageAddFilterPrivate +{ + gchar *object_path; + gchar *method; + guint id; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageAddFilter, + gedit_file_browser_message_add_filter, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageAddFilter)) + +static void +gedit_file_browser_message_add_filter_finalize (GObject *obj) +{ + GeditFileBrowserMessageAddFilter *msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj); + + g_free (msg->priv->object_path); + g_free (msg->priv->method); + + G_OBJECT_CLASS (gedit_file_browser_message_add_filter_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_add_filter_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageAddFilter *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + g_value_set_string (value, msg->priv->object_path); + break; + case PROP_METHOD: + g_value_set_string (value, msg->priv->method); + break; + case PROP_ID: + g_value_set_uint (value, msg->priv->id); + break; + } +} + +static void +gedit_file_browser_message_add_filter_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageAddFilter *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER (obj); + + switch (prop_id) + { + case PROP_OBJECT_PATH: + { + g_free (msg->priv->object_path); + msg->priv->object_path = g_value_dup_string (value); + break; + } + case PROP_METHOD: + { + g_free (msg->priv->method); + msg->priv->method = g_value_dup_string (value); + break; + } + case PROP_ID: + msg->priv->id = g_value_get_uint (value); + break; + } +} + +static void +gedit_file_browser_message_add_filter_class_init (GeditFileBrowserMessageAddFilterClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_add_filter_finalize; + + object_class->get_property = gedit_file_browser_message_add_filter_get_property; + object_class->set_property = gedit_file_browser_message_add_filter_set_property; + + g_object_class_install_property (object_class, + PROP_OBJECT_PATH, + g_param_spec_string ("object-path", + "Object Path", + "Object Path", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_METHOD, + g_param_spec_string ("method", + "Method", + "Method", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_uint ("id", + "Id", + "Id", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_add_filter_init (GeditFileBrowserMessageAddFilter *message) +{ + message->priv = gedit_file_browser_message_add_filter_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h new file mode 100644 index 0000000..4a5b35c --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-add-filter.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-add-filter.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER (gedit_file_browser_message_add_filter_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\ + GeditFileBrowserMessageAddFilter)) +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\ + GeditFileBrowserMessageAddFilter const)) +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\ + GeditFileBrowserMessageAddFilterClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ADD_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ADD_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER)) +#define GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ADD_FILTER,\ + GeditFileBrowserMessageAddFilterClass)) + +typedef struct _GeditFileBrowserMessageAddFilter GeditFileBrowserMessageAddFilter; +typedef struct _GeditFileBrowserMessageAddFilterClass GeditFileBrowserMessageAddFilterClass; +typedef struct _GeditFileBrowserMessageAddFilterPrivate GeditFileBrowserMessageAddFilterPrivate; + +struct _GeditFileBrowserMessageAddFilter +{ + GeditMessage parent; + + GeditFileBrowserMessageAddFilterPrivate *priv; +}; + +struct _GeditFileBrowserMessageAddFilterClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_add_filter_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_ADD_FILTER_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c new file mode 100644 index 0000000..4bb8682 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.c @@ -0,0 +1,128 @@ + +/* + * gedit-file-browser-message-extend-context-menu.c + * This file is part of gedit + * + * Copyright (C) 2014 - Paolo Borelli + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-extend-context-menu.h" +#include + +enum +{ + PROP_0, + + PROP_EXTENSION, +}; + +struct _GeditFileBrowserMessageExtendContextMenuPrivate +{ + GeditMenuExtension *extension; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageExtendContextMenu, + gedit_file_browser_message_extend_context_menu, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageExtendContextMenu)) + +static void +gedit_file_browser_message_extend_context_menu_finalize (GObject *obj) +{ + GeditFileBrowserMessageExtendContextMenu *msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj); + + if (msg->priv->extension) + { + g_object_unref (msg->priv->extension); + } + + G_OBJECT_CLASS (gedit_file_browser_message_extend_context_menu_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_extend_context_menu_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageExtendContextMenu *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj); + + switch (prop_id) + { + case PROP_EXTENSION: + g_value_set_object (value, msg->priv->extension); + break; + } +} + +static void +gedit_file_browser_message_extend_context_menu_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageExtendContextMenu *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (obj); + + switch (prop_id) + { + case PROP_EXTENSION: + { + if (msg->priv->extension) + { + g_object_unref (msg->priv->extension); + } + msg->priv->extension = g_value_dup_object (value); + break; + } + } +} + +static void +gedit_file_browser_message_extend_context_menu_class_init (GeditFileBrowserMessageExtendContextMenuClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_extend_context_menu_finalize; + + object_class->get_property = gedit_file_browser_message_extend_context_menu_get_property; + object_class->set_property = gedit_file_browser_message_extend_context_menu_set_property; + + g_object_class_install_property (object_class, + PROP_EXTENSION, + g_param_spec_object ("extension", + "Extension", + "Extension", + GEDIT_TYPE_MENU_EXTENSION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_extend_context_menu_init (GeditFileBrowserMessageExtendContextMenu *message) +{ + message->priv = gedit_file_browser_message_extend_context_menu_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h new file mode 100644 index 0000000..643485d --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-extend-context-menu.h @@ -0,0 +1,70 @@ + +/* + * gedit-file-browser-message-extend-context-menu.h + * This file is part of gedit + * + * Copyright (C) 2014 - Paolo Borelli + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU (gedit_file_browser_message_extend_context_menu_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\ + GeditFileBrowserMessageExtendContextMenu)) +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\ + GeditFileBrowserMessageExtendContextMenu const)) +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\ + GeditFileBrowserMessageExtendContextMenuClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU)) +#define GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU,\ + GeditFileBrowserMessageExtendContextMenuClass)) + +typedef struct _GeditFileBrowserMessageExtendContextMenu GeditFileBrowserMessageExtendContextMenu; +typedef struct _GeditFileBrowserMessageExtendContextMenuClass GeditFileBrowserMessageExtendContextMenuClass; +typedef struct _GeditFileBrowserMessageExtendContextMenuPrivate GeditFileBrowserMessageExtendContextMenuPrivate; + +struct _GeditFileBrowserMessageExtendContextMenu +{ + GeditMessage parent; + + GeditFileBrowserMessageExtendContextMenuPrivate *priv; +}; + +struct _GeditFileBrowserMessageExtendContextMenuClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_extend_context_menu_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_EXTEND_CONTEXT_MENU_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c new file mode 100644 index 0000000..b766ac1 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.c @@ -0,0 +1,127 @@ + +/* + * gedit-file-browser-message-get-root.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-get-root.h" +#include "gio/gio.h" + +enum +{ + PROP_0, + + PROP_LOCATION, +}; + +struct _GeditFileBrowserMessageGetRootPrivate +{ + GFile *location; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageGetRoot, + gedit_file_browser_message_get_root, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageGetRoot)) + +static void +gedit_file_browser_message_get_root_finalize (GObject *obj) +{ + GeditFileBrowserMessageGetRoot *msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj); + + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + + G_OBJECT_CLASS (gedit_file_browser_message_get_root_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_get_root_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageGetRoot *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj); + + switch (prop_id) + { + case PROP_LOCATION: + g_value_set_object (value, msg->priv->location); + break; + } +} + +static void +gedit_file_browser_message_get_root_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageGetRoot *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT (obj); + + switch (prop_id) + { + case PROP_LOCATION: + { + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + msg->priv->location = g_value_dup_object (value); + break; + } + } +} + +static void +gedit_file_browser_message_get_root_class_init (GeditFileBrowserMessageGetRootClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_get_root_finalize; + + object_class->get_property = gedit_file_browser_message_get_root_get_property; + object_class->set_property = gedit_file_browser_message_get_root_set_property; + + g_object_class_install_property (object_class, + PROP_LOCATION, + g_param_spec_object ("location", + "Location", + "Location", + G_TYPE_FILE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_get_root_init (GeditFileBrowserMessageGetRoot *message) +{ + message->priv = gedit_file_browser_message_get_root_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h new file mode 100644 index 0000000..8cc114d --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-root.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-get-root.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT (gedit_file_browser_message_get_root_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\ + GeditFileBrowserMessageGetRoot)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\ + GeditFileBrowserMessageGetRoot const)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\ + GeditFileBrowserMessageGetRootClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_ROOT,\ + GeditFileBrowserMessageGetRootClass)) + +typedef struct _GeditFileBrowserMessageGetRoot GeditFileBrowserMessageGetRoot; +typedef struct _GeditFileBrowserMessageGetRootClass GeditFileBrowserMessageGetRootClass; +typedef struct _GeditFileBrowserMessageGetRootPrivate GeditFileBrowserMessageGetRootPrivate; + +struct _GeditFileBrowserMessageGetRoot +{ + GeditMessage parent; + + GeditFileBrowserMessageGetRootPrivate *priv; +}; + +struct _GeditFileBrowserMessageGetRootClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_get_root_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_GET_ROOT_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c new file mode 100644 index 0000000..17fe757 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.c @@ -0,0 +1,127 @@ + +/* + * gedit-file-browser-message-get-view.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-get-view.h" +#include "plugins/filebrowser/gedit-file-browser-view.h" + +enum +{ + PROP_0, + + PROP_VIEW, +}; + +struct _GeditFileBrowserMessageGetViewPrivate +{ + GeditFileBrowserView *view; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageGetView, + gedit_file_browser_message_get_view, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageGetView)) + +static void +gedit_file_browser_message_get_view_finalize (GObject *obj) +{ + GeditFileBrowserMessageGetView *msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj); + + if (msg->priv->view) + { + g_object_unref (msg->priv->view); + } + + G_OBJECT_CLASS (gedit_file_browser_message_get_view_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_get_view_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageGetView *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj); + + switch (prop_id) + { + case PROP_VIEW: + g_value_set_object (value, msg->priv->view); + break; + } +} + +static void +gedit_file_browser_message_get_view_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageGetView *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW (obj); + + switch (prop_id) + { + case PROP_VIEW: + { + if (msg->priv->view) + { + g_object_unref (msg->priv->view); + } + msg->priv->view = g_value_dup_object (value); + break; + } + } +} + +static void +gedit_file_browser_message_get_view_class_init (GeditFileBrowserMessageGetViewClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_get_view_finalize; + + object_class->get_property = gedit_file_browser_message_get_view_get_property; + object_class->set_property = gedit_file_browser_message_get_view_set_property; + + g_object_class_install_property (object_class, + PROP_VIEW, + g_param_spec_object ("view", + "View", + "View", + GEDIT_TYPE_FILE_BROWSER_VIEW, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_get_view_init (GeditFileBrowserMessageGetView *message) +{ + message->priv = gedit_file_browser_message_get_view_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h new file mode 100644 index 0000000..452abbc --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-get-view.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-get-view.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW (gedit_file_browser_message_get_view_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\ + GeditFileBrowserMessageGetView)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\ + GeditFileBrowserMessageGetView const)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\ + GeditFileBrowserMessageGetViewClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_GET_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW)) +#define GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_GET_VIEW,\ + GeditFileBrowserMessageGetViewClass)) + +typedef struct _GeditFileBrowserMessageGetView GeditFileBrowserMessageGetView; +typedef struct _GeditFileBrowserMessageGetViewClass GeditFileBrowserMessageGetViewClass; +typedef struct _GeditFileBrowserMessageGetViewPrivate GeditFileBrowserMessageGetViewPrivate; + +struct _GeditFileBrowserMessageGetView +{ + GeditMessage parent; + + GeditFileBrowserMessageGetViewPrivate *priv; +}; + +struct _GeditFileBrowserMessageGetViewClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_get_view_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_GET_VIEW_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c new file mode 100644 index 0000000..36c33c5 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.c @@ -0,0 +1,190 @@ + +/* + * gedit-file-browser-message-id-location.c + * This file is part of gedit + * + * Copyright (C) 2013 - Garrett Regier + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-id-location.h" +#include "gio/gio.h" + +enum +{ + PROP_0, + + PROP_ID, + PROP_NAME, + PROP_LOCATION, + PROP_IS_DIRECTORY, +}; + +struct _GeditFileBrowserMessageIdLocationPrivate +{ + gchar *id; + gchar *name; + GFile *location; + gboolean is_directory; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageIdLocation, + gedit_file_browser_message_id_location, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageIdLocation)) + +static void +gedit_file_browser_message_id_location_finalize (GObject *obj) +{ + GeditFileBrowserMessageIdLocation *msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj); + + g_free (msg->priv->id); + g_free (msg->priv->name); + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + + G_OBJECT_CLASS (gedit_file_browser_message_id_location_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_id_location_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageIdLocation *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, msg->priv->id); + break; + case PROP_NAME: + g_value_set_string (value, msg->priv->name); + break; + case PROP_LOCATION: + g_value_set_object (value, msg->priv->location); + break; + case PROP_IS_DIRECTORY: + g_value_set_boolean (value, msg->priv->is_directory); + break; + } +} + +static void +gedit_file_browser_message_id_location_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageIdLocation *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION (obj); + + switch (prop_id) + { + case PROP_ID: + { + g_free (msg->priv->id); + msg->priv->id = g_value_dup_string (value); + break; + } + case PROP_NAME: + { + g_free (msg->priv->name); + msg->priv->name = g_value_dup_string (value); + break; + } + case PROP_LOCATION: + { + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + msg->priv->location = g_value_dup_object (value); + break; + } + case PROP_IS_DIRECTORY: + msg->priv->is_directory = g_value_get_boolean (value); + break; + } +} + +static void +gedit_file_browser_message_id_location_class_init (GeditFileBrowserMessageIdLocationClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_id_location_finalize; + + object_class->get_property = gedit_file_browser_message_id_location_get_property; + object_class->set_property = gedit_file_browser_message_id_location_set_property; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "Id", + "Id", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_NAME, + g_param_spec_string ("name", + "Name", + "Name", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_LOCATION, + g_param_spec_object ("location", + "Location", + "Location", + G_TYPE_FILE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_IS_DIRECTORY, + g_param_spec_boolean ("is-directory", + "Is Directory", + "Is Directory", + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_id_location_init (GeditFileBrowserMessageIdLocation *message) +{ + message->priv = gedit_file_browser_message_id_location_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h new file mode 100644 index 0000000..0af4c77 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-id-location.h @@ -0,0 +1,70 @@ + +/* + * gedit-file-browser-message-id-location.h + * This file is part of gedit + * + * Copyright (C) 2013 - Garrett Regier + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION (gedit_file_browser_message_id_location_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\ + GeditFileBrowserMessageIdLocation)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\ + GeditFileBrowserMessageIdLocation const)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\ + GeditFileBrowserMessageIdLocationClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_LOCATION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_LOCATION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID_LOCATION,\ + GeditFileBrowserMessageIdLocationClass)) + +typedef struct _GeditFileBrowserMessageIdLocation GeditFileBrowserMessageIdLocation; +typedef struct _GeditFileBrowserMessageIdLocationClass GeditFileBrowserMessageIdLocationClass; +typedef struct _GeditFileBrowserMessageIdLocationPrivate GeditFileBrowserMessageIdLocationPrivate; + +struct _GeditFileBrowserMessageIdLocation +{ + GeditMessage parent; + + GeditFileBrowserMessageIdLocationPrivate *priv; +}; + +struct _GeditFileBrowserMessageIdLocationClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_id_location_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_ID_LOCATION_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id.c b/plugins/filebrowser/messages/gedit-file-browser-message-id.c new file mode 100644 index 0000000..fec490f --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-id.c @@ -0,0 +1,107 @@ + +/* + * gedit-file-browser-message-id.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-id.h" + +enum +{ + PROP_0, + + PROP_ID, +}; + +struct _GeditFileBrowserMessageIdPrivate +{ + guint id; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageId, + gedit_file_browser_message_id, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageId)) + +static void +gedit_file_browser_message_id_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageId *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ID (obj); + + switch (prop_id) + { + case PROP_ID: + g_value_set_uint (value, msg->priv->id); + break; + } +} + +static void +gedit_file_browser_message_id_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageId *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_ID (obj); + + switch (prop_id) + { + case PROP_ID: + msg->priv->id = g_value_get_uint (value); + break; + } +} + +static void +gedit_file_browser_message_id_class_init (GeditFileBrowserMessageIdClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = gedit_file_browser_message_id_get_property; + object_class->set_property = gedit_file_browser_message_id_set_property; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_uint ("id", + "Id", + "Id", + 0, + G_MAXUINT, + 0, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_id_init (GeditFileBrowserMessageId *message) +{ + message->priv = gedit_file_browser_message_id_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-id.h b/plugins/filebrowser/messages/gedit-file-browser-message-id.h new file mode 100644 index 0000000..8351c3b --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-id.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-id.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_ID_H +#define GEDIT_FILE_BROWSER_MESSAGE_ID_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID (gedit_file_browser_message_id_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_ID(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\ + GeditFileBrowserMessageId)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\ + GeditFileBrowserMessageId const)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\ + GeditFileBrowserMessageIdClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_ID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID)) +#define GEDIT_FILE_BROWSER_MESSAGE_ID_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_ID,\ + GeditFileBrowserMessageIdClass)) + +typedef struct _GeditFileBrowserMessageId GeditFileBrowserMessageId; +typedef struct _GeditFileBrowserMessageIdClass GeditFileBrowserMessageIdClass; +typedef struct _GeditFileBrowserMessageIdPrivate GeditFileBrowserMessageIdPrivate; + +struct _GeditFileBrowserMessageId +{ + GeditMessage parent; + + GeditFileBrowserMessageIdPrivate *priv; +}; + +struct _GeditFileBrowserMessageIdClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_id_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_ID_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c new file mode 100644 index 0000000..8e63042 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.c @@ -0,0 +1,142 @@ + +/* + * gedit-file-browser-message-set-emblem.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-set-emblem.h" + +enum +{ + PROP_0, + + PROP_ID, + PROP_EMBLEM, +}; + +struct _GeditFileBrowserMessageSetEmblemPrivate +{ + gchar *id; + gchar *emblem; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetEmblem, + gedit_file_browser_message_set_emblem, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageSetEmblem)) + +static void +gedit_file_browser_message_set_emblem_finalize (GObject *obj) +{ + GeditFileBrowserMessageSetEmblem *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj); + + g_free (msg->priv->id); + g_free (msg->priv->emblem); + + G_OBJECT_CLASS (gedit_file_browser_message_set_emblem_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_set_emblem_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetEmblem *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, msg->priv->id); + break; + case PROP_EMBLEM: + g_value_set_string (value, msg->priv->emblem); + break; + } +} + +static void +gedit_file_browser_message_set_emblem_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetEmblem *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM (obj); + + switch (prop_id) + { + case PROP_ID: + { + g_free (msg->priv->id); + msg->priv->id = g_value_dup_string (value); + break; + } + case PROP_EMBLEM: + { + g_free (msg->priv->emblem); + msg->priv->emblem = g_value_dup_string (value); + break; + } + } +} + +static void +gedit_file_browser_message_set_emblem_class_init (GeditFileBrowserMessageSetEmblemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_set_emblem_finalize; + + object_class->get_property = gedit_file_browser_message_set_emblem_get_property; + object_class->set_property = gedit_file_browser_message_set_emblem_set_property; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "Id", + "Id", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_EMBLEM, + g_param_spec_string ("emblem", + "Emblem", + "Emblem", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_set_emblem_init (GeditFileBrowserMessageSetEmblem *message) +{ + message->priv = gedit_file_browser_message_set_emblem_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h new file mode 100644 index 0000000..6daa795 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-emblem.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-set-emblem.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM (gedit_file_browser_message_set_emblem_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\ + GeditFileBrowserMessageSetEmblem)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\ + GeditFileBrowserMessageSetEmblem const)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\ + GeditFileBrowserMessageSetEmblemClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_EMBLEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_EMBLEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_EMBLEM,\ + GeditFileBrowserMessageSetEmblemClass)) + +typedef struct _GeditFileBrowserMessageSetEmblem GeditFileBrowserMessageSetEmblem; +typedef struct _GeditFileBrowserMessageSetEmblemClass GeditFileBrowserMessageSetEmblemClass; +typedef struct _GeditFileBrowserMessageSetEmblemPrivate GeditFileBrowserMessageSetEmblemPrivate; + +struct _GeditFileBrowserMessageSetEmblem +{ + GeditMessage parent; + + GeditFileBrowserMessageSetEmblemPrivate *priv; +}; + +struct _GeditFileBrowserMessageSetEmblemClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_set_emblem_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_EMBLEM_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c new file mode 100644 index 0000000..3b14d16 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.c @@ -0,0 +1,143 @@ + +/* + * gedit-file-browser-message-set-markup.c + * This file is part of gedit + * + * Copyright (C) 2013 - Garrett Regier + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-set-markup.h" + +enum +{ + PROP_0, + + PROP_ID, + PROP_MARKUP, +}; + +struct _GeditFileBrowserMessageSetMarkupPrivate +{ + gchar *id; + gchar *markup; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetMarkup, + gedit_file_browser_message_set_markup, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageSetMarkup)) + +static void +gedit_file_browser_message_set_markup_finalize (GObject *obj) +{ + GeditFileBrowserMessageSetMarkup *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj); + + g_free (msg->priv->id); + g_free (msg->priv->markup); + + G_OBJECT_CLASS (gedit_file_browser_message_set_markup_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_set_markup_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetMarkup *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj); + + switch (prop_id) + { + case PROP_ID: + g_value_set_string (value, msg->priv->id); + break; + case PROP_MARKUP: + g_value_set_string (value, msg->priv->markup); + break; + } +} + +static void +gedit_file_browser_message_set_markup_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetMarkup *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP (obj); + + switch (prop_id) + { + case PROP_ID: + { + g_free (msg->priv->id); + msg->priv->id = g_value_dup_string (value); + break; + } + case PROP_MARKUP: + { + g_free (msg->priv->markup); + msg->priv->markup = g_value_dup_string (value); + break; + } + } +} + +static void +gedit_file_browser_message_set_markup_class_init (GeditFileBrowserMessageSetMarkupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_set_markup_finalize; + + object_class->get_property = gedit_file_browser_message_set_markup_get_property; + object_class->set_property = gedit_file_browser_message_set_markup_set_property; + + g_object_class_install_property (object_class, + PROP_ID, + g_param_spec_string ("id", + "Id", + "Id", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_MARKUP, + g_param_spec_string ("markup", + "Markup", + "Markup", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_set_markup_init (GeditFileBrowserMessageSetMarkup *message) +{ + message->priv = gedit_file_browser_message_set_markup_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h new file mode 100644 index 0000000..ab9a1dc --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-markup.h @@ -0,0 +1,70 @@ + +/* + * gedit-file-browser-message-set-markup.h + * This file is part of gedit + * + * Copyright (C) 2013 - Garrett Regier + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP (gedit_file_browser_message_set_markup_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\ + GeditFileBrowserMessageSetMarkup)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\ + GeditFileBrowserMessageSetMarkup const)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\ + GeditFileBrowserMessageSetMarkupClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_MARKUP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_MARKUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_MARKUP,\ + GeditFileBrowserMessageSetMarkupClass)) + +typedef struct _GeditFileBrowserMessageSetMarkup GeditFileBrowserMessageSetMarkup; +typedef struct _GeditFileBrowserMessageSetMarkupClass GeditFileBrowserMessageSetMarkupClass; +typedef struct _GeditFileBrowserMessageSetMarkupPrivate GeditFileBrowserMessageSetMarkupPrivate; + +struct _GeditFileBrowserMessageSetMarkup +{ + GeditMessage parent; + + GeditFileBrowserMessageSetMarkupPrivate *priv; +}; + +struct _GeditFileBrowserMessageSetMarkupClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_set_markup_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_MARKUP_H */ diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c new file mode 100644 index 0000000..cb3891b --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.c @@ -0,0 +1,149 @@ + +/* + * gedit-file-browser-message-set-root.c + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#include "config.h" + +#include "gedit-file-browser-message-set-root.h" +#include "gio/gio.h" + +enum +{ + PROP_0, + + PROP_LOCATION, + PROP_VIRTUAL, +}; + +struct _GeditFileBrowserMessageSetRootPrivate +{ + GFile *location; + gchar *virtual; +}; + +G_DEFINE_TYPE_EXTENDED (GeditFileBrowserMessageSetRoot, + gedit_file_browser_message_set_root, + GEDIT_TYPE_MESSAGE, + 0, + G_ADD_PRIVATE (GeditFileBrowserMessageSetRoot)) + +static void +gedit_file_browser_message_set_root_finalize (GObject *obj) +{ + GeditFileBrowserMessageSetRoot *msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj); + + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + g_free (msg->priv->virtual); + + G_OBJECT_CLASS (gedit_file_browser_message_set_root_parent_class)->finalize (obj); +} + +static void +gedit_file_browser_message_set_root_get_property (GObject *obj, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetRoot *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj); + + switch (prop_id) + { + case PROP_LOCATION: + g_value_set_object (value, msg->priv->location); + break; + case PROP_VIRTUAL: + g_value_set_string (value, msg->priv->virtual); + break; + } +} + +static void +gedit_file_browser_message_set_root_set_property (GObject *obj, + guint prop_id, + GValue const *value, + GParamSpec *pspec) +{ + GeditFileBrowserMessageSetRoot *msg; + + msg = GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT (obj); + + switch (prop_id) + { + case PROP_LOCATION: + { + if (msg->priv->location) + { + g_object_unref (msg->priv->location); + } + msg->priv->location = g_value_dup_object (value); + break; + } + case PROP_VIRTUAL: + { + g_free (msg->priv->virtual); + msg->priv->virtual = g_value_dup_string (value); + break; + } + } +} + +static void +gedit_file_browser_message_set_root_class_init (GeditFileBrowserMessageSetRootClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gedit_file_browser_message_set_root_finalize; + + object_class->get_property = gedit_file_browser_message_set_root_get_property; + object_class->set_property = gedit_file_browser_message_set_root_set_property; + + g_object_class_install_property (object_class, + PROP_LOCATION, + g_param_spec_object ("location", + "Location", + "Location", + G_TYPE_FILE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_VIRTUAL, + g_param_spec_string ("virtual", + "Virtual", + "Virtual", + NULL, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); +} + +static void +gedit_file_browser_message_set_root_init (GeditFileBrowserMessageSetRoot *message) +{ + message->priv = gedit_file_browser_message_set_root_get_instance_private (message); +} diff --git a/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h new file mode 100644 index 0000000..8b61c50 --- /dev/null +++ b/plugins/filebrowser/messages/gedit-file-browser-message-set-root.h @@ -0,0 +1,69 @@ + +/* + * gedit-file-browser-message-set-root.h + * This file is part of gedit + * + * Copyright (C) 2014 - Jesse van den Kieboom + * + * 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 + */ + +#ifndef GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H + +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT (gedit_file_browser_message_set_root_get_type ()) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\ + GeditFileBrowserMessageSetRoot)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\ + GeditFileBrowserMessageSetRoot const)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\ + GeditFileBrowserMessageSetRootClass)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_ROOT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT)) +#define GEDIT_IS_FILE_BROWSER_MESSAGE_SET_ROOT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT)) +#define GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\ + GEDIT_TYPE_FILE_BROWSER_MESSAGE_SET_ROOT,\ + GeditFileBrowserMessageSetRootClass)) + +typedef struct _GeditFileBrowserMessageSetRoot GeditFileBrowserMessageSetRoot; +typedef struct _GeditFileBrowserMessageSetRootClass GeditFileBrowserMessageSetRootClass; +typedef struct _GeditFileBrowserMessageSetRootPrivate GeditFileBrowserMessageSetRootPrivate; + +struct _GeditFileBrowserMessageSetRoot +{ + GeditMessage parent; + + GeditFileBrowserMessageSetRootPrivate *priv; +}; + +struct _GeditFileBrowserMessageSetRootClass +{ + GeditMessageClass parent_class; +}; + +GType gedit_file_browser_message_set_root_get_type (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* GEDIT_FILE_BROWSER_MESSAGE_SET_ROOT_H */ diff --git a/plugins/filebrowser/messages/meson.build b/plugins/filebrowser/messages/meson.build new file mode 100644 index 0000000..ec4fb84 --- /dev/null +++ b/plugins/filebrowser/messages/meson.build @@ -0,0 +1,25 @@ +libfilebrowser_public_h += files( + 'gedit-file-browser-message-activation.h', + 'gedit-file-browser-message-add-filter.h', + 'gedit-file-browser-message-extend-context-menu.h', + 'gedit-file-browser-message-get-root.h', + 'gedit-file-browser-message-get-view.h', + 'gedit-file-browser-message-id.h', + 'gedit-file-browser-message-id-location.h', + 'gedit-file-browser-message-set-emblem.h', + 'gedit-file-browser-message-set-markup.h', + 'gedit-file-browser-message-set-root.h', +) + +libfilebrowser_sources += files( + 'gedit-file-browser-message-activation.c', + 'gedit-file-browser-message-add-filter.c', + 'gedit-file-browser-message-extend-context-menu.c', + 'gedit-file-browser-message-get-root.c', + 'gedit-file-browser-message-get-view.c', + 'gedit-file-browser-message-id.c', + 'gedit-file-browser-message-id-location.c', + 'gedit-file-browser-message-set-emblem.c', + 'gedit-file-browser-message-set-markup.c', + 'gedit-file-browser-message-set-root.c', +) diff --git a/plugins/filebrowser/messages/messages.h b/plugins/filebrowser/messages/messages.h new file mode 100644 index 0000000..4dc0f51 --- /dev/null +++ b/plugins/filebrowser/messages/messages.h @@ -0,0 +1,16 @@ +#ifndef GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H +#define GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H + +#include "gedit-file-browser-message-activation.h" +#include "gedit-file-browser-message-add-filter.h" +#include "gedit-file-browser-message-extend-context-menu.h" +#include "gedit-file-browser-message-get-root.h" +#include "gedit-file-browser-message-get-view.h" +#include "gedit-file-browser-message-id.h" +#include "gedit-file-browser-message-id-location.h" +#include "gedit-file-browser-message-set-emblem.h" +#include "gedit-file-browser-message-set-markup.h" +#include "gedit-file-browser-message-set-root.h" + +#endif /* GEDIT_FILE_BROWER_MESSAGES_MESSAGES_H */ + diff --git a/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml b/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml new file mode 100644 index 0000000..8198802 --- /dev/null +++ b/plugins/filebrowser/org.gnome.gedit.plugins.filebrowser.gschema.xml @@ -0,0 +1,55 @@ + + + + true + Open With Tree View + Open the tree view when the file browser plugin gets loaded instead of the bookmarks view + + + '' + File Browser Root Directory + The file browser root directory to use when loading the file browser plugin and onload/tree_view is TRUE. + + + '' + 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. + + + false + Enable Restore of Remote Locations + Sets whether to enable restoring of remote locations. + + + 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 Nautilus, etc.) + + + ['hide-hidden', 'hide-binary'] + File Browser Filter Mode + This value determines what files get filtered from the file browser. Valid values are: none (filter nothing), hide-hidden (filter hidden files) and hide-binary (filter binary files). + + + '' + File Browser Filter Pattern + The filter pattern to filter the file browser with. This filter works on top of the filter_mode. + + + ['*.la', '*.lo'] + File Browser Binary Patterns + The supplemental patterns to use when filtering binary files. + + + + + + + + + + + 'double' + + + diff --git a/plugins/filebrowser/resources/gedit-file-browser.gresource.xml b/plugins/filebrowser/resources/gedit-file-browser.gresource.xml new file mode 100644 index 0000000..273bd26 --- /dev/null +++ b/plugins/filebrowser/resources/gedit-file-browser.gresource.xml @@ -0,0 +1,7 @@ + + + + ui/gedit-file-browser-menus.ui + ui/gedit-file-browser-widget.ui + + diff --git a/plugins/filebrowser/resources/meson.build b/plugins/filebrowser/resources/meson.build new file mode 100644 index 0000000..da2b577 --- /dev/null +++ b/plugins/filebrowser/resources/meson.build @@ -0,0 +1,8 @@ +libfilebrowser_res = gnome.compile_resources( + 'gedit-file-browser-resources', + 'gedit-file-browser.gresource.xml', +) + +libfilebrowser_sources += [ + libfilebrowser_res.get(0), +] diff --git a/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui b/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui new file mode 100644 index 0000000..9134268 --- /dev/null +++ b/plugins/filebrowser/resources/ui/gedit-file-browser-menus.ui @@ -0,0 +1,84 @@ + + + + +
+ + _Open + browser.open + +
+
+ + _Set Root to Active Document + browser.set_active_root + +
+
+ + _New Folder + browser.new_folder + + + New F_ile + browser.new_file + +
+
+ + _Rename… + browser.rename + + + _Move to Trash + browser.move_to_trash + + + _Delete + browser.delete + +
+
+ + Re_fresh View + browser.refresh_view + + + _View Folder + browser.view_folder + + + _Open in Terminal + browser.open_in_terminal + +
+ + _Filter +
+ + Show _Hidden + browser.show_hidden + + + Show _Binary + browser.show_binary + + + Match Filename + browser.show_match_filename + +
+
+
+ extension-section +
+
+ +
+ + _Set Root to Active Document + browser.set_active_root + +
+
+
diff --git a/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui b/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui new file mode 100644 index 0000000..e4fb300 --- /dev/null +++ b/plugins/filebrowser/resources/ui/gedit-file-browser-widget.ui @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + True + True + 300 + 300 + + + True + True + in + 6 + never + True + True + + + True + False + True + locations_model + True + + + + + + + + + + + + end + + + 2 + + + + + + + + + + + + + + True + False + + + True + False + + + + True + go-previous-symbolic + 2 + + + True + go-next-symbolic + 2 + + + True + go-up-symbolic + 2 + + diff --git a/plugins/generate-list.sh b/plugins/generate-list.sh new file mode 100755 index 0000000..c77cab0 --- /dev/null +++ b/plugins/generate-list.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2020 Sébastien Wilmet +# SPDX-License-Identifier: GPL-3.0-or-later + +# This script generates a Markdown file with the names and descriptions of all +# official gedit plugins. + +write_list_for_plugins_dir() { + plugins_dir=$1 + + for plugin_desktop_file in `find "$plugins_dir" -name '*.plugin.desktop*'` + do + name=`grep -P '^Name=' "$plugin_desktop_file" | cut -d'=' -f2` + echo -n "- **$name** - " + + desc=`grep -P '^Description=' "$plugin_desktop_file" | cut -d'=' -f2` + echo "*$desc*" + done | sort +} + +write_content() { + echo 'gedit plugins' + echo '=============' + echo + echo 'Core plugins' + echo '------------' + echo + echo 'Plugins that are distributed with gedit itself.' + echo + + write_list_for_plugins_dir '.' + + echo + echo 'gedit-plugins package' + echo '---------------------' + echo + echo 'The gedit-plugins package contains useful plugins that are (most' + echo 'of the time) too specific to be distributed with gedit itself.' + echo + + write_list_for_plugins_dir '../../gedit-plugins/plugins' +} + +write_content > list-of-gedit-plugins.md diff --git a/plugins/list-of-gedit-plugins.md b/plugins/list-of-gedit-plugins.md new file mode 100644 index 0000000..5356cd4 --- /dev/null +++ b/plugins/list-of-gedit-plugins.md @@ -0,0 +1,42 @@ +gedit plugins +============= + +Core plugins +------------ + +Plugins that are distributed with gedit itself. + +- **Document Statistics** - *Report the number of words, lines and characters in a document.* +- **External Tools** - *Execute external commands and shell scripts.* +- **File Browser Panel** - *Easy file access from the side panel.* +- **Insert Date/Time** - *Inserts current date and time at the cursor position.* +- **Modelines** - *Emacs, Kate and Vim-style modelines support for gedit.* +- **Python Console** - *Interactive Python console standing in the bottom panel.* +- **Quick Highlight** - *Highlights every occurrences of selected text.* +- **Quick Open** - *Quickly open files.* +- **Snippets** - *Insert often-used pieces of text in a fast way.* +- **Sort** - *Sorts a document or selected text.* +- **Spell Checker** - *Checks the spelling of the current document.* + +gedit-plugins package +--------------------- + +The gedit-plugins package contains useful plugins that are (most +of the time) too specific to be distributed with gedit itself. + +- **Bookmarks** - *Easy document navigation with bookmarks* +- **Bracket Completion** - *Automatically adds closing brackets.* +- **Character Map** - *Insert special characters just by clicking on them.* +- **Code Comment** - *Comment out or uncomment a selected block of code.* +- **Color Picker** - *Pick a color from a dialog and insert its hexadecimal representation.* +- **Color Scheme Editor** - *Source code color scheme editor* +- **Draw Spaces** - *Draw spaces and tabs* +- **Embedded Terminal** - *Embed a terminal in the bottom pane.* +- **Git** - *Highlight lines that have been changed since the last commit* +- **Join/Split Lines** - *Join several lines or split long ones* +- **Multi Edit** - *Edit document in multiple places at once* +- **Session Saver** - *Save and restore your working sessions* +- **Smart Spaces** - *Forget you’re not using tabulations.* +- **SyncTeX** - *Synchronize between LaTeX and PDF with gedit and evince.* +- **Text Size** - *Easily increase and decrease the text size* +- **Word Completion** - *Word completion using the completion framework* diff --git a/plugins/meson.build b/plugins/meson.build new file mode 100644 index 0000000..50bc5d3 --- /dev/null +++ b/plugins/meson.build @@ -0,0 +1,31 @@ +# Keep the autotools convention for shared module suffix because GModule +# depends on it: https://gitlab.gnome.org/GNOME/glib/issues/520 +module_suffix = [] +if host_machine.system() == 'darwin' + module_suffix = 'so' +endif + +msgfmt_plugin_cmd = [ + find_program('msgfmt'), + '--desktop', + '--keyword=Name', + '--keyword=Description', + '--template=@INPUT@', + '-d', join_paths(srcdir, 'po'), + '--output=@OUTPUT@' +] + +subdir('docinfo') +subdir('filebrowser') +subdir('modelines') +subdir('pythonconsole') +subdir('quickhighlight') +subdir('quickopen') +subdir('snippets') +subdir('sort') +subdir('spell') +subdir('time') + +if get_option('plugin_externaltools') + subdir('externaltools') +endif diff --git a/plugins/modelines/gedit-modeline-plugin.c b/plugins/modelines/gedit-modeline-plugin.c new file mode 100644 index 0000000..376d996 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.c @@ -0,0 +1,225 @@ +/* + * gedit-modeline-plugin.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2010 - 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, see . + */ + +#include "config.h" + +#include +#include +#include "gedit-modeline-plugin.h" +#include "modeline-parser.h" + +#include +#include +#include + +struct _GeditModelinePluginPrivate +{ + GeditView *view; + + gulong document_loaded_handler_id; + gulong document_saved_handler_id; +}; + +enum +{ + PROP_0, + PROP_VIEW +}; + +static void gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditModelinePlugin, + gedit_modeline_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_VIEW_ACTIVATABLE, + gedit_view_activatable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditModelinePlugin)) + +static void +gedit_modeline_plugin_constructed (GObject *object) +{ + gchar *data_dir; + + data_dir = peas_extension_base_get_data_dir (PEAS_EXTENSION_BASE (object)); + + modeline_parser_init (data_dir); + + g_free (data_dir); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->constructed (object); +} + +static void +gedit_modeline_plugin_init (GeditModelinePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin initializing"); + + plugin->priv = gedit_modeline_plugin_get_instance_private (plugin); + +} + +static void +gedit_modeline_plugin_dispose (GObject *object) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin disposing"); + + g_clear_object (&plugin->priv->view); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->dispose (object); +} + +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 +gedit_modeline_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + plugin->priv->view = GEDIT_VIEW (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_modeline_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + g_value_set_object (value, plugin->priv->view); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_document_loaded_or_saved (GeditDocument *document, + GtkSourceView *view) +{ + modeline_parser_apply_modeline (view); +} + +static void +gedit_modeline_plugin_activate (GeditViewActivatable *activatable) +{ + GeditModelinePlugin *plugin; + GtkTextBuffer *doc; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_MODELINE_PLUGIN (activatable); + + modeline_parser_apply_modeline (GTK_SOURCE_VIEW (plugin->priv->view)); + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + plugin->priv->document_loaded_handler_id = + g_signal_connect (doc, "loaded", + G_CALLBACK (on_document_loaded_or_saved), + plugin->priv->view); + plugin->priv->document_saved_handler_id = + g_signal_connect (doc, "saved", + G_CALLBACK (on_document_loaded_or_saved), + plugin->priv->view); +} + +static void +gedit_modeline_plugin_deactivate (GeditViewActivatable *activatable) +{ + GeditModelinePlugin *plugin; + GtkTextBuffer *doc; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_MODELINE_PLUGIN (activatable); + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + g_signal_handler_disconnect (doc, plugin->priv->document_loaded_handler_id); + g_signal_handler_disconnect (doc, plugin->priv->document_saved_handler_id); +} + +static void +gedit_modeline_plugin_class_init (GeditModelinePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gedit_modeline_plugin_constructed; + object_class->dispose = gedit_modeline_plugin_dispose; + object_class->finalize = gedit_modeline_plugin_finalize; + object_class->set_property = gedit_modeline_plugin_set_property; + object_class->get_property = gedit_modeline_plugin_get_property; + + g_object_class_override_property (object_class, PROP_VIEW, "view"); +} + +static void +gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface) +{ + iface->activate = gedit_modeline_plugin_activate; + iface->deactivate = gedit_modeline_plugin_deactivate; +} + +static void +gedit_modeline_plugin_class_finalize (GeditModelinePluginClass *klass) +{ +} + + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_modeline_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_VIEW_ACTIVATABLE, + GEDIT_TYPE_MODELINE_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/gedit-modeline-plugin.h b/plugins/modelines/gedit-modeline-plugin.h new file mode 100644 index 0000000..d3557e4 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.h @@ -0,0 +1,60 @@ +/* + * 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, see . + */ + +#ifndef GEDIT_MODELINE_PLUGIN_H +#define GEDIT_MODELINE_PLUGIN_H + +#include +#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)) + +typedef struct _GeditModelinePlugin GeditModelinePlugin; +typedef struct _GeditModelinePluginPrivate GeditModelinePluginPrivate; +typedef struct _GeditModelinePluginClass GeditModelinePluginClass; + +struct _GeditModelinePlugin { + PeasExtensionBase parent; + + /*< private >*/ + GeditModelinePluginPrivate *priv; +}; + +struct _GeditModelinePluginClass { + PeasExtensionBaseClass parent_class; +}; + +GType gedit_modeline_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_MODELINE_PLUGIN_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/language-mappings b/plugins/modelines/language-mappings new file mode 100644 index 0000000..47d0029 --- /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/meson.build b/plugins/modelines/meson.build new file mode 100644 index 0000000..3ff0e84 --- /dev/null +++ b/plugins/modelines/meson.build @@ -0,0 +1,42 @@ +libmodelines_sources = files( + 'gedit-modeline-plugin.c', + 'modeline-parser.c', +) + +libmodelines_deps = [ + libgedit_dep, +] + +libmodelines_sha = shared_module( + 'modelines', + sources: libmodelines_sources, + include_directories: root_include_dir, + dependencies: libmodelines_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +custom_target( + 'modelines.plugin', + input: 'modelines.plugin.desktop.in', + output: 'modelines.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) + +install_data( + 'language-mappings', + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'modelines', + ) +) diff --git a/plugins/modelines/modeline-parser.c b/plugins/modelines/modeline-parser.c new file mode 100644 index 0000000..989e111 --- /dev/null +++ b/plugins/modelines/modeline-parser.c @@ -0,0 +1,899 @@ +/* + * 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, see . + */ + +#include +#include +#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 = NULL; +static GHashTable *emacs_languages = NULL; +static GHashTable *kate_languages = NULL; + +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) +{ + if (modelines_data_dir == NULL) + { + modelines_data_dir = g_strdup (data_dir); + } +} + +void +modeline_parser_shutdown () +{ + if (vim_languages != NULL) + g_hash_table_unref (vim_languages); + + if (emacs_languages != NULL) + g_hash_table_unref (emacs_languages); + + if (kate_languages != NULL) + g_hash_table_unref (kate_languages); + + vim_languages = NULL; + emacs_languages = NULL; + kate_languages = NULL; + + g_free (modelines_data_dir); + modelines_data_dir = NULL; +} + +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++) + { + /* steal the name string */ + 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 = NULL; + + name = g_ascii_strdown (language_name, -1); + + if (mapping != NULL) + { + language_id = g_hash_table_lookup (mapping, name); + + if (language_id != NULL) + { + g_free (name); + return g_strdup (language_id); + } + } + + /* 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 || + strcmp (key->str, "tw") == 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 || + strcmp (key->str, "c-basic-offset") == 0 || + strcmp (key->str, "js-indent-level") == 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 *line, + gint line_number, + gint line_count, + ModelineOptions *options) +{ + gchar *s = line; + + /* look for the beginning of a modeline */ + while (s != NULL && *s != '\0') + { + if (s > line && !g_ascii_isspace (*(s - 1))) + { + s++; + 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); + } + else + { + s++; + } + } +} + +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; + GSettings *settings; + + 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) + { + if (g_ascii_strcasecmp (options.language_id, "text") == 0) + { + gedit_document_set_language (GEDIT_DOCUMENT (buffer), + NULL); + } + else + { + GtkSourceLanguageManager *manager; + GtkSourceLanguage *language; + + manager = gtk_source_language_manager_get_default (); + + language = gtk_source_language_manager_get_language + (manager, options.language_id); + if (language != NULL) + { + gedit_document_set_language (GEDIT_DOCUMENT (buffer), + language); + } + else + { + gedit_debug_message (DEBUG_PLUGINS, + "Unknown language `%s'", + options.language_id); + } + } + } + + ModelineOptions *previous = g_object_get_data (G_OBJECT (buffer), + MODELINE_OPTIONS_DATA_KEY); + + settings = g_settings_new ("org.gnome.gedit.preferences.editor"); + + /* 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)) + { + gboolean insert_spaces; + + insert_spaces = g_settings_get_boolean (settings, GEDIT_SETTINGS_INSERT_SPACES); + + gtk_source_view_set_insert_spaces_instead_of_tabs (view, 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)) + { + guint tab_width; + + g_settings_get (settings, GEDIT_SETTINGS_TABS_SIZE, "u", &tab_width); + + gtk_source_view_set_tab_width (view, tab_width); + } + + 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)) + { + GtkWrapMode mode; + + mode = g_settings_get_enum (settings, + GEDIT_SETTINGS_WRAP_MODE); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), 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)) + { + guint right_margin_pos; + + g_settings_get (settings, GEDIT_SETTINGS_RIGHT_MARGIN_POSITION, "u", + &right_margin_pos); + gtk_source_view_set_right_margin_position (view, + right_margin_pos); + } + + 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)) + { + gboolean display_right_margin; + + display_right_margin = g_settings_get_boolean (settings, + GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN); + gtk_source_view_set_show_right_margin (view, display_right_margin); + } + + if (previous) + { + g_free (previous->language_id); + *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_object_unref (settings); + g_free (options.language_id); +} + +/* vi:ts=8 */ diff --git a/plugins/modelines/modeline-parser.h b/plugins/modelines/modeline-parser.h new file mode 100644 index 0000000..0fbf9a1 --- /dev/null +++ b/plugins/modelines/modeline-parser.h @@ -0,0 +1,36 @@ +/* + * 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, see . + */ + +#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); + +G_END_DECLS + +#endif /* MODELINE_PARSER_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/modelines.plugin.desktop.in b/plugins/modelines/modelines.plugin.desktop.in new file mode 100644 index 0000000..d334c55 --- /dev/null +++ b/plugins/modelines/modelines.plugin.desktop.in @@ -0,0 +1,8 @@ +[Plugin] +Module=modelines +IAge=3 +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/meson.build b/plugins/pythonconsole/meson.build new file mode 100644 index 0000000..1aecada --- /dev/null +++ b/plugins/pythonconsole/meson.build @@ -0,0 +1,31 @@ +subdir('pythonconsole') + +pythonconsole_gschema_file = files('org.gnome.gedit.plugins.pythonconsole.gschema.xml') +install_data( + pythonconsole_gschema_file, + install_dir: join_paths(get_option('prefix'), get_option('datadir'), 'glib-2.0/schemas') +) + +if xmllint.found() + test( + 'validate-pythonconsole-gschema', + xmllint, + args: [ + '--noout', + '--dtdvalid', gschema_dtd, + pythonconsole_gschema_file, + ] + ) +endif + +custom_target( + 'pythonconsole.plugin', + input: 'pythonconsole.plugin.desktop.in', + output: 'pythonconsole.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml b/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml new file mode 100644 index 0000000..0a29031 --- /dev/null +++ b/plugins/pythonconsole/org.gnome.gedit.plugins.pythonconsole.gschema.xml @@ -0,0 +1,30 @@ + + + + '#314e6c' + Command Color Text + The command color text + + + '#990000' + Error Color Text + The error color text + + + true + Whether to use the system font + + If true, the terminal will use the desktop-global standard + font if it’s monospace (and the most similar font it can + come up with otherwise). + + + + 'Monospace 10' + Font + + A Pango font name. Examples are “Sans 12” or “Monospace Bold 14”. + + + + diff --git a/plugins/pythonconsole/pythonconsole.plugin.desktop.in b/plugins/pythonconsole/pythonconsole.plugin.desktop.in new file mode 100644 index 0000000..2b4456c --- /dev/null +++ b/plugins/pythonconsole/pythonconsole.plugin.desktop.in @@ -0,0 +1,12 @@ +[Plugin] +Loader=python3 +Module=pythonconsole +IAge=3 +Name=Python Console +Description=Interactive Python console standing in the bottom panel. +# TRANSLATORS: Do NOT translate or transliterate this text! +# This is an icon file name. +Icon=text-x-script +Authors=Steve Frécinaux +Copyright=Copyright © 2006 Steve Frécinaux +Website=http://www.gedit.org diff --git a/plugins/pythonconsole/pythonconsole/__init__.py b/plugins/pythonconsole/pythonconsole/__init__.py new file mode 100644 index 0000000..6405596 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/__init__.py @@ -0,0 +1,75 @@ +# -*- 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, see . + +# 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 gi +gi.require_version('Gedit', '3.0') +gi.require_version('Peas', '1.0') +gi.require_version('PeasGtk', '1.0') +gi.require_version('Gtk', '3.0') + +from gi.repository import GObject, Gtk, Gedit, Peas, PeasGtk +from .console import PythonConsole +from .config import PythonConsoleConfigWidget + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class PythonConsolePlugin(GObject.Object, Gedit.WindowActivatable, PeasGtk.Configurable): + __gtype_name__ = "PythonConsolePlugin" + + window = GObject.Property(type=Gedit.Window) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + self._console = PythonConsole(namespace = {'__builtins__' : __builtins__, + 'gedit' : Gedit, + 'window' : self.window}) + self._console.eval('print("You can access the main window through ' \ + '\'window\' :\\n%s" % window)', False) + bottom = self.window.get_bottom_panel() + self._console.show_all() + bottom.add_titled(self._console, "GeditPythonConsolePanel", _('Python Console')) + + def do_deactivate(self): + self._console.stop() + bottom = self.window.get_bottom_panel() + bottom.remove(self._console) + + def do_update_state(self): + pass + + def do_create_configure_widget(self): + config_widget = PythonConsoleConfigWidget(self.plugin_info.get_data_dir()) + + return config_widget.configure_widget() + +# ex:et:ts=4: diff --git a/plugins/pythonconsole/pythonconsole/config.py b/plugins/pythonconsole/pythonconsole/config.py new file mode 100644 index 0000000..8c609af --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/config.py @@ -0,0 +1,74 @@ +# -*- 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, see . + +# 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 +from gi.repository import Gio, Gtk, Gdk + +__all__ = ('PythonConsoleConfigWidget') + +class PythonConsoleConfigWidget(object): + + CONSOLE_KEY_BASE = 'org.gnome.gedit.plugins.pythonconsole' + CONSOLE_KEY_COMMAND_COLOR = 'command-color' + CONSOLE_KEY_ERROR_COLOR = 'error-color' + + def __init__(self, datadir): + object.__init__(self) + + self._ui_path = os.path.join(datadir, 'ui', 'config.ui') + self._settings = Gio.Settings.new(self.CONSOLE_KEY_BASE) + self._ui = Gtk.Builder() + + def configure_widget(self): + self._ui.add_from_file(self._ui_path) + + self.set_colorbutton_color(self._ui.get_object('colorbutton-command'), + self._settings.get_string(self.CONSOLE_KEY_COMMAND_COLOR)) + self.set_colorbutton_color(self._ui.get_object('colorbutton-error'), + self._settings.get_string(self.CONSOLE_KEY_ERROR_COLOR)) + + self._ui.connect_signals(self) + + widget = self._ui.get_object('grid') + + return widget + + @staticmethod + def set_colorbutton_color(colorbutton, value): + rgba = Gdk.RGBA() + parsed = rgba.parse(value) + + if parsed: + colorbutton.set_rgba(rgba) + + def on_colorbutton_command_color_set(self, colorbutton): + self._settings.set_string(self.CONSOLE_KEY_COMMAND_COLOR, + colorbutton.get_color().to_string()) + + def on_colorbutton_error_color_set(self, colorbutton): + self._settings.set_string(self.CONSOLE_KEY_ERROR_COLOR, + 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 100644 index 0000000..573be34 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/config.ui @@ -0,0 +1,70 @@ + + + + + True + False + 12 + 12 + 12 + 12 + 6 + 12 + + + True + False + 0 + C_ommand color: + True + colorbutton-command + + + 0 + 0 + + + + + True + False + 0 + _Error color: + True + colorbutton-error + + + 0 + 1 + + + + + False + True + True + True + True + #31314e4e6c6c + + + 1 + 0 + + + + + False + True + True + True + True + #999900000000 + + + 1 + 1 + + + + diff --git a/plugins/pythonconsole/pythonconsole/console.py b/plugins/pythonconsole/pythonconsole/console.py new file mode 100644 index 0000000..05a7afd --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/console.py @@ -0,0 +1,414 @@ +# -*- 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, see . + +# 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 + +from gi.repository import GLib, Gio, Gtk, Gdk, Pango + +__all__ = ('PythonConsole', 'OutFile') + +class PythonConsole(Gtk.ScrolledWindow): + + __gsignals__ = { + 'grab-focus' : 'override', + } + + DEFAULT_FONT = "Monospace 10" + + CONSOLE_KEY_BASE = 'org.gnome.gedit.plugins.pythonconsole' + SETTINGS_INTERFACE_DIR = "org.gnome.desktop.interface" + + CONSOLE_KEY_COMMAND_COLOR = 'command-color' + CONSOLE_KEY_ERROR_COLOR = 'error-color' + + def __init__(self, namespace = {}): + Gtk.ScrolledWindow.__init__(self) + + self._settings = Gio.Settings.new(self.CONSOLE_KEY_BASE) + self._settings.connect("changed", self.on_color_settings_changed) + + self._interface_settings = Gio.Settings.new(self.SETTINGS_INTERFACE_DIR) + self._interface_settings.connect("changed", self.on_settings_changed) + + self._profile_settings = self.get_profile_settings() + self._profile_settings.connect("changed", self.on_settings_changed) + + self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + self.set_shadow_type(Gtk.ShadowType.NONE) + self.view = Gtk.TextView() + self.reconfigure() + self.view.set_editable(True) + self.view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) + self.add(self.view) + self.view.show() + + buf = self.view.get_buffer() + self.normal = buf.create_tag("normal") + self.error = buf.create_tag("error") + self.command = buf.create_tag("command") + + # Load the default settings + self.on_color_settings_changed(self._settings, None) + + self.__spaces_pattern = re.compile(r'^\s+') + self.namespace = namespace + + self.block_command = False + + # Init first line + buf.create_mark("input-line", buf.get_end_iter(), True) + buf.insert(buf.get_end_iter(), ">>> ") + buf.create_mark("input", buf.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) + buf.connect("mark-set", self.__mark_set_cb) + + def get_profile_settings(self): + #FIXME return either the gnome-terminal settings or the gedit one + return Gio.Settings.new(self.CONSOLE_KEY_BASE) + + def do_grab_focus(self): + self.view.grab_focus() + + def reconfigure(self): + # Font + font_desc = None + system_font = self._interface_settings.get_string("monospace-font-name") + + if self._profile_settings.get_boolean("use-system-font"): + font_name = system_font + else: + font_name = self._profile_settings.get_string("font") + + try: + font_desc = Pango.FontDescription(font_name) + except: + if font_name != self.DEFAULT_FONT: + if font_name != system_font: + try: + font_desc = Pango.FontDescription(system_font) + except: + pass + + if font_desc is None: + try: + font_desc = Pango.FontDescription(self.DEFAULT_FONT) + except: + pass + + if font_desc != None: + self.view.modify_font(font_desc) + + def on_settings_changed(self, settings, key): + self.reconfigure() + + def on_color_settings_changed(self, settings, key): + self.error.set_property("foreground", settings.get_string(self.CONSOLE_KEY_ERROR_COLOR)) + self.command.set_property("foreground", settings.get_string(self.CONSOLE_KEY_COMMAND_COLOR)) + + 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 == Gdk.KEY_D and event_state == Gdk.ModifierType.CONTROL_MASK: + self.destroy() + + elif event.keyval == Gdk.KEY_Return and event_state == Gdk.ModifierType.CONTROL_MASK: + # Get the command + buf = view.get_buffer() + inp_mark = buf.get_mark("input") + inp = buf.get_iter_at_mark(inp_mark) + cur = buf.get_end_iter() + line = buf.get_text(inp, cur, False) + self.current_command = self.current_command + line + "\n" + self.history_add(line) + + # Prepare the new line + cur = buf.get_end_iter() + buf.insert(cur, "\n... ") + cur = buf.get_end_iter() + buf.move_mark(inp_mark, cur) + + # Keep indentation of precendent line + spaces = re.match(self.__spaces_pattern, line) + if spaces is not None: + buf.insert(cur, line[spaces.start() : spaces.end()]) + cur = buf.get_end_iter() + + buf.place_cursor(cur) + GLib.idle_add(self.scroll_to_end) + return True + + elif event.keyval == Gdk.KEY_Return: + # Get the marks + buf = view.get_buffer() + lin_mark = buf.get_mark("input-line") + inp_mark = buf.get_mark("input") + + # Get the command line + inp = buf.get_iter_at_mark(inp_mark) + cur = buf.get_end_iter() + line = buf.get_text(inp, cur, False) + self.current_command = self.current_command + line + "\n" + self.history_add(line) + + # Make the line blue + lin = buf.get_iter_at_mark(lin_mark) + buf.apply_tag(self.command, lin, cur) + buf.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 = buf.get_end_iter() + buf.move_mark(lin_mark, cur) + buf.insert(cur, com_mark) + cur = buf.get_end_iter() + buf.move_mark(inp_mark, cur) + buf.place_cursor(cur) + GLib.idle_add(self.scroll_to_end) + return True + + elif event.keyval == Gdk.KEY_KP_Down or event.keyval == Gdk.KEY_Down: + # Next entry from history + view.stop_emission_by_name("key_press_event") + self.history_down() + GLib.idle_add(self.scroll_to_end) + return True + + elif event.keyval == Gdk.KEY_KP_Up or event.keyval == Gdk.KEY_Up: + # Previous entry from history + view.stop_emission_by_name("key_press_event") + self.history_up() + GLib.idle_add(self.scroll_to_end) + return True + + elif event.keyval == Gdk.KEY_KP_Left or event.keyval == Gdk.KEY_Left or \ + event.keyval == Gdk.KEY_BackSpace: + buf = view.get_buffer() + inp = buf.get_iter_at_mark(buf.get_mark("input")) + cur = buf.get_iter_at_mark(buf.get_insert()) + if inp.compare(cur) == 0: + if not event_state: + buf.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 == Gdk.KEY_KP_Home or event.keyval == Gdk.KEY_Home) and \ + event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK): + # Go to the begin of the command instead of the begin of the line + buf = view.get_buffer() + it = buf.get_iter_at_mark(buf.get_mark("input")) + ins = buf.get_iter_at_mark(buf.get_insert()) + + while it.get_char().isspace(): + it.forward_char() + + if it.equal(ins): + it = buf.get_iter_at_mark(buf.get_mark("input")) + + if event_state & Gdk.ModifierType.SHIFT_MASK: + buf.move_mark_by_name("insert", it) + else: + buf.place_cursor(it) + return True + + elif (event.keyval == Gdk.KEY_KP_End or event.keyval == Gdk.KEY_End) and \ + event_state == event_state & (Gdk.ModifierType.SHIFT_MASK|Gdk.ModifierType.CONTROL_MASK): + + buf = view.get_buffer() + it = buf.get_end_iter() + ins = buf.get_iter_at_mark(buf.get_insert()) + + it.backward_char() + + while it.get_char().isspace(): + it.backward_char() + + it.forward_char() + + if it.equal(ins): + it = buf.get_end_iter() + + if event_state & Gdk.ModifierType.SHIFT_MASK: + buf.move_mark_by_name("insert", it) + else: + buf.place_cursor(it) + return True + + def __mark_set_cb(self, buf, it, name): + input = buf.get_iter_at_mark(buf.get_mark("input")) + pos = buf.get_iter_at_mark(buf.get_insert()) + self.view.set_editable(pos.compare(input) != -1) + + def get_command_line(self): + buf = self.view.get_buffer() + inp = buf.get_iter_at_mark(buf.get_mark("input")) + cur = buf.get_end_iter() + return buf.get_text(inp, cur, False) + + def set_command_line(self, command): + buf = self.view.get_buffer() + mark = buf.get_mark("input") + inp = buf.get_iter_at_mark(mark) + cur = buf.get_end_iter() + buf.delete(inp, cur) + buf.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): + i = self.view.get_buffer().get_end_iter() + self.view.scroll_to_iter(i, 0.0, False, 0.5, 0.5) + return False + + def write(self, text, tag = None): + buf = self.view.get_buffer() + if tag is None: + buf.insert(buf.get_end_iter(), text) + else: + buf.insert_with_tags(buf.get_end_iter(), text, tag) + + GLib.idle_add(self.scroll_to_end) + + def eval(self, command, display_command = False): + buf = self.view.get_buffer() + lin = buf.get_mark("input-line") + buf.delete(buf.get_iter_at_mark(lin), + buf.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 = buf.get_end_iter() + buf.move_mark_by_name("input-line", cur) + buf.insert(cur, ">>> ") + cur = buf.get_end_iter() + buf.move_mark_by_name("input", cur) + self.view.scroll_to_iter(buf.get_end_iter(), 0.0, False, 0.5, 0.5) + + def __run(self, command): + sys.stdout, self.stdout = self.stdout, sys.stdout + sys.stderr, self.stderr = self.stderr, sys.stderr + + try: + try: + r = eval(command, self.namespace, self.namespace) + if r is not None: + print(r) + except SyntaxError: + exec(command, 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/pythonconsole/pythonconsole/meson.build b/plugins/pythonconsole/pythonconsole/meson.build new file mode 100644 index 0000000..6bae388 --- /dev/null +++ b/plugins/pythonconsole/pythonconsole/meson.build @@ -0,0 +1,24 @@ +pythonconsole_sources = files( + '__init__.py', + 'config.py', + 'console.py', +) + +install_data( + pythonconsole_sources, + install_dir: join_paths( + pkglibdir, + 'plugins', + 'pythonconsole', + ) +) + +install_data( + 'config.ui', + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'pythonconsole', + 'ui', + ) +) diff --git a/plugins/quickhighlight/gedit-quick-highlight-plugin.c b/plugins/quickhighlight/gedit-quick-highlight-plugin.c new file mode 100644 index 0000000..7b4289d --- /dev/null +++ b/plugins/quickhighlight/gedit-quick-highlight-plugin.c @@ -0,0 +1,483 @@ +/* + * gedit-quick-highlight-plugin.c + * + * Copyright (C) 2018 Martin Blanchard + * + * 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, see . + * + */ + +#include "config.h" + +#include + +#include +#include +#include +#include + +#include "gedit-quick-highlight-plugin.h" + +struct _GeditQuickHighlightPluginPrivate +{ + GeditView *view; + + GeditDocument *buffer; + GtkTextMark *insert_mark; + + GtkSourceSearchContext *search_context; + GtkSourceStyle *style; + + gulong buffer_handler_id; + gulong mark_set_handler_id; + gulong delete_range_handler_id; + gulong style_scheme_handler_id; + + guint queued_highlight; +}; + +enum +{ + PROP_0, + PROP_VIEW +}; + +static void gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditQuickHighlightPlugin, + gedit_quick_highlight_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_VIEW_ACTIVATABLE, + gedit_view_activatable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditQuickHighlightPlugin)) + +static void gedit_quick_highlight_plugin_notify_buffer_cb (GObject *object, GParamSpec *pspec, gpointer user_data); +static void gedit_quick_highlight_plugin_mark_set_cb (GtkTextBuffer *textbuffer, GtkTextIter *location, GtkTextMark *mark, gpointer user_data); +static void gedit_quick_highlight_plugin_delete_range_cb (GtkTextBuffer *textbuffer, GtkTextIter *start, GtkTextIter *end, gpointer user_data); +static void gedit_quick_highlight_plugin_notify_style_scheme_cb (GObject *object, GParamSpec *pspec, gpointer user_data); + +static void +gedit_quick_highlight_plugin_load_style (GeditQuickHighlightPlugin *plugin) +{ + GtkSourceStyleScheme *style_scheme; + GtkSourceStyle *style = NULL; + + g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + if (plugin->priv->buffer == NULL) + { + return; + } + + gedit_debug (DEBUG_PLUGINS); + + g_clear_object (&plugin->priv->style); + + style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (plugin->priv->buffer)); + + if (style_scheme != NULL) + { + style = gtk_source_style_scheme_get_style (style_scheme, "quick-highlight-match"); + + if (style != NULL) + { + plugin->priv->style = gtk_source_style_copy (style); + } + } +} + +static gboolean +gedit_quick_highlight_plugin_highlight_worker (gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + GtkSourceSearchSettings *search_settings; + GtkTextIter start, end; + g_autofree gchar *text = NULL; + + g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + plugin->priv->queued_highlight = 0; + + if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (plugin->priv->buffer), &start, &end)) + { + g_clear_object (&plugin->priv->search_context); + return G_SOURCE_REMOVE; + } + + if (gtk_text_iter_get_line (&start) != gtk_text_iter_get_line (&end)) + { + g_clear_object (&plugin->priv->search_context); + return G_SOURCE_REMOVE; + } + + if (plugin->priv->search_context == NULL) + { + search_settings = + g_object_new (GTK_SOURCE_TYPE_SEARCH_SETTINGS, + "at-word-boundaries", FALSE, + "case-sensitive", TRUE, + "regex-enabled", FALSE, + NULL); + + plugin->priv->search_context = + g_object_new (GTK_SOURCE_TYPE_SEARCH_CONTEXT, + "buffer", plugin->priv->buffer, + "highlight", FALSE, + "match-style", plugin->priv->style, + "settings", search_settings, + NULL); + + g_object_unref (search_settings); + } + else + { + search_settings = + gtk_source_search_context_get_settings (plugin->priv->search_context); + } + + text = gtk_text_iter_get_slice (&start, &end); + + gtk_source_search_settings_set_search_text (search_settings, text); + + gtk_source_search_context_set_highlight (plugin->priv->search_context, TRUE); + + return G_SOURCE_REMOVE; +} + +static void +gedit_quick_highlight_plugin_queue_update (GeditQuickHighlightPlugin *plugin) +{ + g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + if (plugin->priv->queued_highlight != 0) + { + return; + } + + plugin->priv->queued_highlight = + gdk_threads_add_idle_full (G_PRIORITY_LOW, + gedit_quick_highlight_plugin_highlight_worker, + g_object_ref (plugin), + g_object_unref); +} + +static void +gedit_quick_highlight_plugin_notify_weak_buffer_cb (gpointer data, + GObject *where_the_object_was) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (data); + + g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + plugin->priv->style_scheme_handler_id = 0; + plugin->priv->buffer = NULL; +} + +static void +gedit_quick_highlight_plugin_unref_weak_buffer (GeditQuickHighlightPlugin *plugin) +{ + g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + if (plugin->priv->buffer == NULL) + { + return; + } + + if (plugin->priv->delete_range_handler_id > 0) + { + g_signal_handler_disconnect (plugin->priv->buffer, + plugin->priv->delete_range_handler_id); + plugin->priv->delete_range_handler_id = 0; + } + + if (plugin->priv->mark_set_handler_id > 0) + { + g_signal_handler_disconnect (plugin->priv->buffer, + plugin->priv->mark_set_handler_id); + plugin->priv->mark_set_handler_id = 0; + } + + if (plugin->priv->style_scheme_handler_id > 0) + { + g_signal_handler_disconnect (plugin->priv->buffer, + plugin->priv->style_scheme_handler_id); + plugin->priv->style_scheme_handler_id = 0; + } + + g_object_weak_unref (G_OBJECT (plugin->priv->buffer), + gedit_quick_highlight_plugin_notify_weak_buffer_cb, + plugin); + + plugin->priv->buffer = NULL; +} + +static void +gedit_quick_highlight_plugin_set_buffer (GeditQuickHighlightPlugin *plugin, + GeditDocument *buffer) +{ + g_return_if_fail (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + g_return_if_fail (GEDIT_IS_DOCUMENT (buffer)); + + if (plugin->priv->buffer == buffer) + { + return; + } + + gedit_debug (DEBUG_PLUGINS); + + gedit_quick_highlight_plugin_unref_weak_buffer (plugin); + + plugin->priv->buffer = buffer; + + if (plugin->priv->buffer != NULL) + { + g_object_weak_ref (G_OBJECT (plugin->priv->buffer), + gedit_quick_highlight_plugin_notify_weak_buffer_cb, + plugin); + + plugin->priv->style_scheme_handler_id = + g_signal_connect (plugin->priv->buffer, + "notify::style-scheme", + G_CALLBACK (gedit_quick_highlight_plugin_notify_style_scheme_cb), + plugin); + + plugin->priv->mark_set_handler_id = + g_signal_connect (plugin->priv->buffer, + "mark-set", + G_CALLBACK (gedit_quick_highlight_plugin_mark_set_cb), + plugin); + + plugin->priv->delete_range_handler_id = + g_signal_connect (plugin->priv->buffer, + "delete-range", + G_CALLBACK (gedit_quick_highlight_plugin_delete_range_cb), + plugin); + + plugin->priv->insert_mark = + gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (plugin->priv->buffer)); + + gedit_quick_highlight_plugin_load_style (plugin); + + gedit_quick_highlight_plugin_queue_update (plugin); + } +} + +static void +gedit_quick_highlight_plugin_mark_set_cb (GtkTextBuffer *textbuffer, + GtkTextIter *location, + GtkTextMark *mark, + gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + + g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + if G_LIKELY (mark != plugin->priv->insert_mark) + { + return; + } + + gedit_quick_highlight_plugin_queue_update (plugin); +} + +static void +gedit_quick_highlight_plugin_delete_range_cb (GtkTextBuffer *textbuffer, + GtkTextIter *start, + GtkTextIter *end, + gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + + g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + gedit_quick_highlight_plugin_queue_update (plugin); +} + +static void +gedit_quick_highlight_plugin_notify_style_scheme_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + + g_assert (GEDIT_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + gedit_quick_highlight_plugin_load_style (plugin); +} + +static void +gedit_quick_highlight_plugin_dispose (GObject *object) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object); + + g_clear_object (&plugin->priv->search_context); + + gedit_quick_highlight_plugin_unref_weak_buffer (plugin); + + g_clear_object (&plugin->priv->view); + + G_OBJECT_CLASS (gedit_quick_highlight_plugin_parent_class)->dispose (object); +} + +static void +gedit_quick_highlight_plugin_finalize (GObject *object) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object); + + g_clear_object (&plugin->priv->style); + + G_OBJECT_CLASS (gedit_quick_highlight_plugin_parent_class)->finalize (object); +} + +static void +gedit_quick_highlight_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + g_value_set_object (value, plugin->priv->view); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_quick_highlight_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + plugin->priv->view = GEDIT_VIEW (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_quick_highlight_plugin_class_init (GeditQuickHighlightPluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gedit_quick_highlight_plugin_dispose; + object_class->finalize = gedit_quick_highlight_plugin_finalize; + object_class->set_property = gedit_quick_highlight_plugin_set_property; + object_class->get_property = gedit_quick_highlight_plugin_get_property; + + g_object_class_override_property (object_class, PROP_VIEW, "view"); +} + +static void +gedit_quick_highlight_plugin_class_finalize (GeditQuickHighlightPluginClass *klass) +{ +} + +static void +gedit_quick_highlight_plugin_init (GeditQuickHighlightPlugin *plugin) +{ + plugin->priv = gedit_quick_highlight_plugin_get_instance_private (plugin); +} + +static void +gedit_quick_highlight_plugin_activate (GeditViewActivatable *activatable) +{ + GeditQuickHighlightPlugin *plugin; + GtkTextBuffer *buffer; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (activatable); + + plugin->priv->buffer_handler_id = + g_signal_connect (plugin->priv->view, + "notify::buffer", + G_CALLBACK (gedit_quick_highlight_plugin_notify_buffer_cb), + plugin); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + gedit_quick_highlight_plugin_set_buffer (plugin, GEDIT_DOCUMENT (buffer)); +} + +static void +gedit_quick_highlight_plugin_deactivate (GeditViewActivatable *activatable) +{ + GeditQuickHighlightPlugin *plugin; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (activatable); + + g_clear_object (&plugin->priv->style); + g_clear_object (&plugin->priv->search_context); + + gedit_quick_highlight_plugin_unref_weak_buffer (plugin); + + if (plugin->priv->view != NULL && plugin->priv->buffer_handler_id > 0) + { + g_signal_handler_disconnect (plugin->priv->view, + plugin->priv->buffer_handler_id); + plugin->priv->buffer_handler_id = 0; + } +} + +static void +gedit_quick_highlight_plugin_notify_buffer_cb (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + GeditQuickHighlightPlugin *plugin = GEDIT_QUICK_HIGHLIGHT_PLUGIN (user_data); + GtkTextBuffer *buffer; + + g_assert (GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN (plugin)); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + gedit_quick_highlight_plugin_set_buffer (plugin, GEDIT_DOCUMENT (buffer)); +} + +static void +gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface) +{ + iface->activate = gedit_quick_highlight_plugin_activate; + iface->deactivate = gedit_quick_highlight_plugin_deactivate; +} + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_quick_highlight_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_VIEW_ACTIVATABLE, + GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/quickhighlight/gedit-quick-highlight-plugin.h b/plugins/quickhighlight/gedit-quick-highlight-plugin.h new file mode 100644 index 0000000..3e668d0 --- /dev/null +++ b/plugins/quickhighlight/gedit-quick-highlight-plugin.h @@ -0,0 +1,63 @@ +/* + * gedit-quick-highlight-plugin.h + * + * Copyright (C) 2018 Martin Blanchard + * + * 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, see . + * + */ + +#ifndef GEDIT_QUICK_HIGHLIGHT_PLUGIN_H +#define GEDIT_QUICK_HIGHLIGHT_PLUGIN_H + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN (gedit_quick_highlight_plugin_get_type ()) +#define GEDIT_QUICK_HIGHLIGHT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPlugin)) +#define GEDIT_QUICK_HIGHLIGHT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPluginClass)) +#define GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN)) +#define GEDIT_IS_QUICK_HIGHLIGHT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN)) +#define GEDIT_QUICK_HIGHLIGHT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_QUICK_HIGHLIGHT_PLUGIN, GeditQuickHighlightPluginClass)) + +typedef struct _GeditQuickHighlightPlugin GeditQuickHighlightPlugin; +typedef struct _GeditQuickHighlightPluginPrivate GeditQuickHighlightPluginPrivate; +typedef struct _GeditQuickHighlightPluginClass GeditQuickHighlightPluginClass; + +struct _GeditQuickHighlightPlugin +{ + PeasExtensionBase parent_instance; + + /* < private > */ + GeditQuickHighlightPluginPrivate *priv; +}; + +struct _GeditQuickHighlightPluginClass +{ + PeasExtensionBaseClass parent_class; +}; + +GType gedit_quick_highlight_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_QUICK_HIGHLIGHT_PLUGIN_H */ + +/* ex:set ts=8 noet: */ diff --git a/plugins/quickhighlight/meson.build b/plugins/quickhighlight/meson.build new file mode 100644 index 0000000..18fb417 --- /dev/null +++ b/plugins/quickhighlight/meson.build @@ -0,0 +1,32 @@ +libquickhighlight_sources = files( + 'gedit-quick-highlight-plugin.c', +) + +libquickhighlight_deps = [ + libgedit_dep, +] + +libquickhighlight_sha = shared_module( + 'quickhighlight', + sources: libquickhighlight_sources, + include_directories: root_include_dir, + dependencies: libquickhighlight_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +custom_target( + 'quickhighlight.plugin', + input: 'quickhighlight.plugin.desktop.in', + output: 'quickhighlight.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/quickhighlight/quickhighlight.plugin.desktop.in b/plugins/quickhighlight/quickhighlight.plugin.desktop.in new file mode 100644 index 0000000..abb1baf --- /dev/null +++ b/plugins/quickhighlight/quickhighlight.plugin.desktop.in @@ -0,0 +1,8 @@ +[Plugin] +Module=quickhighlight +IAge=3 +Name=Quick Highlight +Description=Highlights every occurrences of selected text. +Authors=Martin Blanchard +Copyright=Copyright © 2016 Martin Blanchard +Website=http://www.gedit.org diff --git a/plugins/quickopen/meson.build b/plugins/quickopen/meson.build new file mode 100644 index 0000000..c5a2680 --- /dev/null +++ b/plugins/quickopen/meson.build @@ -0,0 +1,19 @@ +install_subdir( + 'quickopen', + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) + +custom_target( + 'quickopen.plugin', + input: 'quickopen.plugin.desktop.in', + output: 'quickopen.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) diff --git a/plugins/quickopen/quickopen.plugin.desktop.in b/plugins/quickopen/quickopen.plugin.desktop.in new file mode 100644 index 0000000..80fa03b --- /dev/null +++ b/plugins/quickopen/quickopen.plugin.desktop.in @@ -0,0 +1,12 @@ +[Plugin] +Loader=python3 +Module=quickopen +IAge=3 +Name=Quick Open +Description=Quickly open files. +# TRANSLATORS: Do NOT translate or transliterate this text! +# This is an icon file name. +Icon=document-open +Authors=Jesse van den Kieboom +Copyright=Copyright © 2009 Jesse van den Kieboom +Website=http://www.gedit.org diff --git a/plugins/quickopen/quickopen/__init__.py b/plugins/quickopen/quickopen/__init__.py new file mode 100644 index 0000000..3f708a3 --- /dev/null +++ b/plugins/quickopen/quickopen/__init__.py @@ -0,0 +1,193 @@ +# -*- 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, see . + +import os + +import gi +gi.require_version('Gedit', '3.0') +gi.require_version('Gtk', '3.0') +from gi.repository import GObject, Gio, GLib, Gtk, Gedit + +from .popup import Popup +from .virtualdirs import RecentDocumentsDirectory +from .virtualdirs import CurrentDocumentsDirectory + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class QuickOpenAppActivatable(GObject.Object, Gedit.AppActivatable): + app = GObject.Property(type=Gedit.App) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + self.app.add_accelerator("O", "win.quickopen", None) + + self.menu_ext = self.extend_menu("file-section") + item = Gio.MenuItem.new(_("Quick Open…"), "win.quickopen") + self.menu_ext.prepend_menu_item(item) + + def do_deactivate(self): + self.app.remove_accelerator("win.quickopen", None) + + +class QuickOpenPlugin(GObject.Object, Gedit.WindowActivatable): + __gtype_name__ = "QuickOpenPlugin" + + window = GObject.Property(type=Gedit.Window) + + def __init__(self): + GObject.Object.__init__(self) + + def do_activate(self): + self._popup_size = (450, 300) + self._popup = None + + action = Gio.SimpleAction(name="quickopen") + action.connect('activate', self.on_quick_open_activate) + self.window.add_action(action) + + def do_deactivate(self): + self.window.remove_action("quickopen") + + def get_popup_size(self): + return self._popup_size + + def set_popup_size(self, size): + self._popup_size = size + + 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.get_file().is_local(): + gfile = doc.get_file().get_location() + paths.append(gfile.get_parent()) + + # File browser root directory + bus = self.window.get_message_bus() + + if bus.is_registered('/plugins/filebrowser', 'get_root'): + msg = bus.send_sync('/plugins/filebrowser', 'get_root') + + if msg: + gfile = msg.props.location + + if gfile and gfile.is_native(): + paths.append(gfile) + + # Recent documents + paths.append(RecentDocumentsDirectory()) + + # Local bookmarks + for path in self._local_bookmarks(): + paths.append(path) + + # Desktop directory + desktopdir = self._desktop_dir() + + if desktopdir: + paths.append(Gio.file_new_for_path(desktopdir)) + + # Home directory + paths.append(Gio.file_new_for_path(os.path.expanduser('~'))) + + self._popup = Popup(self.window, paths, self.on_activated) + self.window.get_group().add_window(self._popup) + + self._popup.set_default_size(*self.get_popup_size()) + self._popup.set_transient_for(self.window) + self._popup.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) + self._popup.connect('destroy', self.on_popup_destroy) + + def _local_bookmarks(self): + filename = os.path.expanduser('~/.config/gtk-3.0/bookmarks') + + if not os.path.isfile(filename): + return [] + + paths = [] + + for line in open(filename, 'r', encoding='utf-8'): + uri = line.strip().split(" ")[0] + f = Gio.file_new_for_uri(uri) + + if f.is_native(): + try: + info = f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, + Gio.FileQueryInfoFlags.NONE, + None) + + if info and info.get_file_type() == Gio.FileType.DIRECTORY: + paths.append(f) + except: + 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 open(config, 'r', encoding='utf-8'): + line = line.strip() + + if line.startswith('XDG_DESKTOP_DIR'): + parts = line.split('=', 1) + desktopdir = parts[1].strip('"').strip("'") + desktopdir = os.path.expandvars(desktopdir) + break + + if not desktopdir: + desktopdir = os.path.expanduser('~/Desktop') + + return desktopdir + + # Callbacks + def on_quick_open_activate(self, action, parameter, user_data=None): + if not self._popup: + self._create_popup() + + self._popup.show() + + def on_popup_destroy(self, popup, user_data=None): + self.set_popup_size(popup.get_final_size()) + + self._popup = None + + def on_activated(self, gfile, user_data=None): + Gedit.commands_load_location(self.window, gfile, None, -1, -1) + return True + +# ex:ts=4:et: diff --git a/plugins/quickopen/quickopen/popup.py b/plugins/quickopen/quickopen/popup.py new file mode 100644 index 0000000..2e7cb2f --- /dev/null +++ b/plugins/quickopen/quickopen/popup.py @@ -0,0 +1,618 @@ +# -*- 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, see . + +import os +import platform +import functools +import fnmatch + +from gi.repository import GLib, Gio, GObject, Pango, Gtk, Gdk, Gedit +import xml.sax.saxutils +from .virtualdirs import VirtualDirectory + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class Popup(Gtk.Dialog): + __gtype_name__ = "QuickOpenPopup" + + def __init__(self, window, paths, handler): + Gtk.Dialog.__init__(self, + title=_('Quick Open'), + transient_for=window, + modal=True, + destroy_with_parent=True) + + self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL) + self._open_button = self.add_button(_("_Open"), + Gtk.ResponseType.ACCEPT) + + self._handler = handler + self._build_ui() + + self._size = (0, 0) + self._dirs = [] + self._cache = {} + self._theme = None + self._cursor = None + self._shift_start = None + + self._busy_cursor = Gdk.Cursor(Gdk.CursorType.WATCH) + + accel_group = Gtk.AccelGroup() + accel_group.connect(Gdk.KEY_l, + Gdk.ModifierType.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()) + + self.connect('show', self.on_show) + + def get_final_size(self): + return self._size + + def _build_ui(self): + self.set_border_width(5) + vbox = self.get_content_area() + vbox.set_spacing(2) + action_area = self.get_action_area() + action_area.set_border_width(5) + action_area.set_spacing(6) + + self._entry = Gtk.SearchEntry() + self._entry.set_placeholder_text(_('Type to search…')) + + self._entry.connect('changed', self.on_changed) + self._entry.connect('key-press-event', self.on_key_press_event) + + sw = Gtk.ScrolledWindow() + sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) + sw.set_shadow_type(Gtk.ShadowType.OUT) + + tv = Gtk.TreeView() + tv.set_headers_visible(False) + + self._store = Gtk.ListStore(Gio.Icon, + str, + GObject.Object, + Gio.FileType) + tv.set_model(self._store) + + self._treeview = tv + tv.connect('row-activated', self.on_row_activated) + + column = Gtk.TreeViewColumn() + + renderer = Gtk.CellRendererPixbuf() + column.pack_start(renderer, False) + column.add_attribute(renderer, "gicon", 0) + + renderer = Gtk.CellRendererText() + column.pack_start(renderer, True) + column.add_attribute(renderer, "markup", 1) + + column.set_cell_data_func(renderer, self.on_cell_data_cb, None) + + tv.append_column(column) + sw.add(tv) + + selection = tv.get_selection() + selection.connect('changed', self.on_selection_changed) + selection.set_mode(Gtk.SelectionMode.MULTIPLE) + + vbox.pack_start(self._entry, False, False, 0) + vbox.pack_start(sw, True, True, 0) + + lbl = Gtk.Label() + lbl.set_halign(Gtk.Align.START) + lbl.set_ellipsize(Pango.EllipsizeMode.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, user_data): + path = model.get_path(piter) + + if self._cursor and path == self._cursor.get_path(): + style = self._treeview.get_style() + bg = style.bg[Gtk.StateType.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 _is_text(self, entry): + content_type = entry.get_content_type() + + if content_type is None or Gio.content_type_is_unknown(content_type): + return True + + if platform.system() != 'Windows': + if (Gio.content_type_is_a(content_type, 'text/plain') or + Gio.content_type_equals(content_type, 'application/x-zerosize')): + return True + else: + if Gio.content_type_is_a(content_type, 'text'): + return True + + # This covers a rare case in which on Windows the PerceivedType + # is not set to "text" but the Content Type is set to text/plain + if Gio.content_type_get_mime_type(content_type) == 'text/plain': + return True + + return False + + def _list_dir(self, gfile): + entries = [] + + try: + ret = gfile.enumerate_children("standard::*", + Gio.FileQueryInfoFlags.NONE, + None) + except GLib.Error as e: + pass + + if isinstance(ret, Gio.FileEnumerator): + while True: + entry = ret.next_file(None) + + if not entry: + break + + if not entry.get_is_backup(): + entries.append((gfile.get_child(entry.get_name()), entry)) + else: + entries = ret + + children = [] + + for entry in entries: + file_type = entry[1].get_file_type() + + if file_type == Gio.FileType.REGULAR: + if not self._is_text(entry[1]): + continue + + children.append((entry[0], + entry[1].get_name(), + file_type, + entry[1].get_icon())) + + return children + + def _compare_entries(self, a, b, lpart): + if lpart in a: + if lpart in b: + if a.index(lpart) < b.index(lpart): + return -1 + elif a.index(lpart) > b.index(lpart): + return 1 + else: + return 0 + 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 d in self._cache: + entries = self._cache[d] + else: + entries = self._list_dir(d) + entries.sort(key=lambda x: x[1].lower()) + self._cache[d] = entries + + 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.FileType.DIRECTORY: + if len(parts) > 1: + newdirs.append(entry[0]) + else: + found.append(entry) + elif entry[2] == Gio.FileType.REGULAR and \ + (not lpart or len(parts) == 1): + found.append(entry) + + found.sort(key=functools.cmp_to_key(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, + Gio.FileQueryInfoFlags.NONE, + None) + + 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): + uri = item[2].get_uri() + + if uri not in self._stored_items: + self._store.append(item) + self._stored_items.add(uri) + + def _clear_store(self): + self._store.clear() + self._stored_items = set() + + def _show_virtuals(self): + for d in self._dirs: + if isinstance(d, VirtualDirectory): + for entry in d.enumerate_children("standard::*", 0, None): + self._append_to_store((entry[1].get_icon(), + xml.sax.saxutils.escape(entry[1].get_name()), + entry[0], + entry[1].get_file_type())) + + def _set_busy(self, busy): + if busy: + self.get_window().set_cursor(self._busy_cursor) + else: + self.get_window().set_cursor(None) + Gdk.flush() + + 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._set_busy(True) + 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: + path = self._store.get_path(piter) + self._treeview.get_selection().select_path(path) + + self._set_busy(False) + + # FIXME: override doesn't work anymore for some reason, if we override + # the widget is not realized + def on_show(self, data=None): + # 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.new(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.get_indices()[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 = Gio.file_new_for_uri(uri) + + if Gedit.utils_is_valid_location(gfile) or \ + (os.path.isabs(uri) and gfile.query_exists()): + return gfile + else: + return None + + 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.FileType.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: + # We destroy the popup in an idle callback to work around a crash that happens with + # GTK_IM_MODULE=xim. See https://bugzilla.gnome.org/show_bug.cgi?id=737711 . + GLib.idle_add(self.destroy) + + if not rows: + gfile = self._direct_file() + + if gfile and self._handler(gfile): + GLib.idle_add(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 = { + Gdk.KEY_Down: 1, + Gdk.KEY_Up: -1, + Gdk.KEY_Page_Down: 5, + Gdk.KEY_Page_Up: -5 + } + + if event.keyval == Gdk.KEY_Escape: + self.destroy() + return True + elif event.keyval in move_mapping: + return self._move_selection(move_mapping[event.keyval], event.state & Gdk.ModifierType.CONTROL_MASK, event.state & Gdk.ModifierType.SHIFT_MASK) + elif event.keyval in [Gdk.KEY_Return, Gdk.KEY_KP_Enter, Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]: + return self._activate() + elif event.keyval == Gdk.KEY_space and event.state & Gdk.ModifierType.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.ResponseType.ACCEPT or not self._activate(): + self.destroy() + + def do_configure_event(self, event): + if self.get_realized(): + alloc = self.get_allocation() + self._size = (alloc.width, alloc.height) + + return Gtk.Dialog.do_configure_event(self, event) + + 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 is not None) + self._info_label.set_markup(fname or '') + + def on_focus_entry(self, group, accel, keyval, modifier): + self._entry.grab_focus() + +# ex:ts=4:et: diff --git a/plugins/quickopen/quickopen/virtualdirs.py b/plugins/quickopen/quickopen/virtualdirs.py new file mode 100644 index 0000000..531ab33 --- /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, see . + +from gi.repository import GLib, Gio, Gtk + + +class VirtualDirectory(object): + 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, flags, callback): + return self._children + + def append(self, child): + if not child.is_native(): + return + + try: + info = child.query_info("standard::*", + Gio.FileQueryInfoFlags.NONE, + None) + + if info: + self._children.append((child, info)) + except Exception as e: + pass + + +class RecentDocumentsDirectory(VirtualDirectory): + def __init__(self, maxitems=200): + VirtualDirectory.__init__(self, 'recent') + + self._maxitems = maxitems + self.fill() + + def fill(self): + manager = Gtk.RecentManager.get_default() + + items = manager.get_items() + items.sort(key=lambda a: a.get_visited(), reverse=True) + + added = 0 + + for item in items: + if item.has_application(GLib.get_application_name()): + self.append(Gio.file_new_for_uri(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_file().get_location() + + if location: + self.append(location) + +# ex:ts=4:et: diff --git a/plugins/snippets/data/c.xml b/plugins/snippets/data/c.xml new file mode 100644 index 0000000..f46ceed --- /dev/null +++ b/plugins/snippets/data/c.xml @@ -0,0 +1,281 @@ + + + + ]} + * 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, see . + */ + +$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 ${2}. If not, see . + */ + +$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 + + + + +typedef struct _$<[1]: return camel_str >Private +{ +} $<[1]: return camel_str >Private; + +G_DEFINE_TYPE_WITH_PRIVATE ($<[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; +} + +static void +$<[1]: return low_str >_init ($<[1]: return camel_str> *self) +{ +} + +$<[1]: return camel_str > * +$<[1]: return low_str >_new () +{ + return g_object_new ($<[1]: return type_str >, NULL); +}]]> + gobject + GObject template + + + +G_DEFINE_INTERFACE ($<[1]: return camel_str >, $<[1]: return low_str >, ${2:G_TYPE_OBJECT}) + +/* 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); +}]]> + ginterface + GObject interface + + + + +struct _$<[1]: return camel_str > +{ +}; + +G_DEFINE_BOXED_TYPE ($<[1]: return camel_str >, $<[1]: return low_str >, $<[1]: return low_str >_${2:copy}, $<[1]: return low_str >_${3:free}) + +$<[1]: return camel_str > * +$<[1]: return low_str >_${2:copy} ($<[1]: return camel_str > *${4:boxed_name}) +{ + +} + +void +$<[1]: return low_str >_${3:free} ($<[1]: return camel_str > *${4:boxed_name}) +{ + +}]]> + gboxed + GBoxed template + + diff --git a/plugins/snippets/data/chdr.xml b/plugins/snippets/data/chdr.xml new file mode 100644 index 0000000..2ce94d7 --- /dev/null +++ b/plugins/snippets/data/chdr.xml @@ -0,0 +1,258 @@ + + + + + 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, see . + */ + +$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 ${2}. If not, see . + */ + +$0]]> + lgpl + LGPL License + + + + td + typedef + + + + class .. + class + + + + struct + struct + + + ]]> + template <typename ..> + template + + + +#include <${2:glib-object.h}> + +G_BEGIN_DECLS + +#define $<[1]: return type_str > ($<[1]: return $1.lower() >_get_type ()) +G_DECLARE_DERIVABLE_TYPE ($<[1]: return camel_str >, $<[1]: return $1.lower() >, $<[1]: return module >, $<[1]: return name >, ${3:GObject}) + +struct _$<[1]: return camel_str >Class +{ + $3Class parent_class; +}; + +$<[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 + + + + +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)', '((' + camel_str + ' *)obj)'], \ +['#define ' + $1 + '_CONST(obj)', '((' + camel_str + ' const *)obj)'], \ +] + +return align(items) > + +$<[1]: +items = [ \ +['typedef struct _' + camel_str, camel_str + ';'], \ +] + +return align(items) > + +GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST; +$<[1]: return camel_str > *$< return $1.lower()>_${3:copy} ($<[1]: return camel_str > *${4:boxed_name}); +void $< return $1.lower()>_${5:free} ($<[1]: return camel_str > *${4:boxed_name}); + +$0 +G_END_DECLS + +#endif /* $1_H */]]> + gboxed + GBoxed template + + diff --git a/plugins/snippets/data/cpp.xml b/plugins/snippets/data/cpp.xml new file mode 100644 index 0000000..1d4c31c --- /dev/null +++ b/plugins/snippets/data/cpp.xml @@ -0,0 +1,180 @@ + + + + + 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, see . + */ + + $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 ${2}. If not, see . + */ + + $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 100644 index 0000000..babca91 --- /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 100644 index 0000000..d2a07de --- /dev/null +++ b/plugins/snippets/data/docbook.xml @@ -0,0 +1,2645 @@ + + + + + + ${1}]]> + abbrev + abbrev + + + + ${1} +]]> + abstract + abstract + + + ${1}]]> + accel + accel + + + + ${1} +]]> + ackno + ackno + + + ${1}]]> + acronym + acronym + + + ${1}]]> + action + action + + + + ${1} +]]> + address + address + + + + ${1} +]]> + affiliation + affiliation + + + ${1}]]> + alt + alt + + + ]]> + anchor + anchor + + + + ${1} +]]> + answer + answer + + + + ${2} +]]> + appendix + appendix + + + + ${1} +]]> + appendixinfo + appendixinfo + + + ${1}]]> + application + application + + + ]]> + area + area + + + + ${3} +]]> + areaset + areaset + + + + ${2} +]]> + areaspec + areaspec + + + ${1}]]> + arg + arg + + + + ${2} +]]> + article + article + + + + ${1} +]]> + articleinfo + articleinfo + + + ${1}]]> + artpagenums + artpagenums + + + ${1}]]> + attribution + attribution + + + ]]> + audiodata + audiodata + + + + ${1} +]]> + audioobject + audioobject + + + + ${1} +]]> + author + author + + + + ${1} +]]> + authorblurb + authorblurb + + + + ${1} +]]> + authorgroup + authorgroup + + + ${1}]]> + authorinitials + authorinitials + + + ]]> + beginpage + beginpage + + + + ${1} +]]> + bibliocoverage + bibliocoverage + + + + ${1} +]]> + bibliodiv + bibliodiv + + + + ${1} +]]> + biblioentry + biblioentry + + + + ${1} +]]> + bibliography + bibliography + + + + ${1} +]]> + bibliographyinfo + bibliographyinfo + + + ${2}]]> + biblioid + biblioid + + + + ${1} +]]> + bibliolist + bibliolist + + + + ${1} +]]> + bibliomisc + bibliomisc + + + + ${1} +]]> + bibliomixed + bibliomixed + + + + ${2} +]]> + bibliomset + bibliomset + + + ]]> + biblioref + biblioref + + + ${3}]]> + bibliorelation + bibliorelation + + + + ${2} +]]> + biblioset + biblioset + + + ${2}]]> + bibliosource + bibliosource + + + + ${1} +]]> + blockinfo + blockinfo + + + + ${1} +]]> + blockquote + blockquote + + + + ${2} +]]> + book + book + + + + ${1} +]]> + bookinfo + bookinfo + + + ${2}]]> + bridgehead + bridgehead + + + + ${2} +]]> + callout + callout + + + + ${1} +]]> + calloutlist + calloutlist + + + + ${1} +]]> + caption + caption + + + + ${1} +]]> + caution + caution + + + + ${2} +]]> + chapter + chapter + + + + ${1} +]]> + chapterinfo + chapterinfo + + + ${1}]]> + citation + citation + + + ${2}]]> + citebiblioid + citebiblioid + + + ${1}]]> + citerefentry + citerefentry + + + ${2}]]> + citetitle + citetitle + + + ${1}]]> + city + city + + + ${1}]]> + classname + classname + + + + ${3} +]]> + classsynopsis + classsynopsis + + + + ${1} +]]> + classsynopsisinfo + classsynopsisinfo + + + + ${1} +]]> + cmdsynopsis + cmdsynopsis + + + ]]> + co + co + + + ${2}]]> + code + code + + + + ${1} +]]> + col + col + + + + ${1} +]]> + colgroup + colgroup + + + + ${1} +]]> + collab + collab + + + ${1}]]> + collabname + collabname + + + + ${1} +]]> + colophon + colophon + + + ]]> + colspec + colspec + + + ${1}]]> + command + command + + + ${1}]]> + computeroutput + computeroutput + + + ${1}]]> + confdates + confdates + + + + ${1} +]]> + confgroup + confgroup + + + ${1}]]> + confnum + confnum + + + ${1}]]> + confsponsor + confsponsor + + + ${1}]]> + conftitle + conftitle + + + ${1}]]> + constant + constant + + + + ${2} +]]> + constructorsynopsis + constructorsynopsis + + + ${1}]]> + contractnum + contractnum + + + ${1}]]> + contractsponsor + contractsponsor + + + ${1}]]> + contrib + contrib + + + + ${1} +]]> + copyright + copyright + + + ]]> + coref + coref + + + ${1}]]> + corpauthor + corpauthor + + + ${1}]]> + corpcredit + corpcredit + + + ${1}]]> + corpname + corpname + + + ${1}]]> + country + country + + + ${2}]]> + database + database + + + ${1}]]> + date + date + + + + ${1} +]]> + dedication + dedication + + + + ${2} +]]> + destructorsynopsis + destructorsynopsis + + + ${1}]]> + edition + edition + + + + ${1} +]]> + editor + editor + + + ${1}]]> + email + email + + + ${1}]]> + emphasis + emphasis + + + ${1}]]> + entry + entry + + + + ${2} +]]> + entrytbl + entrytbl + + + ${1}]]> + envar + envar + + + + ${1} +]]> + epigraph + epigraph + + + + ${1} +]]> + equation + equation + + + ${1}]]> + errorcode + errorcode + + + ${1}]]> + errorname + errorname + + + ${1}]]> + errortext + errortext + + + ${1}]]> + errortype + errortype + + + + ${2} +]]> + example + example + + + ${1}]]> + exceptionname + exceptionname + + + ${1}]]> + fax + fax + + + + ${2} +]]> + fieldsynopsis + fieldsynopsis + + + + ${2} +]]> + figure + figure + + + ${1}]]> + filename + filename + + + ${1}]]> + firstname + firstname + + + ${1}]]> + firstterm + firstterm + + + + ${1} +]]> + footnote + footnote + + + ]]> + footnoteref + footnoteref + + + ${1}]]> + foreignphrase + foreignphrase + + + + ${1} +]]> + formalpara + formalpara + + + ${1}]]> + funcdef + funcdef + + + ${1}]]> + funcparams + funcparams + + + + ${1} +]]> + funcprototype + funcprototype + + + + ${1} +]]> + funcsynopsis + funcsynopsis + + + + ${1} +]]> + funcsynopsisinfo + funcsynopsisinfo + + + ${1}]]> + function + function + + + + ${2} +]]> + glossary + glossary + + + + ${1} +]]> + glossaryinfo + glossaryinfo + + + + ${1} +]]> + glossdef + glossdef + + + + ${1} +]]> + glossdiv + glossdiv + + + + ${1} +]]> + glossentry + glossentry + + + + ${1} +]]> + glosslist + glosslist + + + ${2}]]> + glosssee + glosssee + + + ${2}]]> + glossseealso + glossseealso + + + ${1}]]> + glossterm + glossterm + + + ]]> + graphic + graphic + + + + ${1} +]]> + graphicco + graphicco + + + + ${1} +]]> + group + group + + + ${1}]]> + guibutton + guibutton + + + ${1}]]> + guiicon + guiicon + + + ${1}]]> + guilabel + guilabel + + + ${1}]]> + guimenu + guimenu + + + ${1}]]> + guimenuitem + guimenuitem + + + ${1}]]> + guisubmenu + guisubmenu + + + ${1}]]> + hardware + hardware + + + + ${1} +]]> + highlights + highlights + + + ${1}]]> + holder + holder + + + ${1}]]> + honorific + honorific + + + ]]> + imagedata + imagedata + + + + ${1} +]]> + imageobject + imageobject + + + + ${1} +]]> + imageobjectco + imageobjectco + + + + ${1} +]]> + important + important + + + + ${1} +]]> + index + index + + + + ${1} +]]> + indexdiv + indexdiv + + + + ${1} +]]> + indexentry + indexentry + + + + ${1} +]]> + indexinfo + indexinfo + + + + ${1} +]]> + indexterm + indexterm + + + + ${1} +]]> + informalequation + informalequation + + + + ${1} +]]> + informalexample + informalexample + + + + ${1} +]]> + informalfigure + informalfigure + + + + ${1} +]]> + informaltable + informaltable + + + ${1}]]> + initializer + initializer + + + + ${1} +]]> + inlineequation + inlineequation + + + ]]> + inlinegraphic + inlinegraphic + + + + ${1} +]]> + inlinemediaobject + inlinemediaobject + + + ${1}]]> + interface + interface + + + ${1}]]> + interfacename + interfacename + + + ${1}]]> + invpartnumber + invpartnumber + + + ${1}]]> + isbn + isbn + + + ${1}]]> + issn + issn + + + ${1}]]> + issuenum + issuenum + + + + ${1} +]]> + itemizedlist + itemizedlist + + + + ${1} +]]> + itermset + itermset + + + ${1}]]> + jobtitle + jobtitle + + + ${1}]]> + keycap + keycap + + + ${1}]]> + keycode + keycode + + + ${1}]]> + keycombo + keycombo + + + ${1}]]> + keysym + keysym + + + ${1}]]> + keyword + keyword + + + + ${1} +]]> + keywordset + keywordset + + + ${1}]]> + label + label + + + + ${1} +]]> + legalnotice + legalnotice + + + ${1}]]> + lineage + lineage + + + ${1}]]> + lineannotation + lineannotation + + + ${2}]]> + link + link + + + + ${1} +]]> + listitem + listitem + + + ${1}]]> + literal + literal + + + ${1}]]> + literallayout + literallayout + + + + ${1} +]]> + lot + lot + + + + ${2} +]]> + lotentry + lotentry + + + ${1}]]> + manvolnum + manvolnum + + + ${1}]]> + markup + markup + + + ${1}]]> + mathphrase + mathphrase + + + ${1}]]> + medialabel + medialabel + + + + ${1} +]]> + mediaobject + mediaobject + + + + ${1} +]]> + mediaobjectco + mediaobjectco + + + + ${1} +]]> + member + member + + + ${1}]]> + menuchoice + menuchoice + + + ${1}]]> + methodname + methodname + + + ${1}]]> + methodparam + methodparam + + + + ${2} +]]> + methodsynopsis + methodsynopsis + + + ${2}]]> + modespec + modespec + + + ${1}]]> + modifier + modifier + + + ${1}]]> + mousebutton + mousebutton + + + + ${1} +]]> + msg + msg + + + ${1}]]> + msgaud + msgaud + + + + ${1} +]]> + msgentry + msgentry + + + + ${1} +]]> + msgexplan + msgexplan + + + + ${1} +]]> + msginfo + msginfo + + + ${1}]]> + msglevel + msglevel + + + + ${1} +]]> + msgmain + msgmain + + + ${1}]]> + msgorig + msgorig + + + + ${1} +]]> + msgrel + msgrel + + + + ${1} +]]> + msgset + msgset + + + + ${1} +]]> + msgsub + msgsub + + + + ${1} +]]> + msgtext + msgtext + + + + ${1} +]]> + note + note + + + + ${1} +]]> + objectinfo + objectinfo + + + ${2}]]> + olink + olink + + + ${1}]]> + ooclass + ooclass + + + ${1}]]> + ooexception + ooexception + + + ${1}]]> + oointerface + oointerface + + + ${1}]]> + option + option + + + ${1}]]> + optional + optional + + + + ${1} +]]> + orderedlist + orderedlist + + + ${1}]]> + orgdiv + orgdiv + + + ${1}]]> + orgname + orgname + + + ${1}]]> + otheraddr + otheraddr + + + + ${1} +]]> + othercredit + othercredit + + + ${1}]]> + othername + othername + + + ${1}]]> + package + package + + + ${1}]]> + pagenums + pagenums + + + + ${1} +]]> + para + para + + + ${1}]]> + paramdef + paramdef + + + ${2}]]> + parameter + parameter + + + + ${2} +]]> + part + part + + + + ${1} +]]> + partinfo + partinfo + + + + ${1} +]]> + partintro + partintro + + + + ${1} +]]> + personblurb + personblurb + + + ${1}]]> + personname + personname + + + ${1}]]> + phone + phone + + + ${1}]]> + phrase + phrase + + + ${1}]]> + pob + pob + + + ${1}]]> + postcode + postcode + + + + ${2} +]]> + preface + preface + + + + ${1} +]]> + prefaceinfo + prefaceinfo + + + ${1}]]> + primary + primary + + + ${1}]]> + primaryie + primaryie + + + + ${1} +]]> + printhistory + printhistory + + + + ${1} +]]> + procedure + procedure + + + ${2}]]> + productname + productname + + + ${1}]]> + productnumber + productnumber + + + ${2}]]> + programlisting + programlisting + + + + ${1} +]]> + programlistingco + programlistingco + + + ${1}]]> + prompt + prompt + + + ${1}]]> + property + property + + + ${1}]]> + pubdate + pubdate + + + + ${1} +]]> + publisher + publisher + + + ${1}]]> + publishername + publishername + + + ${1}]]> + pubsnumber + pubsnumber + + + + ${1} +]]> + qandadiv + qandadiv + + + + ${1} +]]> + qandaentry + qandaentry + + + + ${1} +]]> + qandaset + qandaset + + + + ${1} +]]> + question + question + + + ${1}]]> + quote + quote + + + ${1}]]> + refclass + refclass + + + ${1}]]> + refdescriptor + refdescriptor + + + + ${1} +]]> + refentry + refentry + + + + ${1} +]]> + refentryinfo + refentryinfo + + + ${1}]]> + refentrytitle + refentrytitle + + + + ${1} +]]> + reference + reference + + + + ${1} +]]> + referenceinfo + referenceinfo + + + + ${1} +]]> + refmeta + refmeta + + + + ${1} +]]> + refmiscinfo + refmiscinfo + + + ${1}]]> + refname + refname + + + + ${1} +]]> + refnamediv + refnamediv + + + ${1}]]> + refpurpose + refpurpose + + + + ${1} +]]> + refsect1 + refsect1 + + + + ${1} +]]> + refsect1info + refsect1info + + + + ${1} +]]> + refsect2 + refsect2 + + + + ${1} +]]> + refsect2info + refsect2info + + + + ${1} +]]> + refsect3 + refsect3 + + + + ${1} +]]> + refsect3info + refsect3info + + + + ${1} +]]> + refsection + refsection + + + + ${1} +]]> + refsectioninfo + refsectioninfo + + + + ${1} +]]> + refsynopsisdiv + refsynopsisdiv + + + + ${1} +]]> + refsynopsisdivinfo + refsynopsisdivinfo + + + ${1}]]> + releaseinfo + releaseinfo + + + ${1}]]> + remark + remark + + + ${1}]]> + replaceable + replaceable + + + ${1}]]> + returnvalue + returnvalue + + + + ${1} +]]> + revdescription + revdescription + + + + ${1} +]]> + revhistory + revhistory + + + + ${1} +]]> + revision + revision + + + ${1}]]> + revnumber + revnumber + + + ${1}]]> + revremark + revremark + + + + ${1} +]]> + row + row + + + ]]> + sbr + sbr + + + ${1}]]> + screen + screen + + + + ${1} +]]> + screenco + screenco + + + ${1}]]> + screeninfo + screeninfo + + + + ${1} +]]> + screenshot + screenshot + + + ${1}]]> + secondary + secondary + + + ${1}]]> + secondaryie + secondaryie + + + + ${2} +]]> + sect1 + sect1 + + + + ${1} +]]> + sect1info + sect1info + + + + ${2} +]]> + sect2 + sect2 + + + + ${1} +]]> + sect2info + sect2info + + + + ${2} +]]> + sect3 + sect3 + + + + ${1} +]]> + sect3info + sect3info + + + + ${2} +]]> + sect4 + sect4 + + + + ${1} +]]> + sect4info + sect4info + + + + ${2} +]]> + sect5 + sect5 + + + + ${1} +]]> + sect5info + sect5info + + + + ${2} +]]> + section + section + + + + ${1} +]]> + sectioninfo + sectioninfo + + + ${1}]]> + see + see + + + ${1}]]> + seealso + seealso + + + ${1}]]> + seealsoie + seealsoie + + + ${1}]]> + seeie + seeie + + + ${1}]]> + seg + seg + + + + ${1} +]]> + seglistitem + seglistitem + + + + ${1} +]]> + segmentedlist + segmentedlist + + + ${1}]]> + segtitle + segtitle + + + ${1}]]> + seriesvolnums + seriesvolnums + + + + ${1} +]]> + set + set + + + + ${1} +]]> + setindex + setindex + + + + ${1} +]]> + setindexinfo + setindexinfo + + + + ${1} +]]> + setinfo + setinfo + + + ${1}]]> + sgmltag + sgmltag + + + ${1}]]> + shortaffil + shortaffil + + + ${1}]]> + shortcut + shortcut + + + + ${1} +]]> + sidebar + sidebar + + + + ${1} +]]> + sidebarinfo + sidebarinfo + + + + ${1} +]]> + simpara + simpara + + + + ${1} +]]> + simplelist + simplelist + + + + ${1} +]]> + simplemsgentry + simplemsgentry + + + + ${2} +]]> + simplesect + simplesect + + + ]]> + spanspec + spanspec + + + ${1}]]> + state + state + + + + ${1} +]]> + step + step + + + + ${1} +]]> + stepalternatives + stepalternatives + + + ${1}]]> + street + street + + + ${1}]]> + structfield + structfield + + + ${1}]]> + structname + structname + + + + ${1} +]]> + subject + subject + + + + ${1} +]]> + subjectset + subjectset + + + ${1}]]> + subjectterm + subjectterm + + + ${1}]]> + subscript + subscript + + + + ${1} +]]> + substeps + substeps + + + ${1}]]> + subtitle + subtitle + + + ${1}]]> + superscript + superscript + + + ${1}]]> + surname + surname + + + ${1}]]> + symbol + symbol + + + + ${2} +]]> + synopfragment + synopfragment + + + + ${2} +]]> + synopfragmentref + synopfragmentref + + + ${1}]]> + synopsis + synopsis + + + ${1}]]> + systemitem + systemitem + + + + ${2} +]]> + table + table + + + + ${1} +]]> + task + task + + + + ${1} +]]> + taskprerequisites + taskprerequisites + + + + ${1} +]]> + taskrelated + taskrelated + + + + ${1} +]]> + tasksummary + tasksummary + + + + ${1} +]]> + tbody + tbody + + + + ${1} +]]> + td + td + + + ${1}]]> + term + term + + + ${1}]]> + termdef + termdef + + + ${1}]]> + tertiary + tertiary + + + ${1}]]> + tertiaryie + tertiaryie + + + ]]> + textdata + textdata + + + + ${1} +]]> + textobject + textobject + + + + ${1} +]]> + tfoot + tfoot + + + + ${2} +]]> + tgroup + tgroup + + + + ${1} +]]> + th + th + + + + ${1} +]]> + thead + thead + + + + ${1} +]]> + tip + tip + + + ${1}]]> + title + title + + + ${1}]]> + titleabbrev + titleabbrev + + + + ${1} +]]> + toc + toc + + + ${2}]]> + tocback + tocback + + + + ${1} +]]> + tocchap + tocchap + + + ${2}]]> + tocentry + tocentry + + + ${2}]]> + tocfront + tocfront + + + + ${1} +]]> + toclevel1 + toclevel1 + + + + ${1} +]]> + toclevel2 + toclevel2 + + + + ${1} +]]> + toclevel3 + toclevel3 + + + + ${1} +]]> + toclevel4 + toclevel4 + + + + ${1} +]]> + toclevel5 + toclevel5 + + + + ${1} +]]> + tocpart + tocpart + + + ${1}]]> + token + token + + + + ${1} +]]> + tr + tr + + + ${2}]]> + trademark + trademark + + + ${1}]]> + type + type + + + ${2}]]> + ulink + ulink + + + ${1}]]> + uri + uri + + + ${1}]]> + userinput + userinput + + + ]]> + varargs + varargs + + + + ${1} +]]> + variablelist + variablelist + + + + ${1} +]]> + varlistentry + varlistentry + + + ${1}]]> + varname + varname + + + ]]> + videodata + videodata + + + + ${1} +]]> + videoobject + videoobject + + + ]]> + void + void + + + ${1}]]> + volumenum + volumenum + + + + ${1} +]]> + warning + warning + + + ${1}]]> + wordasword + wordasword + + + ]]> + xref + xref + + + ${1}]]> + year + year + + + + ${1}]]> + bold + emphasis, role bold + + + ${1}]]> + strong + emphasis, role strong + + + ${1}]]> + devicefile + filename, class devicefile + + + ${1}]]> + directory + filename, class directory + + + ${1}]]> + extension + filename, class extension + + + ${1}]]> + headerfile + filename, class headerfile + + + ${1}]]> + libraryfile + filename, class libraryfile + + + ${1}]]> + partition + filename, class partition + + + ${1}]]> + symlink + filename, class symlink + + + ${1}]]> + cartridge + medialabel, class cartridge + + + ${1}]]> + cdrom + medialabel, class cdrom + + + ${1}]]> + disk + medialabel, class disk + + + ${1}]]> + tape + medialabel, class tape + + + ${1}]]> + daemon + systemitem, class daemon + + + ${1}]]> + domainname + systemitem, class domainname + + + ${1}]]> + etheraddress + systemitem, class etheraddress + + + ${1}]]> + eventhandler + systemitem, class eventhandler + + + ${1}]]> + event + systemitem, class event + + + ${1}]]> + filesystem + systemitem, class filesystem + + + ${1}]]> + fqdomainname + systemitem, class fqdomainname + + + ${1}]]> + groupname + systemitem, class groupname + + + ${1}]]> + ipaddress + systemitem, class ipaddress + + + ${1}]]> + library + systemitem, class library + + + ${1}]]> + macro + systemitem, class macro + + + ${1}]]> + netmask + systemitem, class netmask + + + ${1}]]> + newsgroup + systemitem, class newsgroup + + + ${1}]]> + osname + systemitem, class osname + + + ${1}]]> + process + systemitem, class process + + + ${1}]]> + protocol + systemitem, class protocol + + + ${1}]]> + resource + systemitem, class resource + + + ${1}]]> + server + systemitem, class server + + + ${1}]]> + service + systemitem, class service + + + ${1}]]> + systemname + systemitem, class systemname + + + ${1}]]> + username + systemitem, class username + + + + ]]> + include + xi:include + + + + ${1} +]]> + fallback + xi:fallback + + + ]]> + xml + xml + + + +%BOOK_ENTITIES; +]>]]> + doctype + DOCTYPE + + + ]]> + entity + ENTITY + + diff --git a/plugins/snippets/data/fortran.xml b/plugins/snippets/data/fortran.xml new file mode 100644 index 0000000..205abe6 --- /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 100644 index 0000000..afe3c0b --- /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 100644 index 0000000..54a8e7d --- /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 100644 index 0000000..dd9faea --- /dev/null +++ b/plugins/snippets/data/html.xml @@ -0,0 +1,252 @@ + + + + +]]> + HTML5 Doctype + 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 + + + +]]> + HTML — 4.01 Strict + 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 + + + +$0]]> + Link + link + + + +$0]]> + Meta + meta + + + + space]]> + Non-Breaking Space + + + $1$0]]> + noscript + Noscript + + + $2$0]]> + option + Option + + + + $0 +]]> + Script + script + + + ]]> + Script With External Source + scriptsrc + + + + + $4 +$0 +]]> + select + Select + + + $2$0]]> + span + Span + + + + $0 + +]]> + Style + style + + + + ${4:Header} + ${5:Data} + $0 +]]> + Table + table + + + $0]]> + Text Area + textarea + + + ${1:Page Title} +$0]]> + Title + title + + + $1 +$0]]> + tr + Table Row + + + +
  • $1
  • +
  • $2
  • + $3 + +$0]]>
    + ul + Unordered List +
    +
    diff --git a/plugins/snippets/data/idl.xml b/plugins/snippets/data/idl.xml new file mode 100644 index 0000000..2b6ef30 --- /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 100644 index 0000000..f7f11c0 --- /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 100644 index 0000000..b55c5b3 --- /dev/null +++ b/plugins/snippets/data/javascript.xml @@ -0,0 +1,10 @@ + + + + + function + fun + + diff --git a/plugins/snippets/data/lang/snippets.lang b/plugins/snippets/data/lang/snippets.lang new file mode 100644 index 0000000..7b755cd --- /dev/null +++ b/plugins/snippets/data/lang/snippets.lang @@ -0,0 +1,160 @@ + + +