summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorPerberos <[email protected]>2011-11-07 16:46:58 -0300
committerPerberos <[email protected]>2011-11-07 16:46:58 -0300
commit528c1e5ff51e213936e800fc5a9a25da99c0bdf2 (patch)
tree77f8aa456b09367ba81f04d4562fc935f898a951 /plugins
downloadpluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.bz2
pluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.xz
initial
Diffstat (limited to 'plugins')
-rwxr-xr-xplugins/Makefile.am42
-rwxr-xr-xplugins/changecase/Makefile.am34
-rwxr-xr-xplugins/changecase/changecase.gedit-plugin.desktop.in8
-rwxr-xr-xplugins/changecase/gedit-changecase-plugin.c395
-rwxr-xr-xplugins/changecase/gedit-changecase-plugin.h72
-rwxr-xr-xplugins/checkupdate/Makefile.am49
-rwxr-xr-xplugins/checkupdate/checkupdate.gedit-plugin.desktop.in9
-rwxr-xr-xplugins/checkupdate/gedit-check-update-plugin.c697
-rwxr-xr-xplugins/checkupdate/gedit-check-update-plugin.h74
-rwxr-xr-xplugins/checkupdate/gedit-check-update.schemas.in13
-rwxr-xr-xplugins/docinfo/Makefile.am34
-rwxr-xr-xplugins/docinfo/docinfo.gedit-plugin.desktop.in7
-rwxr-xr-xplugins/docinfo/docinfo.ui621
-rwxr-xr-xplugins/docinfo/gedit-docinfo-plugin.c580
-rwxr-xr-xplugins/docinfo/gedit-docinfo-plugin.h75
-rwxr-xr-xplugins/externaltools/Makefile.am15
-rwxr-xr-xplugins/externaltools/data/Makefile.am65
-rwxr-xr-xplugins/externaltools/data/build.desktop.in9
-rwxr-xr-xplugins/externaltools/data/build.tool.in15
-rwxr-xr-xplugins/externaltools/data/open-terminal-here-osx.desktop.in8
-rwxr-xr-xplugins/externaltools/data/open-terminal-here-osx.tool.in16
-rwxr-xr-xplugins/externaltools/data/open-terminal-here.desktop.in8
-rwxr-xr-xplugins/externaltools/data/open-terminal-here.tool.in4
-rwxr-xr-xplugins/externaltools/data/remove-trailing-spaces.desktop.in9
-rwxr-xr-xplugins/externaltools/data/remove-trailing-spaces.tool.in3
-rwxr-xr-xplugins/externaltools/data/run-command.desktop.in8
-rwxr-xr-xplugins/externaltools/data/run-command.tool.in4
-rwxr-xr-xplugins/externaltools/externaltools.gedit-plugin.desktop.in9
-rwxr-xr-xplugins/externaltools/scripts/Makefile.am4
-rwxr-xr-xplugins/externaltools/scripts/gedit-tool-merge.pl78
-rwxr-xr-xplugins/externaltools/tools/Makefile.am23
-rwxr-xr-xplugins/externaltools/tools/__init__.py281
-rwxr-xr-xplugins/externaltools/tools/capture.py214
-rwxr-xr-xplugins/externaltools/tools/filelookup.py145
-rwxr-xr-xplugins/externaltools/tools/functions.py303
-rwxr-xr-xplugins/externaltools/tools/library.py493
-rwxr-xr-xplugins/externaltools/tools/linkparsing.py231
-rwxr-xr-xplugins/externaltools/tools/manager.py948
-rwxr-xr-xplugins/externaltools/tools/outputpanel.py224
-rwxr-xr-xplugins/externaltools/tools/outputpanel.ui53
-rwxr-xr-xplugins/externaltools/tools/tools.ui606
-rwxr-xr-xplugins/filebrowser/Makefile.am104
-rwxr-xr-xplugins/filebrowser/filebrowser.gedit-plugin.desktop.in10
-rwxr-xr-xplugins/filebrowser/gedit-file-bookmarks-store.c879
-rwxr-xr-xplugins/filebrowser/gedit-file-bookmarks-store.h90
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-enum-register.c.template20
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-enum-types.c.template45
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-enum-types.h.template29
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-error.h41
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-marshal.list5
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-messages.c1033
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-messages.h35
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-plugin.c1254
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-plugin.h71
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-store.c3625
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-store.h200
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-utils.c198
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-utils.h27
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-view.c1256
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-view.h84
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-widget-ui.xml54
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-widget.c3143
-rwxr-xr-xplugins/filebrowser/gedit-file-browser-widget.h121
-rwxr-xr-xplugins/filebrowser/gedit-file-browser.schemas.in97
-rwxr-xr-xplugins/modelines/Makefile.am38
-rwxr-xr-xplugins/modelines/gedit-modeline-plugin.c248
-rwxr-xr-xplugins/modelines/gedit-modeline-plugin.h48
-rwxr-xr-xplugins/modelines/language-mappings14
-rwxr-xr-xplugins/modelines/modeline-parser.c852
-rwxr-xr-xplugins/modelines/modeline-parser.h37
-rwxr-xr-xplugins/modelines/modelines.gedit-plugin.desktop.in8
-rwxr-xr-xplugins/pythonconsole/Makefile.am15
-rwxr-xr-xplugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in10
-rwxr-xr-xplugins/pythonconsole/pythonconsole/Makefile.am17
-rwxr-xr-xplugins/pythonconsole/pythonconsole/__init__.py78
-rwxr-xr-xplugins/pythonconsole/pythonconsole/config.py134
-rwxr-xr-xplugins/pythonconsole/pythonconsole/config.ui107
-rwxr-xr-xplugins/pythonconsole/pythonconsole/console.py370
-rwxr-xr-xplugins/quickopen/Makefile.am15
-rwxr-xr-xplugins/quickopen/quickopen.gedit-plugin.desktop.in10
-rwxr-xr-xplugins/quickopen/quickopen/Makefile.am13
-rwxr-xr-xplugins/quickopen/quickopen/__init__.py46
-rwxr-xr-xplugins/quickopen/quickopen/popup.py534
-rwxr-xr-xplugins/quickopen/quickopen/virtualdirs.py87
-rwxr-xr-xplugins/quickopen/quickopen/windowhelper.py198
-rwxr-xr-xplugins/snippets/Makefile.am15
-rwxr-xr-xplugins/snippets/data/Makefile.am33
-rwxr-xr-xplugins/snippets/data/c.xml283
-rwxr-xr-xplugins/snippets/data/chdr.xml241
-rwxr-xr-xplugins/snippets/data/cpp.xml183
-rwxr-xr-xplugins/snippets/data/css.xml557
-rwxr-xr-xplugins/snippets/data/docbook.xml118
-rwxr-xr-xplugins/snippets/data/fortran.xml164
-rwxr-xr-xplugins/snippets/data/global.xml2
-rwxr-xr-xplugins/snippets/data/haskell.xml14
-rwxr-xr-xplugins/snippets/data/html.xml246
-rwxr-xr-xplugins/snippets/data/idl.xml49
-rwxr-xr-xplugins/snippets/data/java.xml91
-rwxr-xr-xplugins/snippets/data/javascript.xml11
-rwxr-xr-xplugins/snippets/data/lang/Makefile.am9
-rwxr-xr-xplugins/snippets/data/lang/snippets.lang162
-rwxr-xr-xplugins/snippets/data/latex.xml38
-rwxr-xr-xplugins/snippets/data/mallard.xml207
-rwxr-xr-xplugins/snippets/data/perl.xml126
-rwxr-xr-xplugins/snippets/data/php.xml224
-rwxr-xr-xplugins/snippets/data/python.xml112
-rwxr-xr-xplugins/snippets/data/ruby.xml166
-rwxr-xr-xplugins/snippets/data/sh.xml47
-rwxr-xr-xplugins/snippets/data/snippets.xml98
-rwxr-xr-xplugins/snippets/data/tcl.xml55
-rwxr-xr-xplugins/snippets/data/xml.xml25
-rwxr-xr-xplugins/snippets/data/xslt.xml143
-rwxr-xr-xplugins/snippets/snippets.gedit-plugin.desktop.in9
-rwxr-xr-xplugins/snippets/snippets/Completion.py165
-rwxr-xr-xplugins/snippets/snippets/Document.py1089
-rwxr-xr-xplugins/snippets/snippets/Exporter.py98
-rwxr-xr-xplugins/snippets/snippets/Helper.py182
-rwxr-xr-xplugins/snippets/snippets/Importer.py100
-rwxr-xr-xplugins/snippets/snippets/LanguageManager.py21
-rwxr-xr-xplugins/snippets/snippets/Library.py993
-rwxr-xr-xplugins/snippets/snippets/Makefile.am28
-rwxr-xr-xplugins/snippets/snippets/Manager.py1157
-rwxr-xr-xplugins/snippets/snippets/Parser.py259
-rwxr-xr-xplugins/snippets/snippets/Placeholder.py700
-rwxr-xr-xplugins/snippets/snippets/Snippet.py355
-rwxr-xr-xplugins/snippets/snippets/SubstitutionParser.py202
-rwxr-xr-xplugins/snippets/snippets/WindowHelper.py209
-rwxr-xr-xplugins/snippets/snippets/__init__.py101
-rwxr-xr-xplugins/snippets/snippets/snippets.ui647
-rwxr-xr-xplugins/sort/Makefile.am34
-rwxr-xr-xplugins/sort/gedit-sort-plugin.c588
-rwxr-xr-xplugins/sort/gedit-sort-plugin.h73
-rwxr-xr-xplugins/sort/sort.gedit-plugin.desktop.in9
-rwxr-xr-xplugins/sort/sort.ui275
-rwxr-xr-xplugins/spell/Makefile.am63
-rwxr-xr-xplugins/spell/gedit-automatic-spell-checker.c1015
-rwxr-xr-xplugins/spell/gedit-automatic-spell-checker.h67
-rwxr-xr-xplugins/spell/gedit-spell-checker-dialog.c722
-rwxr-xr-xplugins/spell/gedit-spell-checker-dialog.h92
-rwxr-xr-xplugins/spell/gedit-spell-checker-language.c439
-rwxr-xr-xplugins/spell/gedit-spell-checker-language.h51
-rwxr-xr-xplugins/spell/gedit-spell-checker.c520
-rwxr-xr-xplugins/spell/gedit-spell-checker.h109
-rwxr-xr-xplugins/spell/gedit-spell-language-dialog.c309
-rwxr-xr-xplugins/spell/gedit-spell-language-dialog.h67
-rwxr-xr-xplugins/spell/gedit-spell-marshal.list6
-rwxr-xr-xplugins/spell/gedit-spell-plugin.c1217
-rwxr-xr-xplugins/spell/gedit-spell-plugin.h75
-rwxr-xr-xplugins/spell/gedit-spell-utils.c94
-rwxr-xr-xplugins/spell/gedit-spell-utils.h37
-rwxr-xr-xplugins/spell/languages-dialog.ui145
-rwxr-xr-xplugins/spell/spell-checker.ui482
-rwxr-xr-xplugins/spell/spell.gedit-plugin.desktop.in9
-rwxr-xr-xplugins/taglist/HTML.tags.xml.in2672
-rwxr-xr-xplugins/taglist/Latex.tags.xml.in344
-rwxr-xr-xplugins/taglist/Makefile.am60
-rwxr-xr-xplugins/taglist/XSLT.tags.xml.in337
-rwxr-xr-xplugins/taglist/XUL.tags.xml.in536
-rwxr-xr-xplugins/taglist/gedit-taglist-plugin-panel.c776
-rwxr-xr-xplugins/taglist/gedit-taglist-plugin-panel.h89
-rwxr-xr-xplugins/taglist/gedit-taglist-plugin-parser.c655
-rwxr-xr-xplugins/taglist/gedit-taglist-plugin-parser.h68
-rwxr-xr-xplugins/taglist/gedit-taglist-plugin.c160
-rwxr-xr-xplugins/taglist/gedit-taglist-plugin.h85
-rwxr-xr-xplugins/taglist/taglist.gedit-plugin.desktop.in8
-rwxr-xr-xplugins/time/Makefile.am36
-rwxr-xr-xplugins/time/gedit-time-dialog.ui297
-rwxr-xr-xplugins/time/gedit-time-plugin.c1272
-rwxr-xr-xplugins/time/gedit-time-plugin.h78
-rwxr-xr-xplugins/time/gedit-time-setup-dialog.ui330
-rwxr-xr-xplugins/time/time.gedit-plugin.desktop.in8
171 files changed, 45829 insertions, 0 deletions
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
new file mode 100755
index 00000000..ba80e090
--- /dev/null
+++ b/plugins/Makefile.am
@@ -0,0 +1,42 @@
+DIST_SUBDIRS = \
+ changecase \
+ checkupdate \
+ docinfo \
+ externaltools \
+ filebrowser \
+ modelines \
+ pythonconsole \
+ quickopen \
+ snippets \
+ sort \
+ spell \
+ taglist \
+ time
+
+SUBDIRS = \
+ changecase \
+ docinfo \
+ filebrowser \
+ modelines \
+ sort \
+ taglist \
+ time
+
+if ENABLE_PYTHON
+SUBDIRS += pythonconsole snippets quickopen
+
+if !OS_WIN32
+SUBDIRS += externaltools
+endif
+
+endif
+
+if ENABLE_ENCHANT
+SUBDIRS += spell
+endif
+
+if ENABLE_UPDATER
+SUBDIRS += checkupdate
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/changecase/Makefile.am b/plugins/changecase/Makefile.am
new file mode 100755
index 00000000..1f165f9e
--- /dev/null
+++ b/plugins/changecase/Makefile.am
@@ -0,0 +1,34 @@
+# changecase plugin
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+plugin_LTLIBRARIES = libchangecase.la
+
+libchangecase_la_SOURCES = \
+ gedit-changecase-plugin.h \
+ gedit-changecase-plugin.c
+
+libchangecase_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libchangecase_la_LIBADD = $(GEDIT_LIBS)
+
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/changecase
+ui_DATA =
+
+plugin_in_files = changecase.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(ui_DATA) $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/changecase/changecase.gedit-plugin.desktop.in b/plugins/changecase/changecase.gedit-plugin.desktop.in
new file mode 100755
index 00000000..52a226f4
--- /dev/null
+++ b/plugins/changecase/changecase.gedit-plugin.desktop.in
@@ -0,0 +1,8 @@
+[Gedit Plugin]
+Module=changecase
+IAge=2
+_Name=Change Case
+_Description=Changes the case of selected text.
+Authors=Paolo Borelli <[email protected]>
+Copyright=Copyright © 2004-2005 Paolo Borelli
+Website=http://www.gedit.org
diff --git a/plugins/changecase/gedit-changecase-plugin.c b/plugins/changecase/gedit-changecase-plugin.c
new file mode 100755
index 00000000..8544aeb0
--- /dev/null
+++ b/plugins/changecase/gedit-changecase-plugin.c
@@ -0,0 +1,395 @@
+/*
+ * gedit-changecase-plugin.c
+ *
+ * Copyright (C) 2004-2005 - Paolo Borelli
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gedit-changecase-plugin.h"
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+
+#include <gedit/gedit-debug.h>
+
+#define WINDOW_DATA_KEY "GeditChangecasePluginWindowData"
+
+GEDIT_PLUGIN_REGISTER_TYPE(GeditChangecasePlugin, gedit_changecase_plugin)
+
+typedef enum {
+ TO_UPPER_CASE,
+ TO_LOWER_CASE,
+ INVERT_CASE,
+ TO_TITLE_CASE,
+} ChangeCaseChoice;
+
+static void
+do_upper_case (GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GString *s = g_string_new (NULL);
+
+ while (!gtk_text_iter_is_end (start) &&
+ !gtk_text_iter_equal (start, end))
+ {
+ gunichar c, nc;
+
+ c = gtk_text_iter_get_char (start);
+ nc = g_unichar_toupper (c);
+ g_string_append_unichar (s, nc);
+
+ gtk_text_iter_forward_char (start);
+ }
+
+ gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
+ gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len);
+
+ g_string_free (s, TRUE);
+}
+
+static void
+do_lower_case (GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GString *s = g_string_new (NULL);
+
+ while (!gtk_text_iter_is_end (start) &&
+ !gtk_text_iter_equal (start, end))
+ {
+ gunichar c, nc;
+
+ c = gtk_text_iter_get_char (start);
+ nc = g_unichar_tolower (c);
+ g_string_append_unichar (s, nc);
+
+ gtk_text_iter_forward_char (start);
+ }
+
+ gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
+ gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len);
+
+ g_string_free (s, TRUE);
+}
+
+static void
+do_invert_case (GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GString *s = g_string_new (NULL);
+
+ while (!gtk_text_iter_is_end (start) &&
+ !gtk_text_iter_equal (start, end))
+ {
+ gunichar c, nc;
+
+ c = gtk_text_iter_get_char (start);
+ if (g_unichar_islower (c))
+ nc = g_unichar_toupper (c);
+ else
+ nc = g_unichar_tolower (c);
+ g_string_append_unichar (s, nc);
+
+ gtk_text_iter_forward_char (start);
+ }
+
+ gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
+ gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len);
+
+ g_string_free (s, TRUE);
+}
+
+static void
+do_title_case (GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GString *s = g_string_new (NULL);
+
+ while (!gtk_text_iter_is_end (start) &&
+ !gtk_text_iter_equal (start, end))
+ {
+ gunichar c, nc;
+
+ c = gtk_text_iter_get_char (start);
+ if (gtk_text_iter_starts_word (start))
+ nc = g_unichar_totitle (c);
+ else
+ nc = g_unichar_tolower (c);
+ g_string_append_unichar (s, nc);
+
+ gtk_text_iter_forward_char (start);
+ }
+
+ gtk_text_buffer_delete_selection (buffer, TRUE, TRUE);
+ gtk_text_buffer_insert_at_cursor (buffer, s->str, s->len);
+
+ g_string_free (s, TRUE);
+}
+
+static void
+change_case (GeditWindow *window,
+ ChangeCaseChoice choice)
+{
+ GeditDocument *doc;
+ GtkTextIter start, end;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ doc = gedit_window_get_active_document (window);
+ g_return_if_fail (doc != NULL);
+
+ if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &start, &end))
+ {
+ return;
+ }
+
+ gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (doc));
+
+ switch (choice)
+ {
+ case TO_UPPER_CASE:
+ do_upper_case (GTK_TEXT_BUFFER (doc), &start, &end);
+ break;
+ case TO_LOWER_CASE:
+ do_lower_case (GTK_TEXT_BUFFER (doc), &start, &end);
+ break;
+ case INVERT_CASE:
+ do_invert_case (GTK_TEXT_BUFFER (doc), &start, &end);
+ break;
+ case TO_TITLE_CASE:
+ do_title_case (GTK_TEXT_BUFFER (doc), &start, &end);
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (doc));
+}
+
+static void
+upper_case_cb (GtkAction *action,
+ GeditWindow *window)
+{
+ change_case (window, TO_UPPER_CASE);
+}
+
+static void
+lower_case_cb (GtkAction *action,
+ GeditWindow *window)
+{
+ change_case (window, TO_LOWER_CASE);
+}
+
+static void
+invert_case_cb (GtkAction *action,
+ GeditWindow *window)
+{
+ change_case (window, INVERT_CASE);
+}
+
+static void
+title_case_cb (GtkAction *action,
+ GeditWindow *window)
+{
+ change_case (window, TO_TITLE_CASE);
+}
+
+static const GtkActionEntry action_entries[] =
+{
+ { "ChangeCase", NULL, N_("C_hange Case") },
+ { "UpperCase", NULL, N_("All _Upper Case"), NULL,
+ N_("Change selected text to upper case"),
+ G_CALLBACK (upper_case_cb) },
+ { "LowerCase", NULL, N_("All _Lower Case"), NULL,
+ N_("Change selected text to lower case"),
+ G_CALLBACK (lower_case_cb) },
+ { "InvertCase", NULL, N_("_Invert Case"), NULL,
+ N_("Invert the case of selected text"),
+ G_CALLBACK (invert_case_cb) },
+ { "TitleCase", NULL, N_("_Title Case"), NULL,
+ N_("Capitalize the first letter of each selected word"),
+ G_CALLBACK (title_case_cb) }
+};
+
+const gchar submenu[] =
+"<ui>"
+" <menubar name='MenuBar'>"
+" <menu name='EditMenu' action='Edit'>"
+" <placeholder name='EditOps_6'>"
+" <menu action='ChangeCase'>"
+" <menuitem action='UpperCase'/>"
+" <menuitem action='LowerCase'/>"
+" <menuitem action='InvertCase'/>"
+" <menuitem action='TitleCase'/>"
+" </menu>"
+" </placeholder>"
+" </menu>"
+" </menubar>"
+"</ui>";
+
+static void
+gedit_changecase_plugin_init (GeditChangecasePlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditChangecasePlugin initializing");
+}
+
+static void
+gedit_changecase_plugin_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (gedit_changecase_plugin_parent_class)->finalize (object);
+
+ gedit_debug_message (DEBUG_PLUGINS, "GeditChangecasePlugin finalizing");
+}
+
+typedef struct
+{
+ GtkActionGroup *action_group;
+ guint ui_id;
+} WindowData;
+
+static void
+free_window_data (WindowData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ g_slice_free (WindowData, data);
+}
+
+static void
+update_ui_real (GeditWindow *window,
+ WindowData *data)
+{
+ GtkTextView *view;
+ GtkAction *action;
+ gboolean sensitive = FALSE;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ view = GTK_TEXT_VIEW (gedit_window_get_active_view (window));
+
+ if (view != NULL)
+ {
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (view);
+ sensitive = (gtk_text_view_get_editable (view) &&
+ gtk_text_buffer_get_has_selection (buffer));
+ }
+
+ action = gtk_action_group_get_action (data->action_group,
+ "ChangeCase");
+ gtk_action_set_sensitive (action, sensitive);
+}
+
+static void
+impl_activate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = g_slice_new (WindowData);
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data->action_group = gtk_action_group_new ("GeditChangecasePluginActions");
+ gtk_action_group_set_translation_domain (data->action_group,
+ GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (data->action_group,
+ action_entries,
+ G_N_ELEMENTS (action_entries),
+ window);
+
+ gtk_ui_manager_insert_action_group (manager, data->action_group, -1);
+
+ data->ui_id = gtk_ui_manager_add_ui_from_string (manager,
+ submenu,
+ -1,
+ &error);
+ if (data->ui_id == 0)
+ {
+ g_warning ("%s", error->message);
+ free_window_data (data);
+ return;
+ }
+
+ g_object_set_data_full (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ data,
+ (GDestroyNotify) free_window_data);
+
+ update_ui_real (window, data);
+}
+
+static void
+impl_deactivate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ gtk_ui_manager_remove_ui (manager, data->ui_id);
+ gtk_ui_manager_remove_action_group (manager, data->action_group);
+
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL);
+}
+
+static void
+impl_update_ui (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ update_ui_real (window, data);
+}
+
+static void
+gedit_changecase_plugin_class_init (GeditChangecasePluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ object_class->finalize = gedit_changecase_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+ plugin_class->update_ui = impl_update_ui;
+}
diff --git a/plugins/changecase/gedit-changecase-plugin.h b/plugins/changecase/gedit-changecase-plugin.h
new file mode 100755
index 00000000..9587928c
--- /dev/null
+++ b/plugins/changecase/gedit-changecase-plugin.h
@@ -0,0 +1,72 @@
+/*
+ * gedit-changecase-plugin.h
+ *
+ * Copyright (C) 2004-2005 - Paolo Borelli
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifndef __GEDIT_CHANGECASE_PLUGIN_H__
+#define __GEDIT_CHANGECASE_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_CHANGECASE_PLUGIN (gedit_changecase_plugin_get_type ())
+#define GEDIT_CHANGECASE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePlugin))
+#define GEDIT_CHANGECASE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePluginClass))
+#define GEDIT_IS_CHANGECASE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_CHANGECASE_PLUGIN))
+#define GEDIT_IS_CHANGECASE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_CHANGECASE_PLUGIN))
+#define GEDIT_CHANGECASE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_CHANGECASE_PLUGIN, GeditChangecasePluginClass))
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditChangecasePlugin GeditChangecasePlugin;
+
+struct _GeditChangecasePlugin
+{
+ GeditPlugin parent_instance;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditChangecasePluginClass GeditChangecasePluginClass;
+
+struct _GeditChangecasePluginClass
+{
+ GeditPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_changecase_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_CHANGECASE_PLUGIN_H__ */
diff --git a/plugins/checkupdate/Makefile.am b/plugins/checkupdate/Makefile.am
new file mode 100755
index 00000000..551b7eee
--- /dev/null
+++ b/plugins/checkupdate/Makefile.am
@@ -0,0 +1,49 @@
+# gedit win32 updater
+
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(LIBSOUP_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+plugin_LTLIBRARIES = libcheckupdate.la
+
+libcheckupdate_la_SOURCES = \
+ gedit-check-update-plugin.h \
+ gedit-check-update-plugin.c
+
+libcheckupdate_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libcheckupdate_la_LIBADD = $(GEDIT_LIBS) $(LIBSOUP_LIBS)
+
+plugin_in_files = checkupdate.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+schemasdir = $(MATECONF_SCHEMA_FILE_DIR)
+schemas_in_files = gedit-check-update.schemas.in
+schemas_DATA = $(schemas_in_files:.schemas.in=.schemas)
+@INTLTOOL_SCHEMAS_RULE@
+
+if MATECONF_SCHEMAS_INSTALL
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schemas_DATA) ; do \
+ MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) $(MATECONFTOOL) --makefile-install-rule $(top_builddir)/plugins/checkupdate/$$p ; \
+ done \
+ fi
+else
+install-data-local:
+endif
+
+EXTRA_DIST = $(plugin_in_files) $(schemas_in_files)
+
+CLEANFILES = $(plugin_DATA) $(schemas_DATA)
+
+DISTCLEANFILES = $(plugin_DATA) $(schemas_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in b/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in
new file mode 100755
index 00000000..4699b6a2
--- /dev/null
+++ b/plugins/checkupdate/checkupdate.gedit-plugin.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Plugin]
+Module=checkupdate
+IAge=2
+_Name=Check update
+_Description=Check for latest version of gedit
+Icon=gedit-plugin
+Authors=Ignacio Casal Quinteiro <[email protected]>
+Copyright=Copyright © 2009 Ignacio Casal Quinteiro
+Website=http://www.gedit.org
diff --git a/plugins/checkupdate/gedit-check-update-plugin.c b/plugins/checkupdate/gedit-check-update-plugin.c
new file mode 100755
index 00000000..aa9f7a5e
--- /dev/null
+++ b/plugins/checkupdate/gedit-check-update-plugin.c
@@ -0,0 +1,697 @@
+/*
+ * Copyright (C) 2009 - Ignacio Casal Quinteiro <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gedit-check-update-plugin.h"
+
+#include <glib/gi18n-lib.h>
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-utils.h>
+#include <libsoup/soup.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+
+#include <mateconf/mateconf-client.h>
+
+#if !GTK_CHECK_VERSION(2, 17, 1)
+#include <gedit/gedit-message-area.h>
+#endif
+
+#define MATECONF_KEY_BASE "/apps/gedit-2/plugins/checkupdate"
+#define MATECONF_KEY_IGNORE_VERSION MATECONF_KEY_BASE "/ignore_version"
+
+#define WINDOW_DATA_KEY "GeditCheckUpdatePluginWindowData"
+
+#define VERSION_PLACE "<a href=\"[0-9]\\.[0-9]+/\">"
+
+#ifdef G_OS_WIN32
+#define GEDIT_URL "http://ftp.acc.umu.se/pub/mate/binaries/win32/gedit/"
+#define FILE_REGEX "gedit\\-setup\\-[0-9]+\\.[0-9]+\\.[0-9]+(\\-[0-9]+)?\\.exe"
+#else
+#define GEDIT_URL "http://ftp.acc.umu.se/pub/mate/binaries/mac/gedit/"
+#define FILE_REGEX "gedit\\-[0-9]+\\.[0-9]+\\.[0-9]+(\\-[0-9]+)?\\.dmg"
+#endif
+
+#ifdef OS_OSX
+#include "gedit/osx/gedit-osx.h"
+#endif
+
+#define GEDIT_CHECK_UPDATE_PLUGIN_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ GEDIT_TYPE_CHECK_UPDATE_PLUGIN, \
+ GeditCheckUpdatePluginPrivate))
+
+GEDIT_PLUGIN_REGISTER_TYPE (GeditCheckUpdatePlugin, gedit_check_update_plugin)
+
+struct _GeditCheckUpdatePluginPrivate
+{
+ SoupSession *session;
+
+ MateConfClient *mateconf_client;
+};
+
+typedef struct
+{
+ GeditCheckUpdatePlugin *plugin;
+
+ gchar *url;
+ gchar *version;
+} WindowData;
+
+static void
+free_window_data (gpointer data)
+{
+ WindowData *window_data;
+
+ if (data == NULL)
+ return;
+
+ window_data = (WindowData *)data;
+
+ g_free (window_data->url);
+ g_free (window_data->version);
+ g_slice_free (WindowData, data);
+}
+
+static void
+gedit_check_update_plugin_init (GeditCheckUpdatePlugin *plugin)
+{
+ plugin->priv = GEDIT_CHECK_UPDATE_PLUGIN_GET_PRIVATE (plugin);
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "GeditCheckUpdatePlugin initializing");
+
+ plugin->priv->session = soup_session_async_new ();
+
+ plugin->priv->mateconf_client = mateconf_client_get_default ();
+
+ mateconf_client_add_dir (plugin->priv->mateconf_client,
+ MATECONF_KEY_BASE,
+ MATECONF_CLIENT_PRELOAD_ONELEVEL,
+ NULL);
+}
+
+static void
+gedit_check_update_plugin_dispose (GObject *object)
+{
+ GeditCheckUpdatePlugin *plugin = GEDIT_CHECK_UPDATE_PLUGIN (object);
+
+ if (plugin->priv->session != NULL)
+ {
+ g_object_unref (plugin->priv->session);
+ plugin->priv->session = NULL;
+ }
+
+ if (plugin->priv->mateconf_client != NULL)
+ {
+ mateconf_client_suggest_sync (plugin->priv->mateconf_client, NULL);
+
+ g_object_unref (G_OBJECT (plugin->priv->mateconf_client));
+
+ plugin->priv->mateconf_client = NULL;
+ }
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "GeditCheckUpdatePlugin disposing");
+
+ G_OBJECT_CLASS (gedit_check_update_plugin_parent_class)->dispose (object);
+}
+
+static void
+gedit_check_update_plugin_finalize (GObject *object)
+{
+ gedit_debug_message (DEBUG_PLUGINS,
+ "GeditCheckUpdatePlugin finalizing");
+
+ G_OBJECT_CLASS (gedit_check_update_plugin_parent_class)->finalize (object);
+}
+
+static void
+set_contents (GtkWidget *infobar,
+ GtkWidget *contents)
+{
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ gedit_message_area_set_contents (GEDIT_MESSAGE_AREA (infobar),
+ contents);
+#else
+ GtkWidget *content_area;
+
+ content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (infobar));
+ gtk_container_add (GTK_CONTAINER (content_area), contents);
+#endif
+}
+
+static void
+set_message_area_text_and_icon (GtkWidget *message_area,
+ const gchar *icon_stock_id,
+ const gchar *primary_text,
+ const gchar *secondary_text)
+{
+ GtkWidget *hbox_content;
+ GtkWidget *image;
+ GtkWidget *vbox;
+ gchar *primary_markup;
+ gchar *secondary_markup;
+ GtkWidget *primary_label;
+ GtkWidget *secondary_label;
+
+ hbox_content = gtk_hbox_new (FALSE, 8);
+ gtk_widget_show (hbox_content);
+
+ image = gtk_image_new_from_stock (icon_stock_id, GTK_ICON_SIZE_DIALOG);
+ gtk_widget_show (image);
+ gtk_box_pack_start (GTK_BOX (hbox_content), image, FALSE, FALSE, 0);
+ gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0);
+
+ vbox = gtk_vbox_new (FALSE, 6);
+ gtk_widget_show (vbox);
+ gtk_box_pack_start (GTK_BOX (hbox_content), vbox, TRUE, TRUE, 0);
+
+ primary_markup = g_strdup_printf ("<b>%s</b>", primary_text);
+ primary_label = gtk_label_new (primary_markup);
+ g_free (primary_markup);
+ gtk_widget_show (primary_label);
+ gtk_box_pack_start (GTK_BOX (vbox), primary_label, TRUE, TRUE, 0);
+ gtk_label_set_use_markup (GTK_LABEL (primary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (primary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (primary_label), 0, 0.5);
+ GTK_WIDGET_SET_FLAGS (primary_label, GTK_CAN_FOCUS);
+ gtk_label_set_selectable (GTK_LABEL (primary_label), TRUE);
+
+ if (secondary_text != NULL)
+ {
+ secondary_markup = g_strdup_printf ("<small>%s</small>",
+ secondary_text);
+ secondary_label = gtk_label_new (secondary_markup);
+ g_free (secondary_markup);
+ gtk_widget_show (secondary_label);
+ gtk_box_pack_start (GTK_BOX (vbox), secondary_label, TRUE, TRUE, 0);
+ GTK_WIDGET_SET_FLAGS (secondary_label, GTK_CAN_FOCUS);
+ gtk_label_set_use_markup (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_line_wrap (GTK_LABEL (secondary_label), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (secondary_label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (secondary_label), 0, 0.5);
+ }
+
+ set_contents (message_area, hbox_content);
+}
+
+static void
+on_response_cb (GtkWidget *infobar,
+ gint response_id,
+ GeditWindow *window)
+{
+ if (response_id == GTK_RESPONSE_YES)
+ {
+ GError *error = NULL;
+ WindowData *data;
+
+ data = g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+
+#ifdef OS_OSX
+ gedit_osx_show_url (data->url);
+#else
+ gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (window)),
+ data->url,
+ GDK_CURRENT_TIME,
+ &error);
+#endif
+ if (error != NULL)
+ {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("There was an error displaying the URI."));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect (G_OBJECT (dialog),
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ gtk_widget_show (dialog);
+
+ g_error_free (error);
+ }
+ }
+ else if (response_id == GTK_RESPONSE_NO)
+ {
+ WindowData *data;
+
+ data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+
+ mateconf_client_set_string (data->plugin->priv->mateconf_client,
+ MATECONF_KEY_IGNORE_VERSION,
+ data->version,
+ NULL);
+ }
+
+ g_object_set_data (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ NULL);
+
+ gtk_widget_destroy (infobar);
+}
+
+static GtkWidget *
+create_infobar (GeditWindow *window,
+ const gchar *version)
+{
+ GtkWidget *infobar;
+ gchar *message;
+
+#if !GTK_CHECK_VERSION (2, 17, 1)
+ infobar = gedit_message_area_new ();
+
+ gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (infobar),
+ _("_Download"),
+ GTK_STOCK_SAVE,
+ GTK_RESPONSE_YES);
+ gedit_message_area_add_stock_button_with_text (GEDIT_MESSAGE_AREA (infobar),
+ _("_Ignore Version"),
+ GTK_STOCK_DISCARD,
+ GTK_RESPONSE_NO);
+ gedit_message_area_add_button (GEDIT_MESSAGE_AREA (infobar),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+#else
+ GtkWidget *button;
+
+ infobar = gtk_info_bar_new ();
+
+ button = gedit_gtk_button_new_with_stock_icon (_("_Download"),
+ GTK_STOCK_SAVE);
+ gtk_widget_show (button);
+
+ gtk_info_bar_add_action_widget (GTK_INFO_BAR (infobar),
+ button,
+ GTK_RESPONSE_YES);
+
+ button = gedit_gtk_button_new_with_stock_icon (_("_Ignore Version"),
+ GTK_STOCK_DISCARD);
+ gtk_widget_show (button);
+
+ gtk_info_bar_add_action_widget (GTK_INFO_BAR (infobar),
+ button,
+ GTK_RESPONSE_NO);
+
+ gtk_info_bar_add_button (GTK_INFO_BAR (infobar),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (infobar),
+ GTK_MESSAGE_INFO);
+#endif
+
+ message = g_strdup_printf ("%s (%s)", _("There is a new version of gedit"), version);
+ set_message_area_text_and_icon (infobar,
+ "gtk-dialog-info",
+ message,
+ _("You can download the new version of gedit"
+ " by clicking on the download button or"
+ " ignore that version and wait for a new one"));
+
+ g_free (message);
+
+ g_signal_connect (infobar, "response",
+ G_CALLBACK (on_response_cb),
+ window);
+
+ return infobar;
+}
+
+static void
+pack_infobar (GtkWidget *window,
+ GtkWidget *infobar)
+{
+ GtkWidget *vbox;
+
+ vbox = gtk_bin_get_child (GTK_BIN (window));
+
+ gtk_box_pack_start (GTK_BOX (vbox), infobar, FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (vbox), infobar, 2);
+}
+
+static gchar *
+get_file (const gchar *text,
+ const gchar *regex_place)
+{
+ GRegex *regex;
+ GMatchInfo *match_info;
+ gchar *word = NULL;
+
+ regex = g_regex_new (regex_place, 0, 0, NULL);
+ g_regex_match (regex, text, 0, &match_info);
+ while (g_match_info_matches (match_info))
+ {
+ g_free (word);
+
+ word = g_match_info_fetch (match_info, 0);
+
+ g_match_info_next (match_info, NULL);
+ }
+ g_match_info_free (match_info);
+ g_regex_unref (regex);
+
+ return word;
+}
+
+static void
+get_numbers (const gchar *version,
+ gint *major,
+ gint *minor,
+ gint *micro)
+{
+ gchar **split;
+ gint num = 2;
+
+ if (micro != NULL)
+ num = 3;
+
+ split = g_strsplit (version, ".", num);
+ *major = atoi (split[0]);
+ *minor = atoi (split[1]);
+ if (micro != NULL)
+ *micro = atoi (split[2]);
+
+ g_strfreev (split);
+}
+
+static gboolean
+newer_version (const gchar *v1,
+ const gchar *v2,
+ gboolean with_micro)
+{
+ gboolean newer = FALSE;
+ gint major1, minor1, micro1;
+ gint major2, minor2, micro2;
+
+ if (v1 == NULL || v2 == NULL)
+ return FALSE;
+
+ if (with_micro)
+ {
+ get_numbers (v1, &major1, &minor1, &micro1);
+ get_numbers (v2, &major2, &minor2, &micro2);
+ }
+ else
+ {
+ get_numbers (v1, &major1, &minor1, NULL);
+ get_numbers (v2, &major2, &minor2, NULL);
+ }
+
+ if (major1 > major2)
+ {
+ newer = TRUE;
+ }
+ else if (minor1 > minor2 && major1 == major2)
+ {
+ newer = TRUE;
+ }
+ else if (with_micro && micro1 > micro2 && minor1 == minor2)
+ {
+ newer = TRUE;
+ }
+
+ return newer;
+}
+
+static gchar *
+parse_file_version (const gchar *file)
+{
+ gchar *p, *aux;
+
+ p = (gchar *)file;
+
+ while (*p != '\0' && !g_ascii_isdigit (*p))
+ {
+ p++;
+ }
+
+ if (*p == '\0')
+ return NULL;
+
+ aux = g_strrstr (p, "-");
+ if (aux == NULL)
+ aux = g_strrstr (p, ".");
+
+ return g_strndup (p, aux - p);
+}
+
+static gchar *
+get_ignore_version (GeditCheckUpdatePlugin *plugin)
+{
+ return mateconf_client_get_string (plugin->priv->mateconf_client,
+ MATECONF_KEY_IGNORE_VERSION,
+ NULL);
+}
+
+static void
+parse_page_file (SoupSession *session,
+ SoupMessage *msg,
+ GeditWindow *window)
+{
+ if (msg->status_code == SOUP_STATUS_OK)
+ {
+ gchar *file;
+ gchar *file_version;
+ gchar *ignore_version;
+ WindowData *data;
+
+ data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+
+ file = get_file (msg->response_body->data, FILE_REGEX);
+ file_version = parse_file_version (file);
+ ignore_version = get_ignore_version (data->plugin);
+
+ if (newer_version (file_version, VERSION, TRUE) &&
+ (ignore_version == NULL || *ignore_version == '\0' ||
+ newer_version (file_version, ignore_version, TRUE)))
+ {
+ GtkWidget *infobar;
+ WindowData *data;
+ gchar *file_url;
+
+ data = g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+
+ file_url = g_strconcat (data->url, file, NULL);
+
+ g_free (data->url);
+ data->url = file_url;
+ data->version = g_strdup (file_version);
+
+ infobar = create_infobar (window, file_version);
+ pack_infobar (GTK_WIDGET (window), infobar);
+ gtk_widget_show (infobar);
+ }
+
+ g_free (ignore_version);
+ g_free (file_version);
+ g_free (file);
+ }
+ else
+ {
+ g_object_set_data (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ NULL);
+ }
+}
+
+static gboolean
+is_unstable (const gchar *version)
+{
+ gchar **split;
+ gint minor;
+ gboolean unstable = TRUE;;
+
+ split = g_strsplit (version, ".", 2);
+ minor = atoi (split[1]);
+ g_strfreev (split);
+
+ if ((minor % 2) == 0)
+ unstable = FALSE;
+
+ return unstable;
+}
+
+static gchar *
+get_file_page_version (const gchar *text,
+ const gchar *regex_place)
+{
+ GRegex *regex;
+ GMatchInfo *match_info;
+ GString *string = NULL;
+ gchar *unstable = NULL;
+ gchar *stable = NULL;
+
+ regex = g_regex_new (regex_place, 0, 0, NULL);
+ g_regex_match (regex, text, 0, &match_info);
+ while (g_match_info_matches (match_info))
+ {
+ gint end;
+ gint i;
+
+ g_match_info_fetch_pos (match_info, 0, NULL, &end);
+
+ string = g_string_new ("");
+
+ i = end;
+ while (text[i] != '/')
+ {
+ string = g_string_append_c (string, text[i]);
+ i++;
+ }
+
+ if (is_unstable (string->str))
+ {
+ g_free (unstable);
+ unstable = g_string_free (string, FALSE);
+ }
+ else
+ {
+ g_free (stable);
+ stable = g_string_free (string, FALSE);
+ }
+
+ g_match_info_next (match_info, NULL);
+ }
+ g_match_info_free (match_info);
+ g_regex_unref (regex);
+
+ if ((GEDIT_MINOR_VERSION % 2) == 0)
+ {
+ g_free (unstable);
+
+ return stable;
+ }
+ else
+ {
+ /* We need to check that stable isn't newer than unstable */
+ if (newer_version (stable, unstable, FALSE))
+ {
+ g_free (unstable);
+
+ return stable;
+ }
+ else
+ {
+ g_free (stable);
+
+ return unstable;
+ }
+ }
+}
+
+static void
+parse_page_version (SoupSession *session,
+ SoupMessage *msg,
+ GeditWindow *window)
+{
+ if (msg->status_code == SOUP_STATUS_OK)
+ {
+ gchar *version;
+ SoupMessage *msg2;
+ WindowData *data;
+
+ data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+
+ version = get_file_page_version (msg->response_body->data,
+ VERSION_PLACE);
+
+ data->url = g_strconcat (GEDIT_URL, version, "/", NULL);
+ g_free (version);
+ msg2 = soup_message_new ("GET", data->url);
+
+ soup_session_queue_message (session, msg2,
+ (SoupSessionCallback)parse_page_file,
+ window);
+ }
+ else
+ {
+ g_object_set_data (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ NULL);
+ }
+}
+
+static void
+impl_activate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ SoupMessage *msg;
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = g_slice_new (WindowData);
+ data->plugin = GEDIT_CHECK_UPDATE_PLUGIN (plugin);
+ data->url = NULL;
+ data->version = NULL;
+
+ g_object_set_data_full (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ data,
+ free_window_data);
+
+ msg = soup_message_new ("GET", GEDIT_URL);
+
+ soup_session_queue_message (GEDIT_CHECK_UPDATE_PLUGIN (plugin)->priv->session, msg,
+ (SoupSessionCallback)parse_page_version,
+ window);
+}
+
+static void
+impl_deactivate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ soup_session_abort (GEDIT_CHECK_UPDATE_PLUGIN (plugin)->priv->session);
+
+ g_object_set_data (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ NULL);
+}
+
+static void
+gedit_check_update_plugin_class_init (GeditCheckUpdatePluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (GeditCheckUpdatePluginPrivate));
+
+ object_class->finalize = gedit_check_update_plugin_finalize;
+ object_class->dispose = gedit_check_update_plugin_dispose;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+}
diff --git a/plugins/checkupdate/gedit-check-update-plugin.h b/plugins/checkupdate/gedit-check-update-plugin.h
new file mode 100755
index 00000000..68dc7f97
--- /dev/null
+++ b/plugins/checkupdate/gedit-check-update-plugin.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 - Ignacio Casal Quinteiro <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_CHECK_UPDATE_PLUGIN_H__
+#define __GEDIT_CHECK_UPDATE_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_CHECK_UPDATE_PLUGIN (gedit_check_update_plugin_get_type ())
+#define GEDIT_CHECK_UPDATE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePlugin))
+#define GEDIT_CHECK_UPDATE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePluginClass))
+#define IS_GEDIT_CHECK_UPDATE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN))
+#define IS_GEDIT_CHECK_UPDATE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_CHECK_UPDATE_PLUGIN))
+#define GEDIT_CHECK_UPDATE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_CHECK_UPDATE_PLUGIN, GeditCheckUpdatePluginClass))
+
+/* Private structure type */
+typedef struct _GeditCheckUpdatePluginPrivate GeditCheckUpdatePluginPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditCheckUpdatePlugin GeditCheckUpdatePlugin;
+
+struct _GeditCheckUpdatePlugin
+{
+ GeditPlugin parent_instance;
+
+ /*< private >*/
+ GeditCheckUpdatePluginPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditCheckUpdatePluginClass GeditCheckUpdatePluginClass;
+
+struct _GeditCheckUpdatePluginClass
+{
+ GeditPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_check_update_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_CHECK_UPDATE_PLUGIN_H__ */
diff --git a/plugins/checkupdate/gedit-check-update.schemas.in b/plugins/checkupdate/gedit-check-update.schemas.in
new file mode 100755
index 00000000..67bc892b
--- /dev/null
+++ b/plugins/checkupdate/gedit-check-update.schemas.in
@@ -0,0 +1,13 @@
+<mateconfschemafile>
+ <schemalist>
+ <schema>
+ <key>/schemas/apps/gedit-2/plugins/checkupdate/ignore_version</key>
+ <applyto>/apps/gedit-2/plugins/checkupdate/ignore_version</applyto>
+ <owner>gedit</owner>
+ <type>string</type>
+ <locale name="C">
+ <short>Version to ignore until the next version is released</short>
+ </locale>
+ </schema>
+ </schemalist>
+</mateconfschemafile>
diff --git a/plugins/docinfo/Makefile.am b/plugins/docinfo/Makefile.am
new file mode 100755
index 00000000..edf2909c
--- /dev/null
+++ b/plugins/docinfo/Makefile.am
@@ -0,0 +1,34 @@
+# docinfo plugin
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+plugin_LTLIBRARIES = libdocinfo.la
+
+libdocinfo_la_SOURCES = \
+ gedit-docinfo-plugin.h \
+ gedit-docinfo-plugin.c
+
+libdocinfo_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libdocinfo_la_LIBADD = $(GEDIT_LIBS)
+
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/docinfo
+ui_DATA = docinfo.ui
+
+plugin_in_files = docinfo.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(ui_DATA) $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/docinfo/docinfo.gedit-plugin.desktop.in b/plugins/docinfo/docinfo.gedit-plugin.desktop.in
new file mode 100755
index 00000000..77f2793d
--- /dev/null
+++ b/plugins/docinfo/docinfo.gedit-plugin.desktop.in
@@ -0,0 +1,7 @@
+[Gedit Plugin]
+Module=docinfo
+IAge=2
+_Name=Document Statistics
+_Description=Analyzes the current document and reports the number of words, lines, characters and non-space characters in it.
+Authors=Paolo Maggi <[email protected]>;Jorge Alberto Torres <[email protected]>
+Copyright=Copyright © 2002-2005 Paolo Maggi
diff --git a/plugins/docinfo/docinfo.ui b/plugins/docinfo/docinfo.ui
new file mode 100755
index 00000000..bb73d40f
--- /dev/null
+++ b/plugins/docinfo/docinfo.ui
@@ -0,0 +1,621 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Document Statistics</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="update_button">
+ <property name="label" translatable="yes">_Update</property>
+ <property name="use_underline">True</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="image">update_image</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="docinfo_dialog_content">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="file_name_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">File Name</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <attributes>
+ <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="label28">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="n_rows">6</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">18</property>
+ <child>
+ <object class="GtkLabel" id="label26">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Bytes</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="bytes_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Characters (no spaces)</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="chars_ns_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Characters (with spaces)</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="chars_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="words_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Words</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Lines</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="lines_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label30">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Document</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="selection_vbox">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="selection_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Selection</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_lines_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_words_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_chars_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_chars_ns_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="selected_bytes_label">
+ <property name="visible">True</property>
+ <property name="label">0</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-7">close_button</action-widget>
+ <action-widget response="-5">update_button</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkImage" id="update_image">
+ <property name="visible">True</property>
+ <property name="stock">gtk-refresh</property>
+ </object>
+</interface>
diff --git a/plugins/docinfo/gedit-docinfo-plugin.c b/plugins/docinfo/gedit-docinfo-plugin.c
new file mode 100755
index 00000000..a143a5a6
--- /dev/null
+++ b/plugins/docinfo/gedit-docinfo-plugin.c
@@ -0,0 +1,580 @@
+/*
+ * gedit-docinfo-plugin.c
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gedit-docinfo-plugin.h"
+
+#include <string.h> /* For strlen (...) */
+
+#include <glib/gi18n-lib.h>
+#include <pango/pango-break.h>
+#include <gmodule.h>
+
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-utils.h>
+
+#define WINDOW_DATA_KEY "GeditDocInfoWindowData"
+#define MENU_PATH "/MenuBar/ToolsMenu/ToolsOps_2"
+
+GEDIT_PLUGIN_REGISTER_TYPE(GeditDocInfoPlugin, gedit_docinfo_plugin)
+
+typedef struct
+{
+ GtkWidget *dialog;
+ GtkWidget *file_name_label;
+ GtkWidget *lines_label;
+ GtkWidget *words_label;
+ GtkWidget *chars_label;
+ GtkWidget *chars_ns_label;
+ GtkWidget *bytes_label;
+ GtkWidget *selection_vbox;
+ GtkWidget *selected_lines_label;
+ GtkWidget *selected_words_label;
+ GtkWidget *selected_chars_label;
+ GtkWidget *selected_chars_ns_label;
+ GtkWidget *selected_bytes_label;
+} DocInfoDialog;
+
+typedef struct
+{
+ GeditPlugin *plugin;
+
+ GtkActionGroup *ui_action_group;
+ guint ui_id;
+
+ DocInfoDialog *dialog;
+} WindowData;
+
+static void docinfo_dialog_response_cb (GtkDialog *widget,
+ gint res_id,
+ GeditWindow *window);
+
+static void
+docinfo_dialog_destroy_cb (GtkObject *obj,
+ WindowData *data)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ if (data != NULL)
+ {
+ g_free (data->dialog);
+ data->dialog = NULL;
+ }
+}
+
+static DocInfoDialog *
+get_docinfo_dialog (GeditWindow *window,
+ WindowData *data)
+{
+ DocInfoDialog *dialog;
+ gchar *data_dir;
+ gchar *ui_file;
+ GtkWidget *content;
+ GtkWidget *error_widget;
+ gboolean ret;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ dialog = g_new (DocInfoDialog, 1);
+
+ data_dir = gedit_plugin_get_data_dir (data->plugin);
+ ui_file = g_build_filename (data_dir, "docinfo.ui", NULL);
+ ret = gedit_utils_get_ui_objects (ui_file,
+ NULL,
+ &error_widget,
+ "dialog", &dialog->dialog,
+ "docinfo_dialog_content", &content,
+ "file_name_label", &dialog->file_name_label,
+ "words_label", &dialog->words_label,
+ "bytes_label", &dialog->bytes_label,
+ "lines_label", &dialog->lines_label,
+ "chars_label", &dialog->chars_label,
+ "chars_ns_label", &dialog->chars_ns_label,
+ "selection_vbox", &dialog->selection_vbox,
+ "selected_words_label", &dialog->selected_words_label,
+ "selected_bytes_label", &dialog->selected_bytes_label,
+ "selected_lines_label", &dialog->selected_lines_label,
+ "selected_chars_label", &dialog->selected_chars_label,
+ "selected_chars_ns_label", &dialog->selected_chars_ns_label,
+ NULL);
+
+ g_free (data_dir);
+ g_free (ui_file);
+
+ if (!ret)
+ {
+ const gchar *err_message;
+
+ err_message = gtk_label_get_label (GTK_LABEL (error_widget));
+ gedit_warning (GTK_WINDOW (window), "%s", err_message);
+
+ g_free (dialog);
+ gtk_widget_destroy (error_widget);
+
+ return NULL;
+ }
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog),
+ GTK_RESPONSE_OK);
+ gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog),
+ GTK_WINDOW (window));
+
+ g_signal_connect (dialog->dialog,
+ "destroy",
+ G_CALLBACK (docinfo_dialog_destroy_cb),
+ data);
+ g_signal_connect (dialog->dialog,
+ "response",
+ G_CALLBACK (docinfo_dialog_response_cb),
+ window);
+
+ return dialog;
+}
+
+static void
+calculate_info (GeditDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ gint *chars,
+ gint *words,
+ gint *white_chars,
+ gint *bytes)
+{
+ gchar *text;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ text = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc),
+ start,
+ end,
+ TRUE);
+
+ *chars = g_utf8_strlen (text, -1);
+ *bytes = strlen (text);
+
+ if (*chars > 0)
+ {
+ PangoLogAttr *attrs;
+ gint i;
+
+ attrs = g_new0 (PangoLogAttr, *chars + 1);
+
+ pango_get_log_attrs (text,
+ -1,
+ 0,
+ pango_language_from_string ("C"),
+ attrs,
+ *chars + 1);
+
+ for (i = 0; i < (*chars); i++)
+ {
+ if (attrs[i].is_white)
+ ++(*white_chars);
+
+ if (attrs[i].is_word_start)
+ ++(*words);
+ }
+
+ g_free (attrs);
+ }
+ else
+ {
+ *white_chars = 0;
+ *words = 0;
+ }
+
+ g_free (text);
+}
+
+static void
+docinfo_real (GeditDocument *doc,
+ DocInfoDialog *dialog)
+{
+ GtkTextIter start, end;
+ gint words = 0;
+ gint chars = 0;
+ gint white_chars = 0;
+ gint lines = 0;
+ gint bytes = 0;
+ gchar *tmp_str;
+ gchar *doc_name;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc),
+ &start,
+ &end);
+
+ lines = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (doc));
+
+ calculate_info (doc,
+ &start, &end,
+ &chars, &words, &white_chars, &bytes);
+
+ if (chars == 0)
+ lines = 0;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Chars: %d", chars);
+ gedit_debug_message (DEBUG_PLUGINS, "Lines: %d", lines);
+ gedit_debug_message (DEBUG_PLUGINS, "Words: %d", words);
+ gedit_debug_message (DEBUG_PLUGINS, "Chars non-space: %d", chars - white_chars);
+ gedit_debug_message (DEBUG_PLUGINS, "Bytes: %d", bytes);
+
+ doc_name = gedit_document_get_short_name_for_display (doc);
+ tmp_str = g_strdup_printf ("<span weight=\"bold\">%s</span>", doc_name);
+ gtk_label_set_markup (GTK_LABEL (dialog->file_name_label), tmp_str);
+ g_free (doc_name);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", lines);
+ gtk_label_set_text (GTK_LABEL (dialog->lines_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", words);
+ gtk_label_set_text (GTK_LABEL (dialog->words_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", chars);
+ gtk_label_set_text (GTK_LABEL (dialog->chars_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", chars - white_chars);
+ gtk_label_set_text (GTK_LABEL (dialog->chars_ns_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", bytes);
+ gtk_label_set_text (GTK_LABEL (dialog->bytes_label), tmp_str);
+ g_free (tmp_str);
+}
+
+static void
+selectioninfo_real (GeditDocument *doc,
+ DocInfoDialog *dialog)
+{
+ gboolean sel;
+ GtkTextIter start, end;
+ gint words = 0;
+ gint chars = 0;
+ gint white_chars = 0;
+ gint lines = 0;
+ gint bytes = 0;
+ gchar *tmp_str;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ sel = gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &start,
+ &end);
+
+ if (sel)
+ {
+ lines = gtk_text_iter_get_line (&end) - gtk_text_iter_get_line (&start) + 1;
+
+ calculate_info (doc,
+ &start, &end,
+ &chars, &words, &white_chars, &bytes);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Selected chars: %d", chars);
+ gedit_debug_message (DEBUG_PLUGINS, "Selected lines: %d", lines);
+ gedit_debug_message (DEBUG_PLUGINS, "Selected words: %d", words);
+ gedit_debug_message (DEBUG_PLUGINS, "Selected chars non-space: %d", chars - white_chars);
+ gedit_debug_message (DEBUG_PLUGINS, "Selected bytes: %d", bytes);
+
+ gtk_widget_set_sensitive (dialog->selection_vbox, TRUE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dialog->selection_vbox, FALSE);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Selection empty");
+ }
+
+ if (chars == 0)
+ lines = 0;
+
+ tmp_str = g_strdup_printf("%d", lines);
+ gtk_label_set_text (GTK_LABEL (dialog->selected_lines_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", words);
+ gtk_label_set_text (GTK_LABEL (dialog->selected_words_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", chars);
+ gtk_label_set_text (GTK_LABEL (dialog->selected_chars_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", chars - white_chars);
+ gtk_label_set_text (GTK_LABEL (dialog->selected_chars_ns_label), tmp_str);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf("%d", bytes);
+ gtk_label_set_text (GTK_LABEL (dialog->selected_bytes_label), tmp_str);
+ g_free (tmp_str);
+}
+
+static void
+docinfo_cb (GtkAction *action,
+ GeditWindow *window)
+{
+ GeditDocument *doc;
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+
+ doc = gedit_window_get_active_document (window);
+ g_return_if_fail (doc != NULL);
+
+ if (data->dialog != NULL)
+ {
+ gtk_window_present (GTK_WINDOW (data->dialog->dialog));
+ gtk_widget_grab_focus (GTK_WIDGET (data->dialog->dialog));
+ }
+ else
+ {
+ DocInfoDialog *dialog;
+
+ dialog = get_docinfo_dialog (window, data);
+ g_return_if_fail (dialog != NULL);
+
+ data->dialog = dialog;
+
+ gtk_widget_show (GTK_WIDGET (dialog->dialog));
+ }
+
+ docinfo_real (doc,
+ data->dialog);
+ selectioninfo_real (doc,
+ data->dialog);
+}
+
+static void
+docinfo_dialog_response_cb (GtkDialog *widget,
+ gint res_id,
+ GeditWindow *window)
+{
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+
+ switch (res_id)
+ {
+ case GTK_RESPONSE_CLOSE:
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CLOSE");
+ gtk_widget_destroy (data->dialog->dialog);
+
+ break;
+ }
+
+ case GTK_RESPONSE_OK:
+ {
+ GeditDocument *doc;
+
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK");
+
+ doc = gedit_window_get_active_document (window);
+ g_return_if_fail (doc != NULL);
+
+ docinfo_real (doc,
+ data->dialog);
+
+ selectioninfo_real (doc,
+ data->dialog);
+
+ break;
+ }
+ }
+}
+
+static const GtkActionEntry action_entries[] =
+{
+ { "DocumentStatistics",
+ NULL,
+ N_("_Document Statistics"),
+ NULL,
+ N_("Get statistical information on the current document"),
+ G_CALLBACK (docinfo_cb) }
+};
+
+static void
+free_window_data (WindowData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_object_unref (data->plugin);
+
+ g_object_unref (data->ui_action_group);
+
+ if (data->dialog != NULL)
+ {
+ gtk_widget_destroy (data->dialog->dialog);
+ }
+
+ g_free (data);
+}
+
+static void
+update_ui_real (GeditWindow *window,
+ WindowData *data)
+{
+ GeditView *view;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ view = gedit_window_get_active_view (window);
+
+ gtk_action_group_set_sensitive (data->ui_action_group,
+ (view != NULL));
+
+ if (data->dialog != NULL)
+ {
+ gtk_dialog_set_response_sensitive (GTK_DIALOG (data->dialog->dialog),
+ GTK_RESPONSE_OK,
+ (view != NULL));
+ }
+}
+
+static void
+gedit_docinfo_plugin_init (GeditDocInfoPlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditDocInfoPlugin initializing");
+}
+
+static void
+gedit_docinfo_plugin_finalize (GObject *object)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditDocInfoPlugin finalizing");
+
+ G_OBJECT_CLASS (gedit_docinfo_plugin_parent_class)->finalize (object);
+}
+
+static void
+impl_activate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = g_new (WindowData, 1);
+
+ data->plugin = g_object_ref (plugin);
+ data->dialog = NULL;
+ data->ui_action_group = gtk_action_group_new ("GeditDocInfoPluginActions");
+
+ gtk_action_group_set_translation_domain (data->ui_action_group,
+ GETTEXT_PACKAGE);
+ gtk_action_group_add_actions (data->ui_action_group,
+ action_entries,
+ G_N_ELEMENTS (action_entries),
+ window);
+
+ manager = gedit_window_get_ui_manager (window);
+ gtk_ui_manager_insert_action_group (manager,
+ data->ui_action_group,
+ -1);
+
+ data->ui_id = gtk_ui_manager_new_merge_id (manager);
+
+ g_object_set_data_full (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ data,
+ (GDestroyNotify) free_window_data);
+
+ gtk_ui_manager_add_ui (manager,
+ data->ui_id,
+ MENU_PATH,
+ "DocumentStatistics",
+ "DocumentStatistics",
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ update_ui_real (window,
+ data);
+}
+
+static void
+impl_deactivate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ gtk_ui_manager_remove_ui (manager,
+ data->ui_id);
+ gtk_ui_manager_remove_action_group (manager,
+ data->ui_action_group);
+
+ g_object_set_data (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ NULL);
+}
+
+static void
+impl_update_ui (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ update_ui_real (window,
+ data);
+}
+
+static void
+gedit_docinfo_plugin_class_init (GeditDocInfoPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ object_class->finalize = gedit_docinfo_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+ plugin_class->update_ui = impl_update_ui;
+}
diff --git a/plugins/docinfo/gedit-docinfo-plugin.h b/plugins/docinfo/gedit-docinfo-plugin.h
new file mode 100755
index 00000000..36d6bddc
--- /dev/null
+++ b/plugins/docinfo/gedit-docinfo-plugin.h
@@ -0,0 +1,75 @@
+/*
+ * gedit-docinfo-plugin.h
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifndef __GEDIT_DOCINFO_PLUGIN_H__
+#define __GEDIT_DOCINFO_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_DOCINFO_PLUGIN (gedit_docinfo_plugin_get_type ())
+#define GEDIT_DOCINFO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPlugin))
+#define GEDIT_DOCINFO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPluginClass))
+#define GEDIT_IS_DOCINFO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_DOCINFO_PLUGIN))
+#define GEDIT_IS_DOCINFO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_DOCINFO_PLUGIN))
+#define GEDIT_DOCINFO_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_DOCINFO_PLUGIN, GeditDocInfoPluginClass))
+
+/* Private structure type */
+typedef struct _GeditDocInfoPluginPrivate GeditDocInfoPluginPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditDocInfoPlugin GeditDocInfoPlugin;
+
+struct _GeditDocInfoPlugin
+{
+ GeditPlugin parent_instance;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditDocInfoPluginClass GeditDocInfoPluginClass;
+
+struct _GeditDocInfoPluginClass
+{
+ GeditPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_docinfo_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_DOCINFO_PLUGIN_H__ */
diff --git a/plugins/externaltools/Makefile.am b/plugins/externaltools/Makefile.am
new file mode 100755
index 00000000..f529640c
--- /dev/null
+++ b/plugins/externaltools/Makefile.am
@@ -0,0 +1,15 @@
+# External Tools plugin
+SUBDIRS = tools data scripts
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+plugin_in_files = externaltools.gedit-plugin.desktop.in
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/externaltools/data/Makefile.am b/plugins/externaltools/data/Makefile.am
new file mode 100755
index 00000000..ae3a1c66
--- /dev/null
+++ b/plugins/externaltools/data/Makefile.am
@@ -0,0 +1,65 @@
+TOOL_MERGE=$(top_srcdir)/plugins/externaltools/scripts/gedit-tool-merge.pl
+
+tools_in_files = \
+ build.tool.in \
+ remove-trailing-spaces.tool.in
+
+tools_in_linux = \
+ open-terminal-here.tool.in \
+ run-command.tool.in
+
+tools_in_osx = \
+ open-terminal-here-osx.tool.in
+
+tools_in_win32 =
+
+install_tools_in_files = $(tools_in_files)
+
+if PLATFORM_OSX
+install_tools_in_files += $(tools_in_osx)
+else
+if PLATFORM_WIN32
+install_tools_in_files += $(tools_in_win32)
+else
+install_tools_in_files += $(tools_in_linux)
+endif
+endif
+
+desktop_in_files = $(install_tools_in_files:.tool.in=.desktop.in)
+desktop_files = $(install_tools_in_files:.tool.in=.desktop)
+
+tools_SCRIPTS = $(install_tools_in_files:.tool.in=)
+toolsdir = $(GEDIT_PLUGINS_DATA_DIR)/externaltools/tools
+
+all_tools_in_files = \
+ $(tools_in_files) \
+ $(tools_in_linux) \
+ $(tools_in_osx) \
+ $(tools_in_win32)
+
+all_desktop_in_files = $(all_tools_in_files:.tool.in=.desktop.in)
+all_desktop_files = $(all_tools_in_files:.tool.in=.desktop)
+all_tools_files = $(all_tools_in_files:.tool.in=)
+
+@INTLTOOL_DESKTOP_RULE@
+
+# Tools are generated by merging a script file (.tool.in) with a data file
+# (.desktop), which happens to be translated using intltool.
+$(tools_SCRIPTS): %: %.tool.in %.desktop $(TOOL_MERGE)
+ perl $(TOOL_MERGE) -o $@ $< $(word 2,$^)
+ chmod 755 $@
+
+EXTRA_DIST = \
+ $(all_desktop_in_files) \
+ $(all_tools_in_files)
+
+CLEANFILES = \
+ $(all_desktop_files) \
+ $(all_tools_files)
+
+DISTCLEANFILES = \
+ $(all_desktop_files) \
+ $(all_tools_files)
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/externaltools/data/build.desktop.in b/plugins/externaltools/data/build.desktop.in
new file mode 100755
index 00000000..13767ee7
--- /dev/null
+++ b/plugins/externaltools/data/build.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Tool]
+_Name=Build
+_Comment=Run "make" in the document directory
+Input=nothing
+Output=output-panel
+Shortcut=<Control>F8
+Applicability=local
+Save-files=nothing
+Languages=
diff --git a/plugins/externaltools/data/build.tool.in b/plugins/externaltools/data/build.tool.in
new file mode 100755
index 00000000..0b81d5b6
--- /dev/null
+++ b/plugins/externaltools/data/build.tool.in
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+EHOME=`echo $HOME | sed "s/#/\#/"`
+DIR=$GEDIT_CURRENT_DOCUMENT_DIR
+while test "$DIR" != "/"; do
+ for m in GNUmakefile makefile Makefile; do
+ if [ -f "${DIR}/${m}" ]; then
+ echo "Using ${m} from ${DIR}" | sed "s#$EHOME#~#" > /dev/stderr
+ make -C "${DIR}"
+ exit
+ fi
+ done
+ DIR=`dirname "${DIR}"`
+done
+echo "No Makefile found!" > /dev/stderr
diff --git a/plugins/externaltools/data/open-terminal-here-osx.desktop.in b/plugins/externaltools/data/open-terminal-here-osx.desktop.in
new file mode 100755
index 00000000..801b003c
--- /dev/null
+++ b/plugins/externaltools/data/open-terminal-here-osx.desktop.in
@@ -0,0 +1,8 @@
+[Gedit Tool]
+_Name=Open terminal here
+_Comment=Open a terminal in the document location
+Input=nothing
+Output=output-panel
+Applicability=local
+Save-files=nothing
+Languages=
diff --git a/plugins/externaltools/data/open-terminal-here-osx.tool.in b/plugins/externaltools/data/open-terminal-here-osx.tool.in
new file mode 100755
index 00000000..c3360064
--- /dev/null
+++ b/plugins/externaltools/data/open-terminal-here-osx.tool.in
@@ -0,0 +1,16 @@
+#!/usr/bin/osascript
+
+set the_path to system attribute "GEDIT_CURRENT_DOCUMENT_DIR"
+set cmd to "cd " & quoted form of the_path
+
+tell application "System Events" to set terminalIsRunning to exists application process "Terminal"
+
+tell application "Terminal"
+ activate
+
+ if terminalIsRunning is true then
+ do script with command cmd
+ else
+ do script with command cmd in window 1
+ end if
+end tell
diff --git a/plugins/externaltools/data/open-terminal-here.desktop.in b/plugins/externaltools/data/open-terminal-here.desktop.in
new file mode 100755
index 00000000..801b003c
--- /dev/null
+++ b/plugins/externaltools/data/open-terminal-here.desktop.in
@@ -0,0 +1,8 @@
+[Gedit Tool]
+_Name=Open terminal here
+_Comment=Open a terminal in the document location
+Input=nothing
+Output=output-panel
+Applicability=local
+Save-files=nothing
+Languages=
diff --git a/plugins/externaltools/data/open-terminal-here.tool.in b/plugins/externaltools/data/open-terminal-here.tool.in
new file mode 100755
index 00000000..d2dda8db
--- /dev/null
+++ b/plugins/externaltools/data/open-terminal-here.tool.in
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#TODO: use "mateconftool-2 -g /desktop/mate/applications/terminal/exec"
+mate-terminal --working-directory=$GEDIT_CURRENT_DOCUMENT_DIR &
diff --git a/plugins/externaltools/data/remove-trailing-spaces.desktop.in b/plugins/externaltools/data/remove-trailing-spaces.desktop.in
new file mode 100755
index 00000000..99b8b703
--- /dev/null
+++ b/plugins/externaltools/data/remove-trailing-spaces.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Tool]
+_Name=Remove trailing spaces
+_Comment=Remove useless trailing spaces in your file
+Input=document
+Output=replace-document
+Shortcut=<Alt>F12
+Applicability=all
+Save-files=nothing
+Languages=
diff --git a/plugins/externaltools/data/remove-trailing-spaces.tool.in b/plugins/externaltools/data/remove-trailing-spaces.tool.in
new file mode 100755
index 00000000..83e4c19b
--- /dev/null
+++ b/plugins/externaltools/data/remove-trailing-spaces.tool.in
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+sed 's/[[:blank:]]*$//'
diff --git a/plugins/externaltools/data/run-command.desktop.in b/plugins/externaltools/data/run-command.desktop.in
new file mode 100755
index 00000000..b58294b0
--- /dev/null
+++ b/plugins/externaltools/data/run-command.desktop.in
@@ -0,0 +1,8 @@
+[Gedit Tool]
+_Name=Run command
+_Comment=Execute a custom command and put its output in a new document
+Input=nothing
+Output=new-document
+Applicability=all
+Save-files=nothing
+Languages=
diff --git a/plugins/externaltools/data/run-command.tool.in b/plugins/externaltools/data/run-command.tool.in
new file mode 100755
index 00000000..ee611bbb
--- /dev/null
+++ b/plugins/externaltools/data/run-command.tool.in
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+#TODO: use "mateconftool-2 -g /desktop/mate/applications/terminal/exec"
+exec `matedialog --entry --title="Run command - gedit" --text="Command to run"`
diff --git a/plugins/externaltools/externaltools.gedit-plugin.desktop.in b/plugins/externaltools/externaltools.gedit-plugin.desktop.in
new file mode 100755
index 00000000..5212c49b
--- /dev/null
+++ b/plugins/externaltools/externaltools.gedit-plugin.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Plugin]
+Loader=python
+Module=externaltools
+IAge=2
+_Name=External Tools
+_Description=Execute external commands and shell scripts.
+Authors=Steve Frécinaux <[email protected]>
+Copyright=Copyright © 2005 Steve Frécinaux
+Website=http://www.gedit.org
diff --git a/plugins/externaltools/scripts/Makefile.am b/plugins/externaltools/scripts/Makefile.am
new file mode 100755
index 00000000..4ff8060b
--- /dev/null
+++ b/plugins/externaltools/scripts/Makefile.am
@@ -0,0 +1,4 @@
+EXTRA_DIST = gedit-tool-merge.pl
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/externaltools/scripts/gedit-tool-merge.pl b/plugins/externaltools/scripts/gedit-tool-merge.pl
new file mode 100755
index 00000000..780d95dd
--- /dev/null
+++ b/plugins/externaltools/scripts/gedit-tool-merge.pl
@@ -0,0 +1,78 @@
+#!/usr/bin/perl
+
+# gedit-tool-merge.pl
+# This file is part of gedit
+#
+# Copyright (C) 2006 - Steve Frécinaux <[email protected]>
+#
+# 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 <<EOF;
+Usage: ${0} [OPTION]... [SCRIPT] [DESKTOP]
+Merges a script file with a desktop file, embedding it in a comment.
+
+ -o, --output=FILE Specify the output file name. [default: stdout]
+EOF
+ exit;
+}
+
+my $output = "";
+my $help;
+
+GetOptions ("help|h" => \$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.
+$_ = <INFILE>;
+print and $_ = <INFILE> if /^#!/;
+print and $_ = <INFILE> if /-\*-/;
+print and $_ = <INFILE> if /(ex|vi|vim):/;
+
+# Put a blank line before the info block if there is one in INFILE.
+print and $_ = <INFILE> if /^\s*$/;
+seek INFILE, -length, 1;
+
+# Embed the desktop file...
+print "# $_" while <DFILE>;
+print "\n";
+
+# ...and write the remaining part of the script.
+print while <INFILE>;
+
+close INFILE;
+close DFILE;
+close STDOUT;
diff --git a/plugins/externaltools/tools/Makefile.am b/plugins/externaltools/tools/Makefile.am
new file mode 100755
index 00000000..5edcab58
--- /dev/null
+++ b/plugins/externaltools/tools/Makefile.am
@@ -0,0 +1,23 @@
+# Python snippets plugin
+
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/externaltools
+plugin_PYTHON = \
+ __init__.py \
+ capture.py \
+ library.py \
+ functions.py \
+ manager.py \
+ outputpanel.py \
+ filelookup.py \
+ linkparsing.py
+
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/externaltools/ui
+ui_DATA = tools.ui \
+ outputpanel.ui
+
+EXTRA_DIST = $(ui_DATA)
+
+CLEANFILES = *.bak *.gladep
+DISTCLEANFILES = *.bak *.gladep
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/externaltools/tools/__init__.py b/plugins/externaltools/tools/__init__.py
new file mode 100755
index 00000000..a46aef8f
--- /dev/null
+++ b/plugins/externaltools/tools/__init__.py
@@ -0,0 +1,281 @@
+# -*- coding: UTF-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__all__ = ('ExternalToolsPlugin', 'ExternalToolsWindowHelper',
+ 'Manager', 'OutputPanel', 'Capture', 'UniqueById')
+
+import gedit
+import gtk
+from manager import Manager
+from library import ToolLibrary
+from outputpanel import OutputPanel
+from capture import Capture
+from functions import *
+
+class ToolMenu(object):
+ ACTION_HANDLER_DATA_KEY = "ExternalToolActionHandlerData"
+ ACTION_ITEM_DATA_KEY = "ExternalToolActionItemData"
+
+ def __init__(self, library, window, menupath):
+ super(ToolMenu, self).__init__()
+ self._library = library
+ self._window = window
+ self._menupath = menupath
+
+ self._merge_id = 0
+ self._action_group = gtk.ActionGroup("ExternalToolsPluginToolActions")
+ self._signals = []
+
+ self.update()
+
+ def deactivate(self):
+ self.remove()
+
+ def remove(self):
+ if self._merge_id != 0:
+ self._window.get_ui_manager().remove_ui(self._merge_id)
+ self._window.get_ui_manager().remove_action_group(self._action_group)
+ self._merge_id = 0
+
+ for action in self._action_group.list_actions():
+ handler = action.get_data(self.ACTION_HANDLER_DATA_KEY)
+
+ if handler is not None:
+ action.disconnect(handler)
+
+ action.set_data(self.ACTION_ITEM_DATA_KEY, None)
+ action.set_data(self.ACTION_HANDLER_DATA_KEY, None)
+
+ self._action_group.remove_action(action)
+
+ accelmap = gtk.accel_map_get()
+
+ for s in self._signals:
+ accelmap.disconnect(s)
+
+ self._signals = []
+
+ def _insert_directory(self, directory, path):
+ manager = self._window.get_ui_manager()
+
+ for item in directory.subdirs:
+ action_name = 'ExternalToolDirectory%X' % id(item)
+ action = gtk.Action(action_name, item.name.replace('_', '__'), None, None)
+ self._action_group.add_action(action)
+
+ manager.add_ui(self._merge_id, path,
+ action_name, action_name,
+ gtk.UI_MANAGER_MENU, False)
+
+ self._insert_directory(item, path + '/' + action_name)
+
+ for item in directory.tools:
+ action_name = 'ExternalToolTool%X' % id(item)
+ action = gtk.Action(action_name, item.name.replace('_', '__'), item.comment, None)
+ handler = action.connect("activate", capture_menu_action, self._window, item)
+
+ action.set_data(self.ACTION_ITEM_DATA_KEY, item)
+ action.set_data(self.ACTION_HANDLER_DATA_KEY, handler)
+
+ # Make sure to replace accel
+ accelpath = '<Actions>/ExternalToolsPluginToolActions/%s' % (action_name, )
+
+ if item.shortcut:
+ key, mod = gtk.accelerator_parse(item.shortcut)
+ gtk.accel_map_change_entry(accelpath, key, mod, True)
+
+ self._signals.append(gtk.accel_map_get().connect('changed::%s' % (accelpath,), self.on_accelmap_changed, item))
+
+ self._action_group.add_action_with_accel(action, item.shortcut)
+
+ manager.add_ui(self._merge_id, path,
+ action_name, action_name,
+ gtk.UI_MANAGER_MENUITEM, False)
+
+ def on_accelmap_changed(self, accelmap, path, key, mod, tool):
+ tool.shortcut = gtk.accelerator_name(key, mod)
+ tool.save()
+
+ self._window.get_data("ExternalToolsPluginWindowData").update_manager(tool)
+
+ def update(self):
+ self.remove()
+ self._merge_id = self._window.get_ui_manager().new_merge_id()
+ self._insert_directory(self._library.tree, self._menupath)
+ self._window.get_ui_manager().insert_action_group(self._action_group, -1)
+ self.filter(self._window.get_active_document())
+
+ def filter_language(self, language, item):
+ if not item.languages:
+ return True
+
+ if not language and 'plain' in item.languages:
+ return True
+
+ if language and (language.get_id() in item.languages):
+ return True
+ else:
+ return False
+
+ def filter(self, document):
+ if document is None:
+ return
+
+ titled = document.get_uri() is not None
+ remote = not document.is_local()
+
+ states = {
+ 'all' : True,
+ 'local': titled and not remote,
+ 'remote': titled and remote,
+ 'titled': titled,
+ 'untitled': not titled,
+ }
+
+ language = document.get_language()
+
+ for action in self._action_group.list_actions():
+ item = action.get_data(self.ACTION_ITEM_DATA_KEY)
+
+ if item is not None:
+ action.set_visible(states[item.applicability] and self.filter_language(language, item))
+
+class ExternalToolsWindowHelper(object):
+ def __init__(self, plugin, window):
+ super(ExternalToolsWindowHelper, self).__init__()
+
+ self._window = window
+ self._plugin = plugin
+ self._library = ToolLibrary()
+
+ manager = window.get_ui_manager()
+
+ self._action_group = gtk.ActionGroup('ExternalToolsPluginActions')
+ self._action_group.set_translation_domain('gedit')
+ self._action_group.add_actions([('ExternalToolManager',
+ None,
+ _('Manage _External Tools...'),
+ None,
+ _("Opens the External Tools Manager"),
+ lambda action: plugin.open_dialog()),
+ ('ExternalTools',
+ None,
+ _('External _Tools'),
+ None,
+ _("External tools"),
+ None)])
+ manager.insert_action_group(self._action_group, -1)
+
+ ui_string = """
+ <ui>
+ <menubar name="MenuBar">
+ <menu name="ToolsMenu" action="Tools">
+ <placeholder name="ToolsOps_4">
+ <separator/>
+ <menu name="ExternalToolsMenu" action="ExternalTools">
+ <placeholder name="ExternalToolPlaceholder"/>
+ </menu>
+ <separator/>
+ </placeholder>
+ <placeholder name="ToolsOps_5">
+ <menuitem name="ExternalToolManager" action="ExternalToolManager"/>
+ </placeholder>
+ </menu>
+ </menubar>
+ </ui>"""
+
+ self._merge_id = manager.add_ui_from_string(ui_string)
+
+ self.menu = ToolMenu(self._library, self._window,
+ "/MenuBar/ToolsMenu/ToolsOps_4/ExternalToolsMenu/ExternalToolPlaceholder")
+ manager.ensure_update()
+
+ # Create output console
+ self._output_buffer = OutputPanel(self._plugin.get_data_dir(), window)
+ bottom = window.get_bottom_panel()
+ bottom.add_item(self._output_buffer.panel,
+ _("Shell Output"),
+ gtk.STOCK_EXECUTE)
+
+ def update_ui(self):
+ self.menu.filter(self._window.get_active_document())
+ self._window.get_ui_manager().ensure_update()
+
+ def deactivate(self):
+ manager = self._window.get_ui_manager()
+ self.menu.deactivate()
+ manager.remove_ui(self._merge_id)
+ manager.remove_action_group(self._action_group)
+ manager.ensure_update()
+
+ bottom = self._window.get_bottom_panel()
+ bottom.remove_item(self._output_buffer.panel)
+
+ def update_manager(self, tool):
+ self._plugin.update_manager(tool)
+
+class ExternalToolsPlugin(gedit.Plugin):
+ WINDOW_DATA_KEY = "ExternalToolsPluginWindowData"
+
+ def __init__(self):
+ super(ExternalToolsPlugin, self).__init__()
+
+ self._manager = None
+ self._manager_default_size = None
+
+ ToolLibrary().set_locations(os.path.join(self.get_data_dir(), 'tools'))
+
+ def activate(self, window):
+ helper = ExternalToolsWindowHelper(self, window)
+ window.set_data(self.WINDOW_DATA_KEY, helper)
+
+ def deactivate(self, window):
+ window.get_data(self.WINDOW_DATA_KEY).deactivate()
+ window.set_data(self.WINDOW_DATA_KEY, None)
+
+ def update_ui(self, window):
+ window.get_data(self.WINDOW_DATA_KEY).update_ui()
+
+ def create_configure_dialog(self):
+ return self.open_dialog()
+
+ def open_dialog(self):
+ if not self._manager:
+ self._manager = Manager(self.get_data_dir())
+
+ if self._manager_default_size:
+ self._manager.dialog.set_default_size(*self._manager_default_size)
+
+ self._manager.dialog.connect('destroy', self.on_manager_destroy)
+
+ window = gedit.app_get_default().get_active_window()
+ self._manager.run(window)
+
+ return self._manager.dialog
+
+ def update_manager(self, tool):
+ if not self._manager:
+ return
+
+ self._manager.tool_changed(tool, True)
+
+ def on_manager_destroy(self, dialog):
+ self._manager_default_size = [dialog.allocation.width, dialog.allocation.height]
+ self._manager = None
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/capture.py b/plugins/externaltools/tools/capture.py
new file mode 100755
index 00000000..e47862c8
--- /dev/null
+++ b/plugins/externaltools/tools/capture.py
@@ -0,0 +1,214 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__all__ = ('Capture', )
+
+import os, sys, signal
+import locale
+import subprocess
+import gobject
+import fcntl
+import glib
+
+class Capture(gobject.GObject):
+ CAPTURE_STDOUT = 0x01
+ CAPTURE_STDERR = 0x02
+ CAPTURE_BOTH = 0x03
+ CAPTURE_NEEDS_SHELL = 0x04
+
+ WRITE_BUFFER_SIZE = 0x4000
+
+ __gsignals__ = {
+ 'stdout-line' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
+ 'stderr-line' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
+ 'begin-execute': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, tuple()),
+ 'end-execute' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,))
+ }
+
+ def __init__(self, command, cwd = None, env = {}):
+ gobject.GObject.__init__(self)
+ self.pipe = None
+ self.env = env
+ self.cwd = cwd
+ self.flags = self.CAPTURE_BOTH | self.CAPTURE_NEEDS_SHELL
+ self.command = command
+ self.input_text = None
+
+ def set_env(self, **values):
+ self.env.update(**values)
+
+ def set_command(self, command):
+ self.command = command
+
+ def set_flags(self, flags):
+ self.flags = flags
+
+ def set_input(self, text):
+ self.input_text = text
+
+ def set_cwd(self, cwd):
+ self.cwd = cwd
+
+ def execute(self):
+ if self.command is None:
+ return
+
+ # Initialize pipe
+ popen_args = {
+ 'cwd' : self.cwd,
+ 'shell': self.flags & self.CAPTURE_NEEDS_SHELL,
+ 'env' : self.env
+ }
+
+ if self.input_text is not None:
+ popen_args['stdin'] = subprocess.PIPE
+ if self.flags & self.CAPTURE_STDOUT:
+ popen_args['stdout'] = subprocess.PIPE
+ if self.flags & self.CAPTURE_STDERR:
+ popen_args['stderr'] = subprocess.PIPE
+
+ self.tried_killing = False
+ self.idle_write_id = 0
+ self.read_buffer = ''
+
+ try:
+ self.pipe = subprocess.Popen(self.command, **popen_args)
+ except OSError, e:
+ self.pipe = None
+ self.emit('stderr-line', _('Could not execute command: %s') % (e, ))
+ return
+
+ # Signal
+ self.emit('begin-execute')
+
+ if self.flags & self.CAPTURE_STDOUT:
+ # Set non blocking
+ flags = fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK
+ fcntl.fcntl(self.pipe.stdout.fileno(), fcntl.F_SETFL, flags)
+
+ gobject.io_add_watch(self.pipe.stdout,
+ gobject.IO_IN | gobject.IO_HUP,
+ self.on_output)
+
+ if self.flags & self.CAPTURE_STDERR:
+ # Set non blocking
+ flags = fcntl.fcntl(self.pipe.stderr.fileno(), fcntl.F_GETFL) | os.O_NONBLOCK
+ fcntl.fcntl(self.pipe.stderr.fileno(), fcntl.F_SETFL, flags)
+
+ gobject.io_add_watch(self.pipe.stderr,
+ gobject.IO_IN | gobject.IO_HUP,
+ self.on_output)
+
+ # IO
+ if self.input_text is not None:
+ # Write async, in chunks of something
+ self.write_buffer = str(self.input_text)
+
+ if self.idle_write_chunk():
+ self.idle_write_id = gobject.idle_add(self.idle_write_chunk)
+
+ # Wait for the process to complete
+ gobject.child_watch_add(self.pipe.pid, self.on_child_end)
+
+ def idle_write_chunk(self):
+ if not self.pipe:
+ self.idle_write_id = 0
+ return False
+
+ try:
+ l = len(self.write_buffer)
+ m = min(l, self.WRITE_BUFFER_SIZE)
+
+ self.pipe.stdin.write(self.write_buffer[:m])
+
+ if m == l:
+ self.write_buffer = ''
+ self.pipe.stdin.close()
+
+ self.idle_write_id = 0
+
+ return False
+ else:
+ self.write_buffer = self.write_buffer[m:]
+ return True
+ except IOError:
+ self.pipe.stdin.close()
+ self.idle_write_id = 0
+
+ return False
+
+ def on_output(self, source, condition):
+ if condition & (glib.IO_IN | glib.IO_PRI):
+ line = source.read()
+
+ if len(line) > 0:
+ try:
+ line = unicode(line, 'utf-8')
+ except:
+ line = unicode(line,
+ locale.getdefaultlocale()[1],
+ 'replace')
+
+ self.read_buffer += line
+ lines = self.read_buffer.splitlines(True)
+
+ if not lines[-1].endswith("\n"):
+ self.read_buffer = lines[-1]
+ lines = lines[0:-1]
+ else:
+ self.read_buffer = ''
+
+ for line in lines:
+ if not self.pipe or source == self.pipe.stdout:
+ self.emit('stdout-line', line)
+ else:
+ self.emit('stderr-line', line)
+
+ if condition & ~(glib.IO_IN | glib.IO_PRI):
+ if self.read_buffer:
+ if source == self.pipe.stdout:
+ self.emit('stdout-line', self.read_buffer)
+ else:
+ self.emit('stderr-line', self.read_buffer)
+
+ self.read_buffer = ''
+
+ self.pipe = None
+
+ return False
+ else:
+ return True
+
+ def stop(self, error_code = -1):
+ if self.pipe is not None:
+ if self.idle_write_id:
+ gobject.source_remove(self.idle_write_id)
+ self.idle_write_id = 0
+
+ if not self.tried_killing:
+ os.kill(self.pipe.pid, signal.SIGTERM)
+ self.tried_killing = True
+ else:
+ os.kill(self.pipe.pid, signal.SIGKILL)
+
+ def on_child_end(self, pid, error_code):
+ # In an idle, so it is emitted after all the std*-line signals
+ # have been intercepted
+ gobject.idle_add(self.emit, 'end-execute', error_code)
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/filelookup.py b/plugins/externaltools/tools/filelookup.py
new file mode 100755
index 00000000..229823b7
--- /dev/null
+++ b/plugins/externaltools/tools/filelookup.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2010 Per Arneng <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gio
+import gedit
+
+class FileLookup:
+ """
+ This class is responsible for looking up files given a part or the whole
+ path of a real file. The lookup is delegated to providers wich use different
+ methods of trying to find the real file.
+ """
+
+ def __init__(self):
+ self.providers = []
+ self.providers.append(AbsoluteFileLookupProvider())
+ self.providers.append(CwdFileLookupProvider())
+ self.providers.append(OpenDocumentRelPathFileLookupProvider())
+ self.providers.append(OpenDocumentFileLookupProvider())
+
+ def lookup(self, path):
+ """
+ Tries to find a file specified by the path parameter. It delegates to
+ different lookup providers and the first match is returned. If no file
+ was found then None is returned.
+
+ path -- the path to find
+ """
+ found_file = None
+ for provider in self.providers:
+ found_file = provider.lookup(path)
+ if found_file is not None:
+ break
+
+ return found_file
+
+
+class FileLookupProvider:
+ """
+ The base class of all file lookup providers.
+ """
+
+ def lookup(self, path):
+ """
+ This method must be implemented by subclasses. Implementors will be
+ given a path and will try to find a matching file. If no file is found
+ then None is returned.
+ """
+ raise NotImplementedError("need to implement a lookup method")
+
+
+class AbsoluteFileLookupProvider(FileLookupProvider):
+ """
+ This file tries to see if the path given is an absolute path and that the
+ path references a file.
+ """
+
+ def lookup(self, path):
+ if os.path.isabs(path) and os.path.isfile(path):
+ return gio.File(path)
+ else:
+ return None
+
+
+class CwdFileLookupProvider(FileLookupProvider):
+ """
+ This lookup provider tries to find a file specified by the path relative to
+ the current working directory.
+ """
+
+ def lookup(self, path):
+ try:
+ cwd = os.getcwd()
+ except OSError:
+ cwd = os.getenv('HOME')
+
+ real_path = os.path.join(cwd, path)
+
+ if os.path.isfile(real_path):
+ return gio.File(real_path)
+ else:
+ return None
+
+
+class OpenDocumentRelPathFileLookupProvider(FileLookupProvider):
+ """
+ Tries to see if the path is relative to any directories where the
+ currently open documents reside in. Example: If you have a document opened
+ '/tmp/Makefile' and a lookup is made for 'src/test2.c' then this class
+ will try to find '/tmp/src/test2.c'.
+ """
+
+ def lookup(self, path):
+ if path.startswith('/'):
+ return None
+
+ for doc in gedit.app_get_default().get_documents():
+ if doc.is_local():
+ location = doc.get_location()
+ if location:
+ rel_path = location.get_parent().get_path()
+ joined_path = os.path.join(rel_path, path)
+ if os.path.isfile(joined_path):
+ return gio.File(joined_path)
+
+ return None
+
+
+class OpenDocumentFileLookupProvider(FileLookupProvider):
+ """
+ Makes a guess that the if the path that was looked for matches the end
+ of the path of a currently open document then that document is the one
+ that is looked for. Example: If a document is opened called '/tmp/t.c'
+ and a lookup is made for 't.c' or 'tmp/t.c' then both will match since
+ the open document ends with the path that is searched for.
+ """
+
+ def lookup(self, path):
+ if path.startswith('/'):
+ return None
+
+ for doc in gedit.app_get_default().get_documents():
+ if doc.is_local():
+ location = doc.get_location()
+ if location and location.get_uri().endswith(path):
+ return location
+ return None
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/functions.py b/plugins/externaltools/tools/functions.py
new file mode 100755
index 00000000..0d2bfdbf
--- /dev/null
+++ b/plugins/externaltools/tools/functions.py
@@ -0,0 +1,303 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gtk
+from gtk import gdk
+import gio
+import gedit
+#import gtksourceview
+from outputpanel import OutputPanel
+from capture import *
+
+def default(val, d):
+ if val is not None:
+ return val
+ else:
+ return d
+
+def current_word(document):
+ piter = document.get_iter_at_mark(document.get_insert())
+ start = piter.copy()
+
+ if not piter.starts_word() and (piter.inside_word() or piter.ends_word()):
+ start.backward_word_start()
+
+ if not piter.ends_word() and piter.inside_word():
+ piter.forward_word_end()
+
+ return (start, piter)
+
+# ==== Capture related functions ====
+def run_external_tool(window, node):
+ # Configure capture environment
+ try:
+ cwd = os.getcwd()
+ except OSError:
+ cwd = os.getenv('HOME');
+
+ capture = Capture(node.command, cwd)
+ capture.env = os.environ.copy()
+ capture.set_env(GEDIT_CWD = cwd)
+
+ view = window.get_active_view()
+ if view is not None:
+ # Environment vars relative to current document
+ document = view.get_buffer()
+ uri = document.get_uri()
+
+ # Current line number
+ piter = document.get_iter_at_mark(document.get_insert())
+ capture.set_env(GEDIT_CURRENT_LINE_NUMBER=str(piter.get_line() + 1))
+
+ # Current line text
+ piter.set_line_offset(0)
+ end = piter.copy()
+
+ if not end.ends_line():
+ end.forward_to_line_end()
+
+ capture.set_env(GEDIT_CURRENT_LINE=piter.get_text(end))
+
+ # Selected text (only if input is not selection)
+ if node.input != 'selection' and node.input != 'selection-document':
+ bounds = document.get_selection_bounds()
+
+ if bounds:
+ capture.set_env(GEDIT_SELECTED_TEXT=bounds[0].get_text(bounds[1]))
+
+ bounds = current_word(document)
+ capture.set_env(GEDIT_CURRENT_WORD=bounds[0].get_text(bounds[1]))
+
+ capture.set_env(GEDIT_CURRENT_DOCUMENT_TYPE=document.get_mime_type())
+
+ if uri is not None:
+ gfile = gio.File(uri)
+ scheme = gfile.get_uri_scheme()
+ name = os.path.basename(uri)
+ capture.set_env(GEDIT_CURRENT_DOCUMENT_URI = uri,
+ GEDIT_CURRENT_DOCUMENT_NAME = name,
+ GEDIT_CURRENT_DOCUMENT_SCHEME = scheme)
+ if gedit.utils.uri_has_file_scheme(uri):
+ path = gfile.get_path()
+ cwd = os.path.dirname(path)
+ capture.set_cwd(cwd)
+ capture.set_env(GEDIT_CURRENT_DOCUMENT_PATH = path,
+ GEDIT_CURRENT_DOCUMENT_DIR = cwd)
+
+ documents_uri = [doc.get_uri()
+ for doc in window.get_documents()
+ if doc.get_uri() is not None]
+ documents_path = [gio.File(uri).get_path()
+ for uri in documents_uri
+ if gedit.utils.uri_has_file_scheme(uri)]
+ capture.set_env(GEDIT_DOCUMENTS_URI = ' '.join(documents_uri),
+ GEDIT_DOCUMENTS_PATH = ' '.join(documents_path))
+
+ flags = capture.CAPTURE_BOTH
+
+ if not node.has_hash_bang():
+ flags |= capture.CAPTURE_NEEDS_SHELL
+
+ capture.set_flags(flags)
+
+ # Get input text
+ input_type = node.input
+ output_type = node.output
+
+ # Get the panel
+ panel = window.get_data("ExternalToolsPluginWindowData")._output_buffer
+ panel.clear()
+
+ if output_type == 'output-panel':
+ panel.show()
+
+ # Assign the error output to the output panel
+ panel.set_process(capture)
+
+ if input_type != 'nothing' and view is not None:
+ if input_type == 'document':
+ start, end = document.get_bounds()
+ elif input_type == 'selection' or input_type == 'selection-document':
+ try:
+ start, end = document.get_selection_bounds()
+
+ print start, end
+ except ValueError:
+ if input_type == 'selection-document':
+ start, end = document.get_bounds()
+
+ if output_type == 'replace-selection':
+ document.select_range(start, end)
+ else:
+ start = document.get_iter_at_mark(document.get_insert())
+ end = start.copy()
+
+ elif input_type == 'line':
+ start = document.get_iter_at_mark(document.get_insert())
+ end = start.copy()
+ if not start.starts_line():
+ start.set_line_offset(0)
+ if not end.ends_line():
+ end.forward_to_line_end()
+ elif input_type == 'word':
+ start = document.get_iter_at_mark(document.get_insert())
+ end = start.copy()
+ if not start.inside_word():
+ panel.write(_('You must be inside a word to run this command'),
+ panel.command_tag)
+ return
+ if not start.starts_word():
+ start.backward_word_start()
+ if not end.ends_word():
+ end.forward_word_end()
+
+ input_text = document.get_text(start, end)
+ capture.set_input(input_text)
+
+ # Assign the standard output to the chosen "file"
+ if output_type == 'new-document':
+ tab = window.create_tab(True)
+ view = tab.get_view()
+ document = tab.get_document()
+ pos = document.get_start_iter()
+ capture.connect('stdout-line', capture_stdout_line_document, document, pos)
+ document.begin_user_action()
+ view.set_editable(False)
+ view.set_cursor_visible(False)
+ elif output_type != 'output-panel' and output_type != 'nothing' and view is not None:
+ document.begin_user_action()
+ view.set_editable(False)
+ view.set_cursor_visible(False)
+
+ if output_type == 'insert':
+ pos = document.get_iter_at_mark(document.get_mark('insert'))
+ elif output_type == 'replace-selection':
+ document.delete_selection(False, False)
+ pos = document.get_iter_at_mark(document.get_mark('insert'))
+ elif output_type == 'replace-document':
+ document.set_text('')
+ pos = document.get_end_iter()
+ else:
+ pos = document.get_end_iter()
+ capture.connect('stdout-line', capture_stdout_line_document, document, pos)
+ elif output_type != 'nothing':
+ capture.connect('stdout-line', capture_stdout_line_panel, panel)
+ document.begin_user_action()
+
+ capture.connect('stderr-line', capture_stderr_line_panel, panel)
+ capture.connect('begin-execute', capture_begin_execute_panel, panel, view, node.name)
+ capture.connect('end-execute', capture_end_execute_panel, panel, view, output_type)
+
+ # Run the command
+ capture.execute()
+
+ if output_type != 'nothing':
+ document.end_user_action()
+
+class MultipleDocumentsSaver:
+ def __init__(self, window, docs, node):
+ self._window = window
+ self._node = node
+ self._error = False
+
+ self._counter = len(docs)
+ self._signal_ids = {}
+ self._counter = 0
+
+ signals = {}
+
+ for doc in docs:
+ signals[doc] = doc.connect('saving', self.on_document_saving)
+ gedit.commands.save_document(window, doc)
+ doc.disconnect(signals[doc])
+
+ def on_document_saving(self, doc, size, total_size):
+ self._counter += 1
+ self._signal_ids[doc] = doc.connect('saved', self.on_document_saved)
+
+ def on_document_saved(self, doc, error):
+ if error:
+ self._error = True
+
+ doc.disconnect(self._signal_ids[doc])
+ del self._signal_ids[doc]
+
+ self._counter -= 1
+
+ if self._counter == 0 and not self._error:
+ run_external_tool(self._window, self._node)
+
+def capture_menu_action(action, window, node):
+ if node.save_files == 'document' and window.get_active_document():
+ MultipleDocumentsSaver(window, [window.get_active_document()], node)
+ return
+ elif node.save_files == 'all':
+ MultipleDocumentsSaver(window, window.get_documents(), node)
+ return
+
+ run_external_tool(window, node)
+
+def capture_stderr_line_panel(capture, line, panel):
+ if not panel.visible():
+ panel.show()
+
+ panel.write(line, panel.error_tag)
+
+def capture_begin_execute_panel(capture, panel, view, label):
+ view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gdk.Cursor(gdk.WATCH))
+
+ panel['stop'].set_sensitive(True)
+ panel.clear()
+ panel.write(_("Running tool:"), panel.italic_tag);
+ panel.write(" %s\n\n" % label, panel.bold_tag);
+
+def capture_end_execute_panel(capture, exit_code, panel, view, output_type):
+ panel['stop'].set_sensitive(False)
+
+ if output_type in ('new-document','replace-document'):
+ doc = view.get_buffer()
+ start = doc.get_start_iter()
+ end = start.copy()
+ end.forward_chars(300)
+
+ mtype = gio.content_type_guess(data=doc.get_text(start, end))
+ lmanager = gedit.get_language_manager()
+
+ language = lmanager.guess_language(doc.get_uri(), mtype)
+
+ if language is not None:
+ doc.set_language(language)
+
+ view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(gdk.Cursor(gdk.XTERM))
+ view.set_cursor_visible(True)
+ view.set_editable(True)
+
+ if exit_code == 0:
+ panel.write("\n" + _("Done.") + "\n", panel.italic_tag)
+ else:
+ panel.write("\n" + _("Exited") + ":", panel.italic_tag)
+ panel.write(" %d\n" % exit_code, panel.bold_tag)
+
+def capture_stdout_line_panel(capture, line, panel):
+ panel.write(line)
+
+def capture_stdout_line_document(capture, line, document, pos):
+ document.insert(pos, line)
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/library.py b/plugins/externaltools/tools/library.py
new file mode 100755
index 00000000..6eb6ff1a
--- /dev/null
+++ b/plugins/externaltools/tools/library.py
@@ -0,0 +1,493 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2006 Steve Frécinaux <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import re
+import locale
+import platform
+
+class Singleton(object):
+ _instance = None
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super(Singleton, cls).__new__(
+ cls, *args, **kwargs)
+ cls._instance.__init_once__()
+
+ return cls._instance
+
+class ToolLibrary(Singleton):
+ def __init_once__(self):
+ self.locations = []
+
+ def set_locations(self, datadir):
+ self.locations = []
+
+ if platform.platform() != 'Windows':
+ for d in self.get_xdg_data_dirs():
+ self.locations.append(os.path.join(d, 'gedit-2', 'plugins', 'externaltools', 'tools'))
+
+ self.locations.append(datadir)
+
+ # self.locations[0] is where we save the custom scripts
+ if platform.platform() == 'Windows':
+ toolsdir = os.path.expanduser('~/gedit/tools')
+ else:
+ userdir = os.getenv('MATE22_USER_DIR')
+ if userdir:
+ toolsdir = os.path.join(userdir, 'gedit/tools')
+ else:
+ toolsdir = os.path.expanduser('~/.mate2/gedit/tools')
+
+ self.locations.insert(0, toolsdir);
+
+ if not os.path.isdir(self.locations[0]):
+ os.makedirs(self.locations[0])
+ self.tree = ToolDirectory(self, '')
+ self.import_old_xml_store()
+ else:
+ self.tree = ToolDirectory(self, '')
+
+ # cf. http://standards.freedesktop.org/basedir-spec/latest/
+ def get_xdg_data_dirs(self):
+ dirs = os.getenv('XDG_DATA_DIRS')
+ if dirs:
+ dirs = dirs.split(os.pathsep)
+ else:
+ dirs = ('/usr/local/share', '/usr/share')
+ return dirs
+
+ # This function is meant to be ran only once, when the tools directory is
+ # created. It imports eventual tools that have been saved in the old XML
+ # storage file.
+ def import_old_xml_store(self):
+ import xml.etree.ElementTree as et
+ userdir = os.getenv('MATE22_USER_DIR')
+ if userdir:
+ filename = os.path.join(userdir, 'gedit/gedit-tools.xml')
+ else:
+ filename = os.path.expanduser('~/.mate2/gedit/gedit-tools.xml')
+
+ if not os.path.isfile(filename):
+ return
+
+ print "External tools: importing old tools into the new store..."
+
+ xtree = et.parse(filename)
+ xroot = xtree.getroot()
+
+ for xtool in xroot:
+ for i in self.tree.tools:
+ if i.name == xtool.get('label'):
+ tool = i
+ break
+ else:
+ tool = Tool(self.tree)
+ tool.name = xtool.get('label')
+ tool.autoset_filename()
+ self.tree.tools.append(tool)
+ tool.comment = xtool.get('description')
+ tool.shortcut = xtool.get('accelerator')
+ tool.applicability = xtool.get('applicability')
+ tool.output = xtool.get('output')
+ tool.input = xtool.get('input')
+
+ tool.save_with_script(xtool.text)
+
+ def get_full_path(self, path, mode='r', system = True, local = True):
+ assert (system or local)
+ if path is None:
+ return None
+ if mode == 'r':
+ if system and local:
+ locations = self.locations
+ elif local and not system:
+ locations = [self.locations[0]]
+ elif system and not local:
+ locations = self.locations[1:]
+ else:
+ raise ValueError("system and local can't be both set to False")
+
+ for i in locations:
+ p = os.path.join(i, path)
+ if os.path.lexists(p):
+ return p
+ return None
+ else:
+ path = os.path.join(self.locations[0], path)
+ dirname = os.path.dirname(path)
+ if not os.path.isdir(dirname):
+ os.mkdir(dirname)
+ return path
+
+class ToolDirectory(object):
+ def __init__(self, parent, dirname):
+ super(ToolDirectory, self).__init__()
+ self.subdirs = list()
+ self.tools = list()
+ if isinstance(parent, ToolDirectory):
+ self.parent = parent
+ self.library = parent.library
+ else:
+ self.parent = None
+ self.library = parent
+ self.dirname = dirname
+ self._load()
+
+ def listdir(self):
+ elements = dict()
+ for l in self.library.locations:
+ d = os.path.join(l, self.dirname)
+ if not os.path.isdir(d):
+ continue
+ for i in os.listdir(d):
+ elements[i] = None
+ keys = elements.keys()
+ keys.sort()
+ return keys
+
+ def _load(self):
+ for p in self.listdir():
+ path = os.path.join(self.dirname, p)
+ full_path = self.library.get_full_path(path)
+ if os.path.isdir(full_path):
+ self.subdirs.append(ToolDirectory(self, p))
+ elif os.path.isfile(full_path) and os.access(full_path, os.X_OK):
+ self.tools.append(Tool(self, p))
+
+ def get_path(self):
+ if self.parent is None:
+ return self.dirname
+ else:
+ return os.path.join(self.parent.get_path(), self.dirname)
+ path = property(get_path)
+
+ def get_name(self):
+ return os.path.basename(self.dirname)
+ name = property(get_name)
+
+ def delete_tool(self, tool):
+ # Only remove it if it lays in $HOME
+ if tool in self.tools:
+ path = tool.get_path()
+ if path is not None:
+ filename = os.path.join(self.library.locations[0], path)
+ if os.path.isfile(filename):
+ os.unlink(filename)
+ self.tools.remove(tool)
+ return True
+ else:
+ return False
+
+ def revert_tool(self, tool):
+ # Only remove it if it lays in $HOME
+ filename = os.path.join(self.library.locations[0], tool.get_path())
+ if tool in self.tools and os.path.isfile(filename):
+ os.unlink(filename)
+ tool._load()
+ return True
+ else:
+ return False
+
+
+class Tool(object):
+ RE_KEY = re.compile('^([a-zA-Z_][a-zA-Z0-9_.\-]*)(\[([a-zA-Z_@]+)\])?$')
+
+ def __init__(self, parent, filename = None):
+ super(Tool, self).__init__()
+ self.parent = parent
+ self.library = parent.library
+ self.filename = filename
+ self.changed = False
+ self._properties = dict()
+ self._transform = {
+ 'Languages': [self._to_list, self._from_list]
+ }
+ self._load()
+
+ def _to_list(self, value):
+ if value.strip() == '':
+ return []
+ else:
+ return map(lambda x: x.strip(), value.split(','))
+
+ def _from_list(self, value):
+ return ','.join(value)
+
+ def _parse_value(self, key, value):
+ if key in self._transform:
+ return self._transform[key][0](value)
+ else:
+ return value
+
+ def _load(self):
+ if self.filename is None:
+ return
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return
+
+ fp = file(filename, 'r', 1)
+ in_block = False
+ lang = locale.getlocale(locale.LC_MESSAGES)[0]
+
+ for line in fp:
+ if not in_block:
+ in_block = line.startswith('# [Gedit Tool]')
+ continue
+ if line.startswith('##') or line.startswith('# #'): continue
+ if not line.startswith('# '): break
+
+ try:
+ (key, value) = [i.strip() for i in line[2:].split('=', 1)]
+ m = self.RE_KEY.match(key)
+ if m.group(3) is None:
+ self._properties[m.group(1)] = self._parse_value(m.group(1), value)
+ elif lang is not None and lang.startswith(m.group(3)):
+ self._properties[m.group(1)] = self._parse_value(m.group(1), value)
+ except ValueError:
+ break
+ fp.close()
+ self.changed = False
+
+ def _set_property_if_changed(self, key, value):
+ if value != self._properties.get(key):
+ self._properties[key] = value
+
+ self.changed = True
+
+ def is_global(self):
+ return self.library.get_full_path(self.get_path(), local=False) is not None
+
+ def is_local(self):
+ return self.library.get_full_path(self.get_path(), system=False) is not None
+
+ def is_global(self):
+ return self.library.get_full_path(self.get_path(), local=False) is not None
+
+ def get_path(self):
+ if self.filename is not None:
+ return os.path.join(self.parent.get_path(), self.filename)
+ else:
+ return None
+ path = property(get_path)
+
+ # This command is the one that is meant to be ran
+ # (later, could have an Exec key or something)
+ def get_command(self):
+ return self.library.get_full_path(self.get_path())
+ command = property(get_command)
+
+ def get_applicability(self):
+ applicability = self._properties.get('Applicability')
+ if applicability: return applicability
+ return 'all'
+ def set_applicability(self, value):
+ self._set_property_if_changed('Applicability', value)
+ applicability = property(get_applicability, set_applicability)
+
+ def get_name(self):
+ name = self._properties.get('Name')
+ if name: return name
+ return os.path.basename(self.filename)
+ def set_name(self, value):
+ self._set_property_if_changed('Name', value)
+ name = property(get_name, set_name)
+
+ def get_shortcut(self):
+ shortcut = self._properties.get('Shortcut')
+ if shortcut: return shortcut
+ return None
+ def set_shortcut(self, value):
+ self._set_property_if_changed('Shortcut', value)
+ shortcut = property(get_shortcut, set_shortcut)
+
+ def get_comment(self):
+ comment = self._properties.get('Comment')
+ if comment: return comment
+ return self.filename
+ def set_comment(self, value):
+ self._set_property_if_changed('Comment', value)
+ comment = property(get_comment, set_comment)
+
+ def get_input(self):
+ input = self._properties.get('Input')
+ if input: return input
+ return 'nothing'
+ def set_input(self, value):
+ self._set_property_if_changed('Input', value)
+ input = property(get_input, set_input)
+
+ def get_output(self):
+ output = self._properties.get('Output')
+ if output: return output
+ return 'output-panel'
+ def set_output(self, value):
+ self._set_property_if_changed('Output', value)
+ output = property(get_output, set_output)
+
+ def get_save_files(self):
+ save_files = self._properties.get('Save-files')
+ if save_files: return save_files
+ return 'nothing'
+ def set_save_files(self, value):
+ self._set_property_if_changed('Save-files', value)
+ save_files = property(get_save_files, set_save_files)
+
+ def get_languages(self):
+ languages = self._properties.get('Languages')
+ if languages: return languages
+ return []
+ def set_languages(self, value):
+ self._set_property_if_changed('Languages', value)
+ languages = property(get_languages, set_languages)
+
+ def has_hash_bang(self):
+ if self.filename is None:
+ return True
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return True
+
+ fp = open(filename, 'r', 1)
+ for line in fp:
+ if line.strip() == '':
+ continue
+
+ return line.startswith('#!')
+
+ # There is no property for this one because this function is quite
+ # expensive to perform
+ def get_script(self):
+ if self.filename is None:
+ return ["#!/bin/sh\n"]
+
+ filename = self.library.get_full_path(self.get_path())
+ if filename is None:
+ return ["#!/bin/sh\n"]
+
+ fp = open(filename, 'r', 1)
+ lines = list()
+
+ # before entering the data block
+ for line in fp:
+ if line.startswith('# [Gedit Tool]'):
+ break
+ lines.append(line)
+ # in the block:
+ for line in fp:
+ if line.startswith('##'): continue
+ if not (line.startswith('# ') and '=' in line):
+ # after the block: strip one emtpy line (if present)
+ if line.strip() != '':
+ lines.append(line)
+ break
+ # after the block
+ for line in fp:
+ lines.append(line)
+ fp.close()
+ return lines
+
+ def _dump_properties(self):
+ lines = ['# [Gedit Tool]']
+ for item in self._properties.iteritems():
+ if item[0] in self._transform:
+ lines.append('# %s=%s' % (item[0], self._transform[item[0]][1](item[1])))
+ elif item[1] is not None:
+ lines.append('# %s=%s' % item)
+ return '\n'.join(lines) + '\n'
+
+ def save_with_script(self, script):
+ filename = self.library.get_full_path(self.filename, 'w')
+
+ fp = open(filename, 'w', 1)
+
+ # Make sure to first print header (shebang, modeline), then
+ # properties, and then actual content
+ header = []
+ content = []
+ inheader = True
+
+ # Parse
+ for line in script:
+ line = line.rstrip("\n")
+
+ if not inheader:
+ content.append(line)
+ elif line.startswith('#!'):
+ # Shebang (should be always present)
+ header.append(line)
+ elif line.strip().startswith('#') and ('-*-' in line or 'ex:' in line or 'vi:' in line or 'vim:' in line):
+ header.append(line)
+ else:
+ content.append(line)
+ inheader = False
+
+ # Write out header
+ for line in header:
+ fp.write(line + "\n")
+
+ fp.write(self._dump_properties())
+ fp.write("\n")
+
+ for line in content:
+ fp.write(line + "\n")
+
+ fp.close()
+ os.chmod(filename, 0750)
+ self.changed = False
+
+ def save(self):
+ if self.changed:
+ self.save_with_script(self.get_script())
+
+ def autoset_filename(self):
+ if self.filename is not None:
+ return
+ dirname = self.parent.path
+ if dirname != '':
+ dirname += os.path.sep
+
+ basename = self.name.lower().replace(' ', '-').replace('/', '-')
+
+ if self.library.get_full_path(dirname + basename):
+ i = 2
+ while self.library.get_full_path(dirname + "%s-%d" % (basename, i)):
+ i += 1
+ basename = "%s-%d" % (basename, i)
+ self.filename = basename
+
+if __name__ == '__main__':
+ library = ToolLibrary()
+
+ def print_tool(t, indent):
+ print indent * " " + "%s: %s" % (t.filename, t.name)
+
+ def print_dir(d, indent):
+ print indent * " " + d.dirname + '/'
+ for i in d.subdirs:
+ print_dir(i, indent+1)
+ for i in d.tools:
+ print_tool(i, indent+1)
+
+ print_dir(library.tree, 0)
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/linkparsing.py b/plugins/externaltools/tools/linkparsing.py
new file mode 100755
index 00000000..27b9ba89
--- /dev/null
+++ b/plugins/externaltools/tools/linkparsing.py
@@ -0,0 +1,231 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2010 Per Arneng <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import re
+
+class Link:
+ """
+ This class represents a file link from within a string given by the
+ output of some software tool. A link contains a reference to a file, the
+ line number within the file and the boundaries within the given output
+ string that should be marked as a link.
+ """
+
+ def __init__(self, path, line_nr, start, end):
+ """
+ path -- the path of the file (that could be extracted)
+ line_nr -- the line nr of the specified file
+ start -- the index within the string that the link starts at
+ end -- the index within the string where the link ends at
+ """
+ self.path = path
+ self.line_nr = int(line_nr)
+ self.start = start
+ self.end = end
+
+ def __repr__(self):
+ return "%s[%s](%s:%s)" % (self.path, self.line_nr,
+ self.start, self.end)
+
+class LinkParser:
+ """
+ Parses a text using different parsing providers with the goal of finding one
+ or more file links within the text. A typical example could be the output
+ from a compiler that specifies an error in a specific file. The path of the
+ file, the line nr and some more info is then returned so that it can be used
+ to be able to navigate from the error output in to the specific file.
+
+ The actual work of parsing the text is done by instances of classes that
+ inherits from AbstractLinkParser or by regular expressions. To add a new
+ parser just create a class that inherits from AbstractLinkParser and then
+ register in this class cunstructor using the method add_parser. If you want
+ to add a regular expression then just call add_regexp in this class
+ constructor and provide your regexp string as argument.
+ """
+
+ def __init__(self):
+ self._providers = []
+ self.add_regexp(REGEXP_STANDARD)
+ self.add_regexp(REGEXP_PYTHON)
+ self.add_regexp(REGEXP_VALAC)
+ self.add_regexp(REGEXP_BASH)
+ self.add_regexp(REGEXP_RUBY)
+ self.add_regexp(REGEXP_PERL)
+ self.add_regexp(REGEXP_MCS)
+
+ def add_parser(self, parser):
+ self._providers.append(parser)
+
+ def add_regexp(self, regexp):
+ """
+ Adds a regular expression string that should match a link using
+ re.MULTILINE and re.VERBOSE regexp. The area marked as a link should
+ be captured by a group named lnk. The path of the link should be
+ captured by a group named pth. The line number should be captured by
+ a group named ln. To read more about this look at the documentation
+ for the RegexpLinkParser constructor.
+ """
+ self.add_parser(RegexpLinkParser(regexp))
+
+ def parse(self, text):
+ """
+ Parses the given text and returns a list of links that are parsed from
+ the text. This method delegates to parser providers that can parse
+ output from different kinds of formats. If no links are found then an
+ empty list is returned.
+
+ text -- the text to scan for file links. 'text' can not be None.
+ """
+ if text is None:
+ raise ValueError("text can not be None")
+
+ links = []
+
+ for provider in self._providers:
+ links.extend(provider.parse(text))
+
+ return links
+
+class AbstractLinkParser(object):
+ """The "abstract" base class for link parses"""
+
+ def parse(self, text):
+ """
+ This method should be implemented by subclasses. It takes a text as
+ argument (never None) and then returns a list of Link objects. If no
+ links are found then an empty list is expected. The Link class is
+ defined in this module. If you do not override this method then a
+ NotImplementedError will be thrown.
+
+ text -- the text to parse. This argument is never None.
+ """
+ raise NotImplementedError("need to implement a parse method")
+
+class RegexpLinkParser(AbstractLinkParser):
+ """
+ A class that represents parsers that only use one single regular expression.
+ It can be used by subclasses or by itself. See the constructor documentation
+ for details about the rules surrouning the regexp.
+ """
+
+ def __init__(self, regex):
+ """
+ Creates a new RegexpLinkParser based on the given regular expression.
+ The regular expression is multiline and verbose (se python docs on
+ compilation flags). The regular expression should contain three named
+ capturing groups 'lnk', 'pth' and 'ln'. 'lnk' represents the area wich
+ should be marked as a link in the text. 'pth' is the path that should
+ be looked for and 'ln' is the line number in that file.
+ """
+ self.re = re.compile(regex, re.MULTILINE | re.VERBOSE)
+
+ def parse(self, text):
+ links = []
+ for m in re.finditer(self.re, text):
+ path = m.group("pth")
+ line_nr = m.group("ln")
+ start = m.start("lnk")
+ end = m.end("lnk")
+ link = Link(path, line_nr, start, end)
+ links.append(link)
+
+ return links
+
+# gcc 'test.c:13: warning: ...'
+# javac 'Test.java:13: ...'
+# ruby 'test.rb:5: ...'
+# scalac 'Test.scala:5: ...'
+# 6g (go) 'test.go:9: ...'
+REGEXP_STANDARD = r"""
+^
+(?P<lnk>
+ (?P<pth> .*[a-z0-9] )
+ \:
+ (?P<ln> \d+)
+)
+\:\s"""
+
+# python ' File "test.py", line 13'
+REGEXP_PYTHON = r"""
+^\s\sFile\s
+(?P<lnk>
+ \"
+ (?P<pth> [^\"]+ )
+ \",\sline\s
+ (?P<ln> \d+ )
+),"""
+
+# python 'test.sh: line 5:'
+REGEXP_BASH = r"""
+^(?P<lnk>
+ (?P<pth> .* )
+ \:\sline\s
+ (?P<ln> \d+ )
+)\:"""
+
+# valac 'Test.vala:13.1-13.3: ...'
+REGEXP_VALAC = r"""
+^(?P<lnk>
+ (?P<pth>
+ .*vala
+ )
+ \:
+ (?P<ln>
+ \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<lnk>
+ (?P<pth>
+ .*
+ )
+ \:
+ (?P<ln>
+ \d+
+ )
+ )"""
+
+# perl 'syntax error at test.pl line 88, near "$fake_var'
+REGEXP_PERL = r"""
+\sat\s
+(?P<lnk>
+ (?P<pth> .* )
+ \sline\s
+ (?P<ln> \d+ )
+)"""
+
+# mcs (C#) 'Test.cs(12,7): error CS0103: The name `fakeMethod'
+REGEXP_MCS = r"""
+^
+(?P<lnk>
+ (?P<pth> .*\.[cC][sS] )
+ \(
+ (?P<ln> \d+ )
+ ,\d+\)
+)
+\:\s
+"""
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/manager.py b/plugins/externaltools/tools/manager.py
new file mode 100755
index 00000000..e28a088a
--- /dev/null
+++ b/plugins/externaltools/tools/manager.py
@@ -0,0 +1,948 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__all__ = ('Manager', )
+
+import gedit
+import gtk
+import gtksourceview2 as gsv
+import os.path
+from library import *
+from functions import *
+import hashlib
+from xml.sax import saxutils
+import gobject
+
+class LanguagesPopup(gtk.Window):
+ COLUMN_NAME = 0
+ COLUMN_ID = 1
+ COLUMN_ENABLED = 2
+
+ def __init__(self, languages):
+ gtk.Window.__init__(self, gtk.WINDOW_POPUP)
+
+ self.set_default_size(200, 200)
+ self.props.can_focus = True
+
+ self.build()
+ self.init_languages(languages)
+
+ self.show()
+ self.map()
+
+ self.grab_add()
+
+ gtk.gdk.keyboard_grab(self.window, False, 0L)
+ gtk.gdk.pointer_grab(self.window, False, gtk.gdk.BUTTON_PRESS_MASK |
+ gtk.gdk.BUTTON_RELEASE_MASK |
+ gtk.gdk.POINTER_MOTION_MASK |
+ gtk.gdk.ENTER_NOTIFY_MASK |
+ gtk.gdk.LEAVE_NOTIFY_MASK |
+ gtk.gdk.PROXIMITY_IN_MASK |
+ gtk.gdk.PROXIMITY_OUT_MASK, None, None, 0L)
+
+ self.view.get_selection().select_path((0,))
+
+ def build(self):
+ self.model = gtk.ListStore(str, str, bool)
+
+ self.sw = gtk.ScrolledWindow()
+ self.sw.show()
+
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+
+ self.view = gtk.TreeView(self.model)
+ self.view.show()
+
+ self.view.set_headers_visible(False)
+
+ column = gtk.TreeViewColumn()
+
+ renderer = gtk.CellRendererToggle()
+ column.pack_start(renderer, False)
+ column.set_attributes(renderer, active=self.COLUMN_ENABLED)
+
+ renderer.connect('toggled', self.on_language_toggled)
+
+ renderer = gtk.CellRendererText()
+ column.pack_start(renderer, True)
+ column.set_attributes(renderer, text=self.COLUMN_NAME)
+
+ self.view.append_column(column)
+ self.view.set_row_separator_func(self.on_separator)
+
+ self.sw.add(self.view)
+
+ self.add(self.sw)
+
+ def enabled_languages(self, model, path, piter, ret):
+ enabled = model.get_value(piter, self.COLUMN_ENABLED)
+
+ if path == (0,) and enabled:
+ return True
+
+ if enabled:
+ ret.append(model.get_value(piter, self.COLUMN_ID))
+
+ return False
+
+ def languages(self):
+ ret = []
+
+ self.model.foreach(self.enabled_languages, ret)
+ return ret
+
+ def on_separator(self, model, piter):
+ val = model.get_value(piter, self.COLUMN_NAME)
+ return val == '-'
+
+ def init_languages(self, languages):
+ manager = gsv.LanguageManager()
+ langs = gedit.language_manager_list_languages_sorted(manager, True)
+
+ self.model.append([_('All languages'), None, not languages])
+ self.model.append(['-', None, False])
+ self.model.append([_('Plain Text'), 'plain', 'plain' in languages])
+ self.model.append(['-', None, False])
+
+ for lang in langs:
+ self.model.append([lang.get_name(), lang.get_id(), lang.get_id() in languages])
+
+ def correct_all(self, model, path, piter, enabled):
+ if path == (0,):
+ return False
+
+ model.set_value(piter, self.COLUMN_ENABLED, enabled)
+
+ def on_language_toggled(self, renderer, path):
+ piter = self.model.get_iter(path)
+
+ enabled = self.model.get_value(piter, self.COLUMN_ENABLED)
+ self.model.set_value(piter, self.COLUMN_ENABLED, not enabled)
+
+ if path == '0':
+ self.model.foreach(self.correct_all, False)
+ else:
+ self.model.set_value(self.model.get_iter_first(), self.COLUMN_ENABLED, False)
+
+ def do_key_press_event(self, event):
+ if event.keyval == gtk.keysyms.Escape:
+ self.destroy()
+ return True
+ else:
+ event.window = self.view.get_bin_window()
+ return self.view.event(event)
+
+ def do_key_release_event(self, event):
+ event.window = self.view.get_bin_window()
+ return self.view.event(event)
+
+ def in_window(self, event, window=None):
+ if not window:
+ window = self.window
+
+ geometry = window.get_geometry()
+ origin = window.get_origin()
+
+ return event.x_root >= origin[0] and \
+ event.x_root <= origin[0] + geometry[2] and \
+ event.y_root >= origin[1] and \
+ event.y_root <= origin[1] + geometry[3]
+
+ def do_destroy(self):
+ gtk.gdk.keyboard_ungrab(0L)
+ gtk.gdk.pointer_ungrab(0L)
+
+ return gtk.Window.do_destroy(self)
+
+ def setup_event(self, event, window):
+ fr = event.window.get_origin()
+ to = window.get_origin()
+
+ event.window = window
+ event.x += fr[0] - to[0]
+ event.y += fr[1] - to[1]
+
+ def resolve_widgets(self, root):
+ res = [root]
+
+ if isinstance(root, gtk.Container):
+ root.forall(lambda x, y: res.extend(self.resolve_widgets(x)), None)
+
+ return res
+
+ def resolve_windows(self, window):
+ if not window:
+ return []
+
+ res = [window]
+ res.extend(window.get_children())
+
+ return res
+
+ def propagate_mouse_event(self, event):
+ allwidgets = self.resolve_widgets(self.get_child())
+ allwidgets.reverse()
+
+ orig = [event.x, event.y]
+
+ for widget in allwidgets:
+ windows = self.resolve_windows(widget.window)
+ windows.reverse()
+
+ for window in windows:
+ if not (window.get_events() & event.type):
+ continue
+
+ if self.in_window(event, window):
+ self.setup_event(event, window)
+
+ if widget.event(event):
+ return True
+
+ return False
+
+ def do_button_press_event(self, event):
+ if not self.in_window(event):
+ self.destroy()
+ else:
+ return self.propagate_mouse_event(event)
+
+ def do_button_release_event(self, event):
+ if not self.in_window(event):
+ self.destroy()
+ else:
+ return self.propagate_mouse_event(event)
+
+ def do_scroll_event(self, event):
+ return self.propagate_mouse_event(event)
+
+ def do_motion_notify_event(self, event):
+ return self.propagate_mouse_event(event)
+
+ def do_enter_notify_event(self, event):
+ return self.propagate_mouse_event(event)
+
+ def do_leave_notify_event(self, event):
+ return self.propagate_mouse_event(event)
+
+ def do_proximity_in_event(self, event):
+ return self.propagate_mouse_event(event)
+
+ def do_proximity_out_event(self, event):
+ return self.propagate_mouse_event(event)
+
+gobject.type_register(LanguagesPopup)
+
+class Manager:
+ TOOL_COLUMN = 0 # For Tree
+ NAME_COLUMN = 1 # For Combo
+
+ def __init__(self, datadir):
+ self.datadir = datadir
+ self.dialog = None
+ self._languages = {}
+ self._tool_rows = {}
+
+ self.build()
+
+ def build(self):
+ callbacks = {
+ 'on_new_tool_button_clicked' : self.on_new_tool_button_clicked,
+ 'on_remove_tool_button_clicked' : self.on_remove_tool_button_clicked,
+ 'on_tool_manager_dialog_response' : self.on_tool_manager_dialog_response,
+ 'on_tool_manager_dialog_focus_out': self.on_tool_manager_dialog_focus_out,
+ 'on_accelerator_key_press' : self.on_accelerator_key_press,
+ 'on_accelerator_focus_in' : self.on_accelerator_focus_in,
+ 'on_accelerator_focus_out' : self.on_accelerator_focus_out,
+ 'on_languages_button_clicked' : self.on_languages_button_clicked
+ }
+
+ # Load the "main-window" widget from the ui file.
+ self.ui = gtk.Builder()
+ self.ui.add_from_file(os.path.join(self.datadir, 'ui', 'tools.ui'))
+ self.ui.connect_signals(callbacks)
+ self.dialog = self.ui.get_object('tool-manager-dialog')
+
+ self.view = self.ui.get_object('view')
+
+ self.__init_tools_model()
+ self.__init_tools_view()
+
+ for name in ['input', 'output', 'applicability', 'save-files']:
+ self.__init_combobox(name)
+
+ self.do_update()
+
+ def expand_from_doc(self, doc):
+ row = None
+
+ if doc:
+ if doc.get_language():
+ lid = doc.get_language().get_id()
+
+ if lid in self._languages:
+ row = self._languages[lid]
+ elif 'plain' in self._languages:
+ row = self._languages['plain']
+
+ if not row and None in self._languages:
+ row = self._languages[None]
+
+ if not row:
+ return
+
+ self.view.expand_row(row.get_path(), False)
+ self.view.get_selection().select_path(row.get_path())
+
+ def run(self, window):
+ if self.dialog == None:
+ self.build()
+
+ # Open up language
+ self.expand_from_doc(window.get_active_document())
+
+ self.dialog.set_transient_for(window)
+ window.get_group().add_window(self.dialog)
+ self.dialog.present()
+
+ def add_accelerator(self, item):
+ if not item.shortcut:
+ return
+
+ if item.shortcut in self.accelerators:
+ if not item in self.accelerators[item.shortcut]:
+ self.accelerators[item.shortcut].append(item)
+ else:
+ self.accelerators[item.shortcut] = [item]
+
+ def remove_accelerator(self, item, shortcut=None):
+ if not shortcut:
+ shortcut = item.shortcut
+
+ if not shortcut in self.accelerators:
+ return
+
+ self.accelerators[shortcut].remove(item)
+
+ if not self.accelerators[shortcut]:
+ del self.accelerators[shortcut]
+
+ def add_tool_to_language(self, tool, language):
+ if isinstance(language, gsv.Language):
+ lid = language.get_id()
+ else:
+ lid = language
+
+ if not lid in self._languages:
+ piter = self.model.append(None, [language])
+
+ parent = gtk.TreeRowReference(self.model, self.model.get_path(piter))
+ self._languages[lid] = parent
+ else:
+ parent = self._languages[lid]
+
+ piter = self.model.get_iter(parent.get_path())
+ child = self.model.append(piter, [tool])
+
+ if not tool in self._tool_rows:
+ self._tool_rows[tool] = []
+
+ self._tool_rows[tool].append(gtk.TreeRowReference(self.model, self.model.get_path(child)))
+ return child
+
+ def add_tool(self, tool):
+ manager = gsv.LanguageManager()
+ ret = None
+
+ for lang in tool.languages:
+ l = manager.get_language(lang)
+
+ if l:
+ ret = self.add_tool_to_language(tool, l)
+ elif lang == 'plain':
+ ret = self.add_tool_to_language(tool, 'plain')
+
+ if not ret:
+ ret = self.add_tool_to_language(tool, None)
+
+ self.add_accelerator(tool)
+ return ret
+
+ def __init_tools_model(self):
+ self.tools = ToolLibrary()
+ self.current_node = None
+ self.script_hash = None
+ self.accelerators = dict()
+
+ self.model = gtk.TreeStore(object)
+ self.view.set_model(self.model)
+
+ for tool in self.tools.tree.tools:
+ self.add_tool(tool)
+
+ self.model.set_default_sort_func(self.sort_tools)
+ self.model.set_sort_column_id(-1, gtk.SORT_ASCENDING)
+
+ def sort_tools(self, model, iter1, iter2):
+ # For languages, sort All before everything else, otherwise alphabetical
+ t1 = model.get_value(iter1, self.TOOL_COLUMN)
+ t2 = model.get_value(iter2, self.TOOL_COLUMN)
+
+ if model.iter_parent(iter1) == None:
+ if t1 == None:
+ return -1
+
+ if t2 == None:
+ return 1
+
+ def lang_name(lang):
+ if isinstance(lang, gsv.Language):
+ return lang.get_name()
+ else:
+ return _('Plain Text')
+
+ n1 = lang_name(t1)
+ n2 = lang_name(t2)
+ else:
+ n1 = t1.name
+ n2 = t2.name
+
+ return cmp(n1.lower(), n2.lower())
+
+ def __init_tools_view(self):
+ # Tools column
+ column = gtk.TreeViewColumn('Tools')
+ renderer = gtk.CellRendererText()
+ column.pack_start(renderer, False)
+ renderer.set_property('editable', True)
+ self.view.append_column(column)
+
+ column.set_cell_data_func(renderer, self.get_cell_data_cb)
+
+ renderer.connect('edited', self.on_view_label_cell_edited)
+ renderer.connect('editing-started', self.on_view_label_cell_editing_started)
+
+ self.selection_changed_id = self.view.get_selection().connect('changed', self.on_view_selection_changed, None)
+
+ def __init_combobox(self, name):
+ combo = self[name]
+ combo.set_active(0)
+
+ # Convenience function to get an object from its name
+ def __getitem__(self, key):
+ return self.ui.get_object(key)
+
+ def set_active_by_name(self, combo_name, option_name):
+ combo = self[combo_name]
+ model = combo.get_model()
+ piter = model.get_iter_first()
+ while piter is not None:
+ if model.get_value(piter, self.NAME_COLUMN) == option_name:
+ combo.set_active_iter(piter)
+ return True
+ piter = model.iter_next(piter)
+ return False
+
+ def get_selected_tool(self):
+ model, piter = self.view.get_selection().get_selected()
+
+ if piter is not None:
+ tool = model.get_value(piter, self.TOOL_COLUMN)
+
+ if not isinstance(tool, Tool):
+ tool = None
+
+ return piter, tool
+ else:
+ return None, None
+
+ def compute_hash(self, string):
+ return hashlib.md5(string).hexdigest()
+
+ def save_current_tool(self):
+ if self.current_node is None:
+ return
+
+ if self.current_node.filename is None:
+ self.current_node.autoset_filename()
+
+ def combo_value(o, name):
+ combo = o[name]
+ return combo.get_model().get_value(combo.get_active_iter(), self.NAME_COLUMN)
+
+ self.current_node.input = combo_value(self, 'input')
+ self.current_node.output = combo_value(self, 'output')
+ self.current_node.applicability = combo_value(self, 'applicability')
+ self.current_node.save_files = combo_value(self, 'save-files')
+
+ buf = self['commands'].get_buffer()
+ script = buf.get_text(*buf.get_bounds())
+ h = self.compute_hash(script)
+ if h != self.script_hash:
+ # script has changed -> save it
+ self.current_node.save_with_script([line + "\n" for line in script.splitlines()])
+ self.script_hash = h
+ else:
+ self.current_node.save()
+
+ self.update_remove_revert()
+
+ def clear_fields(self):
+ self['accelerator'].set_text('')
+
+ buf = self['commands'].get_buffer()
+ buf.begin_not_undoable_action()
+ buf.set_text('')
+ buf.end_not_undoable_action()
+
+ for nm in ('input', 'output', 'applicability', 'save-files'):
+ self[nm].set_active(0)
+
+ self['languages_label'].set_text(_('All Languages'))
+
+ def fill_languages_button(self):
+ if not self.current_node or not self.current_node.languages:
+ self['languages_label'].set_text(_('All Languages'))
+ else:
+ manager = gsv.LanguageManager()
+ langs = []
+
+ for lang in self.current_node.languages:
+ if lang == 'plain':
+ langs.append(_('Plain Text'))
+ else:
+ l = manager.get_language(lang)
+
+ if l:
+ langs.append(l.get_name())
+
+ self['languages_label'].set_text(', '.join(langs))
+
+ def fill_fields(self):
+ node = self.current_node
+ self['accelerator'].set_text(default(node.shortcut, ''))
+
+ buf = self['commands'].get_buffer()
+ script = default(''.join(node.get_script()), '')
+
+ buf.begin_not_undoable_action()
+ buf.set_text(script)
+ buf.end_not_undoable_action()
+
+ self.script_hash = self.compute_hash(script)
+ contenttype = gio.content_type_guess(data=script)
+ lmanager = gedit.get_language_manager()
+ language = lmanager.guess_language(content_type=contenttype)
+
+ if language is not None:
+ buf.set_language(language)
+ buf.set_highlight_syntax(True)
+ else:
+ buf.set_highlight_syntax(False)
+
+ for nm in ('input', 'output', 'applicability', 'save-files'):
+ model = self[nm].get_model()
+ piter = model.get_iter_first()
+
+ self.set_active_by_name(nm,
+ default(node.__getattribute__(nm.replace('-', '_')),
+ model.get_value(piter, self.NAME_COLUMN)))
+
+ self.fill_languages_button()
+
+ def update_remove_revert(self):
+ piter, node = self.get_selected_tool()
+
+ removable = node is not None and node.is_local()
+
+ self['remove-tool-button'].set_sensitive(removable)
+ self['revert-tool-button'].set_sensitive(removable)
+
+ if node is not None and node.is_global():
+ self['remove-tool-button'].hide()
+ self['revert-tool-button'].show()
+ else:
+ self['remove-tool-button'].show()
+ self['revert-tool-button'].hide()
+
+ def do_update(self):
+ self.update_remove_revert()
+
+ piter, node = self.get_selected_tool()
+ self.current_node = node
+
+ if node is not None:
+ self.fill_fields()
+ self['tool-table'].set_sensitive(True)
+ else:
+ self.clear_fields()
+ self['tool-table'].set_sensitive(False)
+
+ def language_id_from_iter(self, piter):
+ if not piter:
+ return None
+
+ tool = self.model.get_value(piter, self.TOOL_COLUMN)
+
+ if isinstance(tool, Tool):
+ piter = self.model.iter_parent(piter)
+ tool = self.model.get_value(piter, self.TOOL_COLUMN)
+
+ if isinstance(tool, gsv.Language):
+ return tool.get_id()
+ elif tool:
+ return 'plain'
+
+ return None
+
+ def selected_language_id(self):
+ # Find current language if there is any
+ model, piter = self.view.get_selection().get_selected()
+
+ return self.language_id_from_iter(piter)
+
+ def on_new_tool_button_clicked(self, button):
+ self.save_current_tool()
+
+ # block handlers while inserting a new item
+ self.view.get_selection().handler_block(self.selection_changed_id)
+
+ self.current_node = Tool(self.tools.tree);
+ self.current_node.name = _('New tool')
+ self.tools.tree.tools.append(self.current_node)
+
+ lang = self.selected_language_id()
+
+ if lang:
+ self.current_node.languages = [lang]
+
+ piter = self.add_tool(self.current_node)
+
+ self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), True)
+ self.fill_fields()
+
+ self['tool-table'].set_sensitive(True)
+ self.view.get_selection().handler_unblock(self.selection_changed_id)
+
+ def tool_changed(self, tool, refresh=False):
+ for row in self._tool_rows[tool]:
+ self.model.row_changed(row.get_path(), self.model.get_iter(row.get_path()))
+
+ if refresh and tool == self.current_node:
+ self.fill_fields()
+
+ self.update_remove_revert()
+
+ def on_remove_tool_button_clicked(self, button):
+ piter, node = self.get_selected_tool()
+
+ if not node:
+ return
+
+ if node.is_global():
+ shortcut = node.shortcut
+
+ if node.parent.revert_tool(node):
+ self.remove_accelerator(node, shortcut)
+ self.add_accelerator(node)
+
+ self['revert-tool-button'].set_sensitive(False)
+ self.fill_fields()
+
+ self.tool_changed(node)
+ else:
+ parent = self.model.iter_parent(piter)
+ language = self.language_id_from_iter(parent)
+
+ self.model.remove(piter)
+
+ if language in node.languages:
+ node.languages.remove(language)
+
+ self._tool_rows[node] = filter(lambda x: x.valid(), self._tool_rows[node])
+
+ if not self._tool_rows[node]:
+ del self._tool_rows[node]
+
+ if node.parent.delete_tool(node):
+ self.remove_accelerator(node)
+ self.current_node = None
+ self.script_hash = None
+
+ if self.model.iter_is_valid(piter):
+ self.view.set_cursor(self.model.get_path(piter), self.view.get_column(self.TOOL_COLUMN), False)
+
+ self.view.grab_focus()
+
+ path = self._languages[language].get_path()
+ parent = self.model.get_iter(path)
+
+ if not self.model.iter_has_child(parent):
+ self.model.remove(parent)
+ del self._languages[language]
+
+ def on_view_label_cell_edited(self, cell, path, new_text):
+ if new_text != '':
+ piter = self.model.get_iter(path)
+ tool = self.model.get_value(piter, self.TOOL_COLUMN)
+
+ tool.name = new_text
+
+ self.save_current_tool()
+ self.tool_changed(tool)
+
+ def on_view_label_cell_editing_started(self, renderer, editable, path):
+ piter = self.model.get_iter(path)
+ tool = self.model.get_value(piter, self.TOOL_COLUMN)
+
+ if isinstance(editable, gtk.Entry):
+ editable.set_text(tool.name)
+ editable.grab_focus()
+
+ def on_view_selection_changed(self, selection, userdata):
+ self.save_current_tool()
+ self.do_update()
+
+ def accelerator_collision(self, name, node):
+ if not name in self.accelerators:
+ return []
+
+ ret = []
+
+ for other in self.accelerators[name]:
+ if not other.languages or not node.languages:
+ ret.append(other)
+ continue
+
+ for lang in other.languages:
+ if lang in node.languages:
+ ret.append(other)
+ continue
+
+ return ret
+
+ def set_accelerator(self, keyval, mod):
+ # Check whether accelerator already exists
+ self.remove_accelerator(self.current_node)
+
+ name = gtk.accelerator_name(keyval, mod)
+
+ if name == '':
+ self.current_node.shorcut = None
+ self.save_current_tool()
+ return True
+
+ col = self.accelerator_collision(name, self.current_node)
+
+ if col:
+ dialog = gtk.MessageDialog(self.dialog,
+ gtk.DIALOG_MODAL,
+ gtk.MESSAGE_ERROR,
+ gtk.BUTTONS_OK,
+ _('This accelerator is already bound to %s') % (', '.join(map(lambda x: x.name, col)),))
+
+ dialog.run()
+ dialog.destroy()
+
+ self.add_accelerator(self.current_node)
+ return False
+
+ self.current_node.shortcut = name
+ self.add_accelerator(self.current_node)
+ self.save_current_tool()
+
+ return True
+
+ def on_accelerator_key_press(self, entry, event):
+ mask = event.state & gtk.accelerator_get_default_mod_mask()
+
+ if event.keyval == gtk.keysyms.Escape:
+ entry.set_text(default(self.current_node.shortcut, ''))
+ self['commands'].grab_focus()
+ return True
+ elif event.keyval == gtk.keysyms.Delete \
+ or event.keyval == gtk.keysyms.BackSpace:
+ entry.set_text('')
+ self.remove_accelerator(self.current_node)
+ self.current_node.shortcut = None
+ self['commands'].grab_focus()
+ return True
+ elif event.keyval in range(gtk.keysyms.F1, gtk.keysyms.F12 + 1):
+ # New accelerator
+ if self.set_accelerator(event.keyval, mask):
+ entry.set_text(default(self.current_node.shortcut, ''))
+ self['commands'].grab_focus()
+
+ # Capture all `normal characters`
+ return True
+ elif gtk.gdk.keyval_to_unicode(event.keyval):
+ if mask:
+ # New accelerator
+ if self.set_accelerator(event.keyval, mask):
+ entry.set_text(default(self.current_node.shortcut, ''))
+ self['commands'].grab_focus()
+ # Capture all `normal characters`
+ return True
+ else:
+ return False
+
+ def on_accelerator_focus_in(self, entry, event):
+ if self.current_node is None:
+ return
+ if self.current_node.shortcut:
+ entry.set_text(_('Type a new accelerator, or press Backspace to clear'))
+ else:
+ entry.set_text(_('Type a new accelerator'))
+
+ def on_accelerator_focus_out(self, entry, event):
+ if self.current_node is not None:
+ entry.set_text(default(self.current_node.shortcut, ''))
+ self.tool_changed(self.current_node)
+
+ def on_tool_manager_dialog_response(self, dialog, response):
+ if response == gtk.RESPONSE_HELP:
+ gedit.help_display(self.dialog, 'gedit', 'gedit-external-tools-plugin')
+ return
+
+ self.on_tool_manager_dialog_focus_out(dialog, None)
+
+ self.dialog.destroy()
+ self.dialog = None
+ self.tools = None
+
+ def on_tool_manager_dialog_focus_out(self, dialog, event):
+ self.save_current_tool()
+
+ for window in gedit.app_get_default().get_windows():
+ helper = window.get_data("ExternalToolsPluginWindowData")
+ helper.menu.update()
+
+ def get_cell_data_cb(self, column, cell, model, piter):
+ tool = model.get_value(piter, self.TOOL_COLUMN)
+
+ if tool == None or not isinstance(tool, Tool):
+ if tool == None:
+ label = _('All Languages')
+ elif not isinstance(tool, gsv.Language):
+ label = _('Plain Text')
+ else:
+ label = tool.get_name()
+
+ markup = saxutils.escape(label)
+ editable = False
+ else:
+ escaped = saxutils.escape(tool.name)
+
+ if tool.shortcut:
+ markup = '%s (<b>%s</b>)' % (escaped, saxutils.escape(tool.shortcut))
+ else:
+ markup = escaped
+
+ editable = True
+
+ cell.set_properties(markup=markup, editable=editable)
+
+ def tool_in_language(self, tool, lang):
+ if not lang in self._languages:
+ return False
+
+ ref = self._languages[lang]
+ parent = ref.get_path()
+
+ for row in self._tool_rows[tool]:
+ path = row.get_path()
+
+ if path[0] == parent[0]:
+ return True
+
+ return False
+
+ def update_languages(self, popup):
+ self.current_node.languages = popup.languages()
+ self.fill_languages_button()
+
+ piter, node = self.get_selected_tool()
+ ret = None
+
+ if node:
+ ref = gtk.TreeRowReference(self.model, self.model.get_path(piter))
+
+ # Update languages, make sure to inhibit selection change stuff
+ self.view.get_selection().handler_block(self.selection_changed_id)
+
+ # Remove all rows that are no longer
+ for row in list(self._tool_rows[self.current_node]):
+ piter = self.model.get_iter(row.get_path())
+ language = self.language_id_from_iter(piter)
+
+ if (not language and not self.current_node.languages) or \
+ (language in self.current_node.languages):
+ continue
+
+ # Remove from language
+ self.model.remove(piter)
+ self._tool_rows[self.current_node].remove(row)
+
+ # If language is empty, remove it
+ parent = self.model.get_iter(self._languages[language].get_path())
+
+ if not self.model.iter_has_child(parent):
+ self.model.remove(parent)
+ del self._languages[language]
+
+ # Now, add for any that are new
+ manager = gsv.LanguageManager()
+
+ for lang in self.current_node.languages:
+ if not self.tool_in_language(self.current_node, lang):
+ l = manager.get_language(lang)
+
+ if not l:
+ l = 'plain'
+
+ self.add_tool_to_language(self.current_node, l)
+
+ if not self.current_node.languages and not self.tool_in_language(self.current_node, None):
+ self.add_tool_to_language(self.current_node, None)
+
+ # Check if we can still keep the current
+ if not ref or not ref.valid():
+ # Change selection to first language
+ path = self._tool_rows[self.current_node][0].get_path()
+ piter = self.model.get_iter(path)
+ parent = self.model.iter_parent(piter)
+
+ # Expand parent, select child and scroll to it
+ self.view.expand_row(self.model.get_path(parent), False)
+ self.view.get_selection().select_path(path)
+ self.view.set_cursor(path, self.view.get_column(self.TOOL_COLUMN), False)
+
+ self.view.get_selection().handler_unblock(self.selection_changed_id)
+
+ def on_languages_button_clicked(self, button):
+ popup = LanguagesPopup(self.current_node.languages)
+ popup.set_transient_for(self.dialog)
+
+ origin = button.window.get_origin()
+ popup.move(origin[0], origin[1] - popup.allocation.height)
+
+ popup.connect('destroy', self.update_languages)
+
+# ex:et:ts=4:
diff --git a/plugins/externaltools/tools/outputpanel.py b/plugins/externaltools/tools/outputpanel.py
new file mode 100755
index 00000000..a30aad72
--- /dev/null
+++ b/plugins/externaltools/tools/outputpanel.py
@@ -0,0 +1,224 @@
+# -*- coding: utf-8 -*-
+# Gedit External Tools plugin
+# Copyright (C) 2005-2006 Steve Frécinaux <[email protected]>
+# Copyright (C) 2010 Per Arneng <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+__all__ = ('OutputPanel', 'UniqueById')
+
+import gtk, gedit
+import pango
+import gobject
+import os
+from weakref import WeakKeyDictionary
+from capture import *
+from gtk import gdk
+import re
+import gio
+import linkparsing
+import filelookup
+
+class UniqueById:
+ __shared_state = WeakKeyDictionary()
+
+ def __init__(self, i):
+ if i in self.__class__.__shared_state:
+ self.__dict__ = self.__class__.__shared_state[i]
+ return True
+ else:
+ self.__class__.__shared_state[i] = self.__dict__
+ return False
+
+ def states(self):
+ return self.__class__.__shared_state
+
+class OutputPanel(UniqueById):
+ def __init__(self, datadir, window):
+ if UniqueById.__init__(self, window):
+ return
+
+ callbacks = {
+ 'on_stop_clicked' : self.on_stop_clicked,
+ 'on_view_visibility_notify_event': self.on_view_visibility_notify_event,
+ 'on_view_motion_notify_event': self.on_view_motion_notify_event,
+ 'on_view_button_press_event': self.on_view_button_press_event
+ }
+
+ self.window = window
+ self.ui = gtk.Builder()
+ self.ui.add_from_file(os.path.join(datadir, 'ui', 'outputpanel.ui'))
+ self.ui.connect_signals(callbacks)
+
+ self.panel = self["output-panel"]
+ self['view'].modify_font(pango.FontDescription('Monospace'))
+
+ buffer = self['view'].get_buffer()
+
+ self.normal_tag = buffer.create_tag('normal')
+
+ self.error_tag = buffer.create_tag('error')
+ self.error_tag.set_property('foreground', 'red')
+
+ self.italic_tag = buffer.create_tag('italic')
+ self.italic_tag.set_property('style', pango.STYLE_OBLIQUE)
+
+ self.bold_tag = buffer.create_tag('bold')
+ self.bold_tag.set_property('weight', pango.WEIGHT_BOLD)
+
+ self.invalid_link_tag = buffer.create_tag('invalid_link')
+
+ self.link_tag = buffer.create_tag('link')
+ self.link_tag.set_property('underline', pango.UNDERLINE_SINGLE)
+
+ self.link_cursor = gdk.Cursor(gdk.HAND2)
+ self.normal_cursor = gdk.Cursor(gdk.XTERM)
+
+ self.process = None
+
+ self.links = []
+
+ self.link_parser = linkparsing.LinkParser()
+ self.file_lookup = filelookup.FileLookup()
+
+ def set_process(self, process):
+ self.process = process
+
+ def __getitem__(self, key):
+ # Convenience function to get an object from its name
+ return self.ui.get_object(key)
+
+ def on_stop_clicked(self, widget, *args):
+ if self.process is not None:
+ self.write("\n" + _('Stopped.') + "\n",
+ self.italic_tag)
+ self.process.stop(-1)
+
+ def scroll_to_end(self):
+ iter = self['view'].get_buffer().get_end_iter()
+ self['view'].scroll_to_iter(iter, 0.0)
+ return False # don't requeue this handler
+
+ def clear(self):
+ self['view'].get_buffer().set_text("")
+ self.links = []
+
+ def visible(self):
+ panel = self.window.get_bottom_panel()
+ return panel.props.visible and panel.item_is_active(self.panel)
+
+ def write(self, text, tag = None):
+ buffer = self['view'].get_buffer()
+
+ end_iter = buffer.get_end_iter()
+ insert = buffer.create_mark(None, end_iter, True)
+
+ if tag is None:
+ buffer.insert(end_iter, text)
+ else:
+ buffer.insert_with_tags(end_iter, text, tag)
+
+ # find all links and apply the appropriate tag for them
+ links = self.link_parser.parse(text)
+ for lnk in links:
+
+ insert_iter = buffer.get_iter_at_mark(insert)
+ lnk.start = insert_iter.get_offset() + lnk.start
+ lnk.end = insert_iter.get_offset() + lnk.end
+
+ start_iter = buffer.get_iter_at_offset(lnk.start)
+ end_iter = buffer.get_iter_at_offset(lnk.end)
+
+ tag = None
+
+ # if the link points to an existing file then it is a valid link
+ if self.file_lookup.lookup(lnk.path) is not None:
+ self.links.append(lnk)
+ tag = self.link_tag
+ else:
+ tag = self.invalid_link_tag
+
+ buffer.apply_tag(tag, start_iter, end_iter)
+
+ buffer.delete_mark(insert)
+ gobject.idle_add(self.scroll_to_end)
+
+ def show(self):
+ panel = self.window.get_bottom_panel()
+ panel.show()
+ panel.activate_item(self.panel)
+
+ def update_cursor_style(self, view, x, y):
+ if self.get_link_at_location(view, x, y) is not None:
+ cursor = self.link_cursor
+ else:
+ cursor = self.normal_cursor
+
+ view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor)
+
+ def on_view_motion_notify_event(self, view, event):
+ if event.window == view.get_window(gtk.TEXT_WINDOW_TEXT):
+ self.update_cursor_style(view, int(event.x), int(event.y))
+
+ return False
+
+ def on_view_visibility_notify_event(self, view, event):
+ if event.window == view.get_window(gtk.TEXT_WINDOW_TEXT):
+ x, y, m = event.window.get_pointer()
+ self.update_cursor_style(view, x, y)
+
+ return False
+
+ def idle_grab_focus(self):
+ self.window.get_active_view().grab_focus()
+ return False
+
+ def get_link_at_location(self, view, x, y):
+ """
+ Get the link under a specified x,y coordinate. If no link exists then
+ None is returned.
+ """
+
+ # get the offset within the buffer from the x,y coordinates
+ buff_x, buff_y = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,
+ x, y)
+ iter_at_xy = view.get_iter_at_location(buff_x, buff_y)
+ offset = iter_at_xy.get_offset()
+
+ # find the first link that contains the offset
+ for lnk in self.links:
+ if offset >= lnk.start and offset <= lnk.end:
+ return lnk
+
+ # no link was found at x,y
+ return None
+
+ def on_view_button_press_event(self, view, event):
+ if event.button != 1 or event.type != gdk.BUTTON_PRESS or \
+ event.window != view.get_window(gtk.TEXT_WINDOW_TEXT):
+ return False
+
+ link = self.get_link_at_location(view, int(event.x), int(event.y))
+ if link is None:
+ return False
+
+ gfile = self.file_lookup.lookup(link.path)
+
+ if gfile:
+ gedit.commands.load_uri(self.window, gfile.get_uri(), None,
+ link.line_nr)
+ gobject.idle_add(self.idle_grab_focus)
+
+# ex:ts=4:et:
diff --git a/plugins/externaltools/tools/outputpanel.ui b/plugins/externaltools/tools/outputpanel.ui
new file mode 100755
index 00000000..f0281792
--- /dev/null
+++ b/plugins/externaltools/tools/outputpanel.ui
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+<!-- Generated with glade3
+ Version: 2.91.3
+ Date: Sat Nov 18 13:58:59 2006
+ User: sf
+ Host: antea
+-->
+<interface>
+ <object class="GtkHBox" id="output-panel">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <child>
+ <object class="GtkTextView" id="view">
+ <property name="visible">True</property>
+ <property name="editable">False</property>
+ <property name="wrap_mode">GTK_WRAP_WORD</property>
+ <property name="cursor_visible">False</property>
+ <property name="accepts_tab">False</property>
+ <signal name="button_press_event" handler="on_view_button_press_event"/>
+ <signal name="motion_notify_event" handler="on_view_motion_notify_event"/>
+ <signal name="visibility_notify_event" handler="on_view_visibility_notify_event"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkVButtonBox" id="vbuttonbox1">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="stop">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="label">gtk-stop</property>
+ <property name="use_stock">True</property>
+ <signal handler="on_stop_clicked" name="clicked"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/externaltools/tools/tools.ui b/plugins/externaltools/tools/tools.ui
new file mode 100755
index 00000000..dff7d192
--- /dev/null
+++ b/plugins/externaltools/tools/tools.ui
@@ -0,0 +1,606 @@
+<?xml version="1.0"?>
+<interface>
+ <object class="GtkListStore" id="model_save_files">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Nothing</col>
+ <col id="1">nothing</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current document</col>
+ <col id="1">document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">All documents</col>
+ <col id="1">all</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model_input">
+ <columns>
+ <column type="gchararray"/>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Nothing</col>
+ <col id="1">nothing</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current document</col>
+ <col id="1">document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current selection</col>
+ <col id="1">selection</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current selection (default to document)</col>
+ <col id="1">selection-document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current line</col>
+ <col id="1">line</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Current word</col>
+ <col id="1">word</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model_output">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Nothing</col>
+ <col id="1">nothing</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Display in bottom pane</col>
+ <col id="1">output-panel</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Create new document</col>
+ <col id="1">new-document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Append to current document</col>
+ <col id="1">append-document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Replace current document</col>
+ <col id="1">replace-document</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Replace current selection</col>
+ <col id="1">replace-selection</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Insert at cursor position</col>
+ <col id="1">insert</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="model_applicability">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ <!-- column-name gchararray1 -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">All documents</col>
+ <col id="1">all</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">All documents except untitled ones</col>
+ <col id="1">titled</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Local files only</col>
+ <col id="1">local</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Remote files only</col>
+ <col id="1">remote</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Untitled documents only</col>
+ <col id="1">untitled</col>
+ </row>
+ </data>
+ </object>
+ <object class="GeditDocument" id="commands_buffer">
+ <property name="highlight-matching-brackets">True</property>
+ </object>
+ <object class="GtkDialog" id="tool-manager-dialog">
+ <property name="title" translatable="yes">External Tools Manager</property>
+ <property name="default_width">750</property>
+ <property name="default_height">500</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="has_separator">False</property>
+ <signal name="focus_out_event" handler="on_tool_manager_dialog_focus_out"/>
+ <signal name="response" handler="on_tool_manager_dialog_response"/>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="tool-manager-dialog-vbox">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkHPaned" id="paned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">6</property>
+ <property name="position">275</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Tools:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">view</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="reorderable">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="new-tool-button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <signal name="clicked" handler="on_new_tool_button_clicked"/>
+ <child>
+ <object class="GtkImage" id="new-tool-image">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon-size">4</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="revert-tool-button">
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <signal name="clicked" handler="on_remove_tool_button_clicked"/>
+ <child>
+ <object class="GtkImage" id="revert-tool-image">
+ <property name="visible">True</property>
+ <property name="stock">gtk-revert-to-saved</property>
+ <property name="icon-size">4</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="remove-tool-button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <signal name="clicked" handler="on_remove_tool_button_clicked"/>
+ <child>
+ <object class="GtkImage" id="remove-tool-image">
+ <property name="visible">True</property>
+ <property name="stock">gtk-delete</property>
+ <property name="icon-size">4</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="title">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="label" translatable="yes">_Edit:</property>
+ <property name="mnemonic_widget">commands</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox7">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="tool-table">
+ <property name="visible">True</property>
+ <property name="n_rows">6</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkEntry" id="accelerator">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <signal name="key_press_event" handler="on_accelerator_key_press"/>
+ <signal name="focus_out_event" handler="on_accelerator_focus_out"/>
+ <signal name="focus_in_event" handler="on_accelerator_focus_in"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkComboBox" id="applicability">
+ <property name="visible">True</property>
+ <property name="model">model_applicability</property>
+ <child>
+ <object class="GtkCellRendererText" id="applicability_renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEventBox" id="languages_event_box">
+ <property name="visible">True</property>
+ <property name="visible-window">True</property>
+ <child>
+ <object class="GtkButton" id="languages_button">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="on_languages_button_clicked"/>
+ <child>
+ <object class="GtkLabel" id="languages_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">All Languages</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_MIDDLE</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="output">
+ <property name="visible">True</property>
+ <property name="model">model_output</property>
+ <child>
+ <object class="GtkCellRendererText" id="output_renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="input">
+ <property name="visible">True</property>
+ <property name="model">model_input</property>
+ <child>
+ <object class="GtkCellRendererText" id="input_renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="save-files">
+ <property name="model">model_save_files</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+ <property name="y_options">GTK_FILL</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Applicability:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">applicability</property>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Output:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">output</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Input:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">input</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Save:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">save-files</property>
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Shortcut Key:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">accelerator</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">GTK_FILL</property>
+ <property name="y_options"></property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GeditView" id="commands">
+ <property name="buffer">commands_buffer</property>
+ <property name="visible">True</property>
+ <property name="auto-indent">True</property>
+ <property name="insert-spaces-instead-of-tabs">False</property>
+ <property name="smart-home-end">GTK_SOURCE_SMART_HOME_END_AFTER</property>
+ <property name="tab-width">2</property>
+ <property name="highlight-current-line">True</property>
+ <property name="show-right-margin">False</property>
+ <property name="show-line-numbers">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label">gtk-help</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">button1</action-widget>
+ <action-widget response="-7">button2</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/filebrowser/Makefile.am b/plugins/filebrowser/Makefile.am
new file mode 100755
index 00000000..22301d5b
--- /dev/null
+++ b/plugins/filebrowser/Makefile.am
@@ -0,0 +1,104 @@
+# filebrowser
+
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+BUILT_SOURCES = \
+ gedit-file-browser-enum-types.h \
+ gedit-file-browser-enum-types.c \
+ gedit-file-browser-marshal.h \
+ gedit-file-browser-marshal.c
+
+plugin_LTLIBRARIES = libfilebrowser.la
+
+NOINST_H_FILES = \
+ gedit-file-bookmarks-store.h \
+ gedit-file-browser-store.h \
+ gedit-file-browser-view.h \
+ gedit-file-browser-widget.h \
+ gedit-file-browser-error.h \
+ gedit-file-browser-utils.h \
+ gedit-file-browser-plugin.h \
+ gedit-file-browser-messages.h
+
+libfilebrowser_la_SOURCES = \
+ $(BUILT_SOURCES) \
+ gedit-file-bookmarks-store.c \
+ gedit-file-browser-store.c \
+ gedit-file-browser-view.c \
+ gedit-file-browser-widget.c \
+ gedit-file-browser-utils.c \
+ gedit-file-browser-plugin.c \
+ gedit-file-browser-messages.c \
+ $(NOINST_H_FILES)
+
+libfilebrowser_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libfilebrowser_la_LIBADD = $(GEDIT_LIBS)
+
+# UI files (if you use ui for your plugin, list those files here)
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/filebrowser
+ui_DATA = gedit-file-browser-widget-ui.xml
+
+plugin_in_files = filebrowser.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+gedit-file-browser-enum-types.h: gedit-file-browser-enum-types.h.template $(NOINST_H_FILES) $(GLIB_MKENUMS)
+ (cd $(srcdir) && $(GLIB_MKENUMS) --template gedit-file-browser-enum-types.h.template $(NOINST_H_FILES)) > $@
+
+gedit-file-browser-enum-types.c: gedit-file-browser-enum-types.c.template gedit-file-browser-enum-register.c.template $(NOINST_H_FILES) $(GLIB_MKENUMS)
+ $(AM_V_GEN) (cd $(srcdir) && \
+ $(GLIB_MKENUMS) --template gedit-file-browser-enum-types.c.template $(NOINST_H_FILES) && \
+ $(GLIB_MKENUMS) --template gedit-file-browser-enum-register.c.template $(NOINST_H_FILES)) > $@
+
+gedit-file-browser-marshal.h: gedit-file-browser-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN) $(GLIB_GENMARSHAL) $< --header --prefix=gedit_file_browser_marshal > $@
+
+gedit-file-browser-marshal.c: gedit-file-browser-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN) echo "#include \"gedit-file-browser-marshal.h\"" > $@ && \
+ $(GLIB_GENMARSHAL) $< --body --prefix=gedit_file_browser_marshal >> $@
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+schemasdir = $(MATECONF_SCHEMA_FILE_DIR)
+schemas_in_files = gedit-file-browser.schemas.in
+schemas_DATA = $(schemas_in_files:.schemas.in=.schemas)
+@INTLTOOL_SCHEMAS_RULE@
+
+if MATECONF_SCHEMAS_INSTALL
+install-data-local:
+ if test -z "$(DESTDIR)" ; then \
+ for p in $(schemas_DATA) ; do \
+ MATECONF_CONFIG_SOURCE=$(MATECONF_SCHEMA_CONFIG_SOURCE) $(MATECONFTOOL) --makefile-install-rule $(top_builddir)/plugins/filebrowser/$$p ; \
+ done \
+ fi
+else
+install-data-local:
+endif
+
+
+EXTRA_DIST = \
+ $(ui_DATA) \
+ $(plugin_in_files) \
+ $(schemas_in_files) \
+ gedit-file-browser-enum-types.h.template \
+ gedit-file-browser-enum-types.c.template \
+ gedit-file-browser-enum-register.c.template \
+ gedit-file-browser-marshal.list
+
+CLEANFILES = \
+ $(plugin_DATA) \
+ $(schemas_DATA) \
+ $(BUILT_SOURCES)
+
+DISTCLEANFILES = \
+ $(plugin_DATA) \
+ $(schemas_DATA) \
+ $(BUILT_SOURCES)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in b/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in
new file mode 100755
index 00000000..808816c5
--- /dev/null
+++ b/plugins/filebrowser/filebrowser.gedit-plugin.desktop.in
@@ -0,0 +1,10 @@
+[Gedit Plugin]
+Loader=C
+Module=filebrowser
+IAge=2
+_Name=File Browser Pane
+_Description=Easy file access from the side pane
+Icon=system-file-manager
+Authors=Jesse van den Kieboom <[email protected]>
+Copyright=Copyright © 2006 Jesse van den Kieboom
+Website=http://www.gedit.org
diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.c b/plugins/filebrowser/gedit-file-bookmarks-store.c
new file mode 100755
index 00000000..86e7f0c8
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-bookmarks-store.c
@@ -0,0 +1,879 @@
+/*
+ * gedit-file-bookmarks-store.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-plugin.h>
+
+#include "gedit-file-bookmarks-store.h"
+#include "gedit-file-browser-utils.h"
+
+#define GEDIT_FILE_BOOKMARKS_STORE_GET_PRIVATE(object)( \
+ G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_FILE_BOOKMARKS_STORE, \
+ GeditFileBookmarksStorePrivate))
+
+struct _GeditFileBookmarksStorePrivate
+{
+ GVolumeMonitor * volume_monitor;
+ GFileMonitor * bookmarks_monitor;
+};
+
+static void remove_node (GtkTreeModel * model,
+ GtkTreeIter * iter);
+
+static void on_fs_changed (GVolumeMonitor *monitor,
+ GObject *object,
+ GeditFileBookmarksStore *model);
+
+static void on_bookmarks_file_changed (GFileMonitor * monitor,
+ GFile * file,
+ GFile * other_file,
+ GFileMonitorEvent event_type,
+ GeditFileBookmarksStore * model);
+static gboolean find_with_flags (GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gpointer obj,
+ guint flags,
+ guint notflags);
+
+GEDIT_PLUGIN_DEFINE_TYPE(GeditFileBookmarksStore, gedit_file_bookmarks_store, GTK_TYPE_TREE_STORE)
+
+static void
+gedit_file_bookmarks_store_dispose (GObject * object)
+{
+ GeditFileBookmarksStore *obj = GEDIT_FILE_BOOKMARKS_STORE (object);
+
+ if (obj->priv->volume_monitor != NULL) {
+ g_signal_handlers_disconnect_by_func (obj->priv->volume_monitor,
+ on_fs_changed,
+ obj);
+
+ g_object_unref (obj->priv->volume_monitor);
+ obj->priv->volume_monitor = NULL;
+ }
+
+ if (obj->priv->bookmarks_monitor != NULL) {
+ g_object_unref (obj->priv->bookmarks_monitor);
+ obj->priv->bookmarks_monitor = NULL;
+ }
+
+ G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->dispose (object);
+}
+
+static void
+gedit_file_bookmarks_store_finalize (GObject * object)
+{
+ G_OBJECT_CLASS (gedit_file_bookmarks_store_parent_class)->finalize (object);
+}
+
+static void
+gedit_file_bookmarks_store_class_init (GeditFileBookmarksStoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gedit_file_bookmarks_store_dispose;
+ object_class->finalize = gedit_file_bookmarks_store_finalize;
+
+ g_type_class_add_private (object_class, sizeof (GeditFileBookmarksStorePrivate));
+}
+
+static void
+gedit_file_bookmarks_store_init (GeditFileBookmarksStore * obj)
+{
+ obj->priv = GEDIT_FILE_BOOKMARKS_STORE_GET_PRIVATE (obj);
+}
+
+/* Private */
+static void
+add_node (GeditFileBookmarksStore *model,
+ GdkPixbuf *pixbuf,
+ const gchar *name,
+ GObject *obj,
+ guint flags,
+ GtkTreeIter *iter)
+{
+ GtkTreeIter newiter;
+
+ gtk_tree_store_append (GTK_TREE_STORE (model), &newiter, NULL);
+
+ gtk_tree_store_set (GTK_TREE_STORE (model), &newiter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON, pixbuf,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, name,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT, obj,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, flags,
+ -1);
+
+ if (iter != NULL)
+ *iter = newiter;
+}
+
+static gboolean
+add_file (GeditFileBookmarksStore *model,
+ GFile *file,
+ const gchar *name,
+ guint flags,
+ GtkTreeIter *iter)
+{
+ GdkPixbuf *pixbuf = NULL;
+ gboolean native;
+ gchar *newname;
+
+ native = g_file_is_native (file);
+
+ if (native && !g_file_query_exists (file, NULL)) {
+ return FALSE;
+ }
+
+ if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_HOME)
+ pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("user-home", GTK_ICON_SIZE_MENU);
+ else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP)
+ pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("user-desktop", GTK_ICON_SIZE_MENU);
+ else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT)
+ pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("drive-harddisk", GTK_ICON_SIZE_MENU);
+
+ if (pixbuf == NULL) {
+ /* getting the icon is a sync get_info call, so we just do it for local files */
+ if (native) {
+ pixbuf = gedit_file_browser_utils_pixbuf_from_file (file, GTK_ICON_SIZE_MENU);
+ } else {
+ pixbuf = gedit_file_browser_utils_pixbuf_from_theme ("folder", GTK_ICON_SIZE_MENU);
+ }
+ }
+
+ if (name == NULL) {
+ newname = gedit_file_browser_utils_file_basename (file);
+ } else {
+ newname = g_strdup (name);
+ }
+
+ add_node (model, pixbuf, newname, G_OBJECT (file), flags, iter);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+ g_free (newname);
+
+ return TRUE;
+}
+
+static void
+check_mount_separator (GeditFileBookmarksStore * model, guint flags,
+ gboolean added)
+{
+ GtkTreeIter iter;
+ gboolean found;
+
+ found =
+ find_with_flags (GTK_TREE_MODEL (model), &iter, NULL,
+ flags |
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR, 0);
+
+ if (added && !found) {
+ /* Add the separator */
+ add_node (model, NULL, NULL, NULL,
+ flags | GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR,
+ NULL);
+ } else if (!added && found) {
+ remove_node (GTK_TREE_MODEL (model), &iter);
+ }
+}
+
+static void
+init_special_directories (GeditFileBookmarksStore * model)
+{
+ gchar const *path;
+ GFile * file;
+
+ path = g_get_home_dir ();
+ if (path != NULL)
+ {
+ file = g_file_new_for_path (path);
+ add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_HOME |
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL);
+ g_object_unref (file);
+ }
+
+ path = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
+ if (path != NULL)
+ {
+ file = g_file_new_for_path (path);
+ add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP |
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL);
+ g_object_unref (file);
+ }
+
+ path = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
+ if (path != NULL)
+ {
+ file = g_file_new_for_path (path);
+ add_file (model, file, NULL, GEDIT_FILE_BOOKMARKS_STORE_IS_DOCUMENTS |
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR, NULL);
+ g_object_unref (file);
+ }
+
+ file = g_file_new_for_uri ("file:///");
+ add_file (model, file, _("File System"), GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, NULL);
+ g_object_unref (file);
+
+ check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT, TRUE);
+}
+
+static void
+get_fs_properties (gpointer fs,
+ gchar **name,
+ GdkPixbuf **pixbuf,
+ guint *flags)
+{
+ GIcon *icon = NULL;
+
+ *flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS;
+ *name = NULL;
+ *pixbuf = NULL;
+
+ if (G_IS_DRIVE (fs))
+ {
+ icon = g_drive_get_icon (G_DRIVE (fs));
+ *name = g_drive_get_name (G_DRIVE (fs));
+
+ *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE;
+ }
+ else if (G_IS_VOLUME (fs))
+ {
+ icon = g_volume_get_icon (G_VOLUME (fs));
+ *name = g_volume_get_name (G_VOLUME (fs));
+
+ *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME;
+ }
+ else if (G_IS_MOUNT (fs))
+ {
+ icon = g_mount_get_icon (G_MOUNT (fs));
+ *name = g_mount_get_name (G_MOUNT (fs));
+
+ *flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT;
+ }
+
+ if (icon)
+ {
+ *pixbuf = gedit_file_browser_utils_pixbuf_from_icon (icon, GTK_ICON_SIZE_MENU);
+ g_object_unref (icon);
+ }
+}
+
+
+static void
+add_fs (GeditFileBookmarksStore *model,
+ gpointer fs,
+ guint flags,
+ GtkTreeIter *iter)
+{
+ gchar *name;
+ GdkPixbuf *pixbuf;
+ guint fsflags;
+
+ get_fs_properties (fs, &name, &pixbuf, &fsflags);
+ add_node (model, pixbuf, name, fs, flags | fsflags, iter);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+ g_free (name);
+ check_mount_separator (model, GEDIT_FILE_BOOKMARKS_STORE_IS_FS, TRUE);
+}
+
+static void
+process_volume_cb (GVolume *volume,
+ GeditFileBookmarksStore *model)
+{
+ GMount *mount;
+ guint flags = GEDIT_FILE_BOOKMARKS_STORE_NONE;
+ mount = g_volume_get_mount (volume);
+
+ /* CHECK: should we use the LOCAL/REMOTE thing still? */
+ if (mount)
+ {
+ /* Show mounted volume */
+ add_fs (model, mount, flags, NULL);
+ g_object_unref (mount);
+ }
+ else if (g_volume_can_mount (volume))
+ {
+ /* We also show the unmounted volume here so users can
+ mount it if they want to access it */
+ add_fs (model, volume, flags, NULL);
+ }
+}
+
+static void
+process_drive_novolumes (GeditFileBookmarksStore *model,
+ GDrive *drive)
+{
+ if (g_drive_is_media_removable (drive) &&
+ !g_drive_is_media_check_automatic (drive) &&
+ g_drive_can_poll_for_media (drive))
+ {
+ /* This can be the case for floppy drives or other
+ drives where media detection fails. We show the
+ drive and poll for media when the user activates
+ it */
+ add_fs (model, drive, GEDIT_FILE_BOOKMARKS_STORE_NONE, NULL);
+ }
+}
+
+static void
+process_drive_cb (GDrive *drive,
+ GeditFileBookmarksStore *model)
+{
+ GList *volumes;
+
+ volumes = g_drive_get_volumes (drive);
+
+ if (volumes)
+ {
+ /* Add all volumes for the drive */
+ g_list_foreach (volumes, (GFunc)process_volume_cb, model);
+ g_list_free (volumes);
+ }
+ else
+ {
+ process_drive_novolumes (model, drive);
+ }
+}
+
+static void
+init_drives (GeditFileBookmarksStore *model)
+{
+ GList *drives;
+
+ drives = g_volume_monitor_get_connected_drives (model->priv->volume_monitor);
+
+ g_list_foreach (drives, (GFunc)process_drive_cb, model);
+ g_list_foreach (drives, (GFunc)g_object_unref, NULL);
+ g_list_free (drives);
+}
+
+static void
+process_volume_nodrive_cb (GVolume *volume,
+ GeditFileBookmarksStore *model)
+{
+ GDrive *drive;
+
+ drive = g_volume_get_drive (volume);
+
+ if (drive)
+ {
+ g_object_unref (drive);
+ return;
+ }
+
+ process_volume_cb (volume, model);
+}
+
+static void
+init_volumes (GeditFileBookmarksStore *model)
+{
+ GList *volumes;
+
+ volumes = g_volume_monitor_get_volumes (model->priv->volume_monitor);
+
+ g_list_foreach (volumes, (GFunc)process_volume_nodrive_cb, model);
+ g_list_foreach (volumes, (GFunc)g_object_unref, NULL);
+ g_list_free (volumes);
+}
+
+static void
+process_mount_novolume_cb (GMount *mount,
+ GeditFileBookmarksStore *model)
+{
+ GVolume *volume;
+
+ volume = g_mount_get_volume (mount);
+
+ if (volume)
+ {
+ g_object_unref (volume);
+ }
+ else if (!g_mount_is_shadowed (mount))
+ {
+ /* Add the mount */
+ add_fs (model, mount, GEDIT_FILE_BOOKMARKS_STORE_NONE, NULL);
+ }
+}
+
+static void
+init_mounts (GeditFileBookmarksStore *model)
+{
+ GList *mounts;
+
+ mounts = g_volume_monitor_get_mounts (model->priv->volume_monitor);
+
+ g_list_foreach (mounts, (GFunc)process_mount_novolume_cb, model);
+ g_list_foreach (mounts, (GFunc)g_object_unref, NULL);
+ g_list_free (mounts);
+}
+
+static void
+init_fs (GeditFileBookmarksStore * model)
+{
+ if (model->priv->volume_monitor == NULL) {
+ const gchar **ptr;
+ const gchar *signals[] = {
+ "drive-connected", "drive-changed", "drive-disconnected",
+ "volume-added", "volume-removed", "volume-changed",
+ "mount-added", "mount-removed", "mount-changed",
+ NULL
+ };
+
+ model->priv->volume_monitor = g_volume_monitor_get ();
+
+ /* Connect signals */
+ for (ptr = signals; *ptr; ptr++)
+ {
+ g_signal_connect (model->priv->volume_monitor,
+ *ptr,
+ G_CALLBACK (on_fs_changed), model);
+ }
+ }
+
+ /* First go through all the connected drives */
+ init_drives (model);
+
+ /* Then add all volumes, not associated with a drive */
+ init_volumes (model);
+
+ /* Then finally add all mounts that have no volume */
+ init_mounts (model);
+}
+
+static gboolean
+add_bookmark (GeditFileBookmarksStore * model,
+ gchar const * name,
+ gchar const * uri)
+{
+ GFile * file;
+ gboolean ret;
+ guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK;
+ GtkTreeIter iter;
+
+ file = g_file_new_for_uri (uri);
+
+ if (g_file_is_native (file)) {
+ flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_LOCAL_BOOKMARK;
+ } else {
+ flags |= GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK;
+ }
+
+ ret = add_file (model, file, name, flags, &iter);
+
+ g_object_unref (file);
+
+ return ret;
+}
+
+static void
+init_bookmarks (GeditFileBookmarksStore * model)
+{
+ gchar *bookmarks;
+ GError *error = NULL;
+ gchar *contents;
+ gchar **lines;
+ gchar **line;
+ gboolean added = FALSE;
+
+ /* Read the bookmarks file */
+ bookmarks = g_build_filename (g_get_home_dir (),
+ ".gtk-bookmarks",
+ NULL);
+
+ if (g_file_get_contents (bookmarks, &contents, NULL, &error)) {
+ lines = g_strsplit (contents, "\n", 0);
+
+ for (line = lines; *line; ++line) {
+ if (**line) {
+ gchar *pos;
+ gchar *name;
+
+ /* CHECK: is this really utf8? */
+ pos = g_utf8_strchr (*line, -1, ' ');
+
+ if (pos != NULL) {
+ *pos = '\0';
+ name = pos + 1;
+ } else {
+ name = NULL;
+ }
+
+ /* the bookmarks file should contain valid
+ * URIs, but paranoia is good */
+ if (gedit_utils_is_valid_uri (*line)) {
+ added |= add_bookmark (model, name, *line);
+ }
+ }
+ }
+
+ g_strfreev (lines);
+ g_free (contents);
+
+ /* Add a watch */
+ if (model->priv->bookmarks_monitor == NULL) {
+ GFile * file;
+
+ file = g_file_new_for_path (bookmarks);
+ model->priv->bookmarks_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL);
+ g_object_unref (file);
+
+ g_signal_connect (model->priv->bookmarks_monitor,
+ "changed",
+ (GCallback)on_bookmarks_file_changed,
+ model);
+ }
+ } else {
+ /* The bookmarks file doesn't exist (which is perfectly fine) */
+ g_error_free (error);
+ }
+
+ if (added) {
+ /* Bookmarks separator */
+ add_node (model, NULL, NULL, NULL,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK |
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR, NULL);
+ }
+
+ g_free (bookmarks);
+}
+
+static gint flags_order[] = {
+ GEDIT_FILE_BOOKMARKS_STORE_IS_HOME,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_FS,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK,
+ -1
+};
+
+static gint
+utf8_casecmp (gchar const *s1, const gchar * s2)
+{
+ gchar *n1;
+ gchar *n2;
+ gint result;
+
+ n1 = g_utf8_casefold (s1, -1);
+ n2 = g_utf8_casefold (s2, -1);
+
+ result = g_utf8_collate (n1, n2);
+
+ g_free (n1);
+ g_free (n2);
+
+ return result;
+}
+
+static gint
+bookmarks_compare_names (GtkTreeModel * model, GtkTreeIter * a,
+ GtkTreeIter * b)
+{
+ gchar *n1;
+ gchar *n2;
+ gint result;
+ guint f1;
+ guint f2;
+
+ gtk_tree_model_get (model, a,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &n1,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f1,
+ -1);
+ gtk_tree_model_get (model, b,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME, &n2,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f2,
+ -1);
+
+ /* do not sort actual bookmarks to keep same order as in caja */
+ if ((f1 & GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK) &&
+ (f2 & GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK))
+ result = 0;
+ else if (n1 == NULL && n2 == NULL)
+ result = 0;
+ else if (n1 == NULL)
+ result = -1;
+ else if (n2 == NULL)
+ result = 1;
+ else
+ result = utf8_casecmp (n1, n2);
+
+ g_free (n1);
+ g_free (n2);
+
+ return result;
+}
+
+static gint
+bookmarks_compare_flags (GtkTreeModel * model, GtkTreeIter * a,
+ GtkTreeIter * b)
+{
+ guint f1;
+ guint f2;
+ gint *flags;
+ guint sep;
+
+ sep = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR;
+
+ gtk_tree_model_get (model, a,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f1,
+ -1);
+ gtk_tree_model_get (model, b,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &f2,
+ -1);
+
+ for (flags = flags_order; *flags != -1; ++flags) {
+ if ((f1 & *flags) != (f2 & *flags)) {
+ if (f1 & *flags) {
+ return -1;
+ } else {
+ return 1;
+ }
+ } else if ((f1 & *flags) && (f1 & sep) != (f2 & sep)) {
+ if (f1 & sep)
+ return -1;
+ else
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static gint
+bookmarks_compare_func (GtkTreeModel * model, GtkTreeIter * a,
+ GtkTreeIter * b, gpointer user_data)
+{
+ gint result;
+
+ result = bookmarks_compare_flags (model, a, b);
+
+ if (result == 0)
+ result = bookmarks_compare_names (model, a, b);
+
+ return result;
+}
+
+static gboolean
+find_with_flags (GtkTreeModel * model, GtkTreeIter * iter, gpointer obj,
+ guint flags, guint notflags)
+{
+ GtkTreeIter child;
+ guint childflags = 0;
+ GObject * childobj;
+ gboolean fequal;
+
+ if (!gtk_tree_model_get_iter_first (model, &child))
+ return FALSE;
+
+ do {
+ gtk_tree_model_get (model, &child,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT,
+ &childobj,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS,
+ &childflags, -1);
+
+ fequal = (obj == childobj);
+
+ if (childobj)
+ g_object_unref (childobj);
+
+ if ((obj == NULL || fequal) &&
+ (childflags & flags) == flags
+ && !(childflags & notflags)) {
+ *iter = child;
+ return TRUE;
+ }
+ } while (gtk_tree_model_iter_next (model, &child));
+
+ return FALSE;
+}
+
+static void
+remove_node (GtkTreeModel * model, GtkTreeIter * iter)
+{
+ guint flags;
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!(flags & GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR)) {
+ if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS) {
+ check_mount_separator (GEDIT_FILE_BOOKMARKS_STORE (model),
+ flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS,
+ FALSE);
+ }
+ }
+
+ gtk_tree_store_remove (GTK_TREE_STORE (model), iter);
+}
+
+static void
+remove_bookmarks (GeditFileBookmarksStore * model)
+{
+ GtkTreeIter iter;
+
+ while (find_with_flags (GTK_TREE_MODEL (model), &iter, NULL,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK,
+ 0)) {
+ remove_node (GTK_TREE_MODEL (model), &iter);
+ }
+}
+
+static void
+initialize_fill (GeditFileBookmarksStore * model)
+{
+ init_special_directories (model);
+ init_fs (model);
+ init_bookmarks (model);
+}
+
+/* Public */
+GeditFileBookmarksStore *
+gedit_file_bookmarks_store_new (void)
+{
+ GeditFileBookmarksStore *model;
+ GType column_types[] = {
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_OBJECT,
+ G_TYPE_UINT
+ };
+
+ model = g_object_new (GEDIT_TYPE_FILE_BOOKMARKS_STORE, NULL);
+ gtk_tree_store_set_column_types (GTK_TREE_STORE (model),
+ GEDIT_FILE_BOOKMARKS_STORE_N_COLUMNS,
+ column_types);
+
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model),
+ bookmarks_compare_func,
+ NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+
+ initialize_fill (model);
+
+ return model;
+}
+
+gchar *
+gedit_file_bookmarks_store_get_uri (GeditFileBookmarksStore * model,
+ GtkTreeIter * iter)
+{
+ GObject * obj;
+ GFile * file = NULL;
+ guint flags;
+ gchar * ret = NULL;
+ gboolean isfs;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BOOKMARKS_STORE (model), NULL);
+ g_return_val_if_fail (iter != NULL, NULL);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS,
+ &flags,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT,
+ &obj,
+ -1);
+
+ if (obj == NULL)
+ return NULL;
+
+ isfs = (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_FS);
+
+ if (isfs && (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT))
+ {
+ file = g_mount_get_root (G_MOUNT (obj));
+ }
+ else if (!isfs)
+ {
+ file = g_object_ref (obj);
+ }
+
+ g_object_unref (obj);
+
+ if (file)
+ {
+ ret = g_file_get_uri (file);
+ g_object_unref (file);
+ }
+
+ return ret;
+}
+
+void
+gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore * model)
+{
+ gtk_tree_store_clear (GTK_TREE_STORE (model));
+ initialize_fill (model);
+}
+
+static void
+on_fs_changed (GVolumeMonitor *monitor,
+ GObject *object,
+ GeditFileBookmarksStore *model)
+{
+ GtkTreeModel *tree_model = GTK_TREE_MODEL (model);
+ guint flags = GEDIT_FILE_BOOKMARKS_STORE_IS_FS;
+ guint noflags = GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR;
+ GtkTreeIter iter;
+
+ /* clear all fs items */
+ while (find_with_flags (tree_model, &iter, NULL, flags, noflags))
+ remove_node (tree_model, &iter);
+
+ /* then reinitialize */
+ init_fs (model);
+}
+
+static void
+on_bookmarks_file_changed (GFileMonitor * monitor,
+ GFile * file,
+ GFile * other_file,
+ GFileMonitorEvent event_type,
+ GeditFileBookmarksStore * model)
+{
+ switch (event_type) {
+ case G_FILE_MONITOR_EVENT_CHANGED:
+ case G_FILE_MONITOR_EVENT_CREATED:
+ /* Re-initialize bookmarks */
+ remove_bookmarks (model);
+ init_bookmarks (model);
+ break;
+ case G_FILE_MONITOR_EVENT_DELETED: // FIXME: shouldn't we also monitor the directory?
+ /* Remove bookmarks */
+ remove_bookmarks (model);
+ g_object_unref (monitor);
+ model->priv->bookmarks_monitor = NULL;
+ break;
+ default:
+ break;
+ }
+}
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-bookmarks-store.h b/plugins/filebrowser/gedit-file-bookmarks-store.h
new file mode 100755
index 00000000..bd20911e
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-bookmarks-store.h
@@ -0,0 +1,90 @@
+/*
+ * gedit-file-bookmarks-store.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_FILE_BOOKMARKS_STORE_H__
+#define __GEDIT_FILE_BOOKMARKS_STORE_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define GEDIT_TYPE_FILE_BOOKMARKS_STORE (gedit_file_bookmarks_store_get_type ())
+#define GEDIT_FILE_BOOKMARKS_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStore))
+#define GEDIT_FILE_BOOKMARKS_STORE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStore const))
+#define GEDIT_FILE_BOOKMARKS_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStoreClass))
+#define GEDIT_IS_FILE_BOOKMARKS_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE))
+#define GEDIT_IS_FILE_BOOKMARKS_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BOOKMARKS_STORE))
+#define GEDIT_FILE_BOOKMARKS_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BOOKMARKS_STORE, GeditFileBookmarksStoreClass))
+
+typedef struct _GeditFileBookmarksStore GeditFileBookmarksStore;
+typedef struct _GeditFileBookmarksStoreClass GeditFileBookmarksStoreClass;
+typedef struct _GeditFileBookmarksStorePrivate GeditFileBookmarksStorePrivate;
+
+enum
+{
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON = 0,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS,
+ GEDIT_FILE_BOOKMARKS_STORE_N_COLUMNS
+};
+
+enum
+{
+ GEDIT_FILE_BOOKMARKS_STORE_NONE = 0,
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR = 1 << 0, /* Separator item */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_SPECIAL_DIR = 1 << 1, /* Special user dir */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_HOME = 1 << 2, /* The special Home user directory */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DESKTOP = 1 << 3, /* The special Desktop user directory */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DOCUMENTS = 1 << 4, /* The special Documents user directory */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_FS = 1 << 5, /* A mount object */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT = 1 << 6, /* A mount object */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME = 1 << 7, /* A volume object */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE = 1 << 8, /* A drive object */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_ROOT = 1 << 9, /* The root file system (file:///) */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_BOOKMARK = 1 << 10, /* A gtk bookmark */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK = 1 << 11, /* A remote gtk bookmark */
+ GEDIT_FILE_BOOKMARKS_STORE_IS_LOCAL_BOOKMARK = 1 << 12 /* A local gtk bookmark */
+};
+
+struct _GeditFileBookmarksStore
+{
+ GtkTreeStore parent;
+
+ GeditFileBookmarksStorePrivate *priv;
+};
+
+struct _GeditFileBookmarksStoreClass
+{
+ GtkTreeStoreClass parent_class;
+};
+
+GType gedit_file_bookmarks_store_get_type (void) G_GNUC_CONST;
+GType gedit_file_bookmarks_store_register_type (GTypeModule * module);
+
+GeditFileBookmarksStore *gedit_file_bookmarks_store_new (void);
+gchar *gedit_file_bookmarks_store_get_uri (GeditFileBookmarksStore * model,
+ GtkTreeIter * iter);
+void gedit_file_bookmarks_store_refresh (GeditFileBookmarksStore * model);
+
+G_END_DECLS
+#endif /* __GEDIT_FILE_BOOKMARKS_STORE_H__ */
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-enum-register.c.template b/plugins/filebrowser/gedit-file-browser-enum-register.c.template
new file mode 100755
index 00000000..63a9c562
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-enum-register.c.template
@@ -0,0 +1,20 @@
+/*** BEGIN file-header ***/
+void
+gedit_file_browser_enum_and_flag_register_type (GTypeModule * module)
+{
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+ /* Enumerations from "@filename@" */
+
+/*** END file-production ***/
+
+/*** BEGIN enumeration-production ***/
+ register_@enum_name@ (module);
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+}
+
+/*** END file-tail ***/
diff --git a/plugins/filebrowser/gedit-file-browser-enum-types.c.template b/plugins/filebrowser/gedit-file-browser-enum-types.c.template
new file mode 100755
index 00000000..4e89370d
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-enum-types.c.template
@@ -0,0 +1,45 @@
+/*** BEGIN file-header ***/
+#include "gedit-file-browser-enum-types.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+#include "@filename@"
+
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+static GType @enum_name@_type = 0;
+
+static GType
+register_@enum_name@ (GTypeModule *module)
+{
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@,
+ "@VALUENAME@",
+ "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+
+ @enum_name@_type =
+ g_type_module_register_@type@ (module,
+ "@EnumName@",
+ values);
+
+ return @enum_name@_type;
+}
+
+GType
+@enum_name@_get_type (void)
+{
+ return @enum_name@_type;
+}
+
+/*** END value-tail ***/
diff --git a/plugins/filebrowser/gedit-file-browser-enum-types.h.template b/plugins/filebrowser/gedit-file-browser-enum-types.h.template
new file mode 100755
index 00000000..aea4fad9
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-enum-types.h.template
@@ -0,0 +1,29 @@
+/*** BEGIN file-header ***/
+#ifndef __GEDIT_FILE_BROWSER_ENUM_TYPES_H__
+#define __GEDIT_FILE_BROWSER_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* Enumerations from "@filename@" */
+
+/*** END file-production ***/
+
+/*** BEGIN enumeration-production ***/
+#define GEDIT_TYPE_@ENUMSHORT@ (@enum_name@_get_type())
+GType @enum_name@_get_type (void) G_GNUC_CONST;
+
+/*** END enumeration-production ***/
+
+/*** BEGIN file-tail ***/
+void gedit_file_browser_enum_and_flag_register_type (GTypeModule * module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_FILE_BROWSER_ENUM_TYPES_H__ */
+/*** END file-tail ***/
+
diff --git a/plugins/filebrowser/gedit-file-browser-error.h b/plugins/filebrowser/gedit-file-browser-error.h
new file mode 100755
index 00000000..ec5b8618
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-error.h
@@ -0,0 +1,41 @@
+/*
+ * gedit-file-browser-error.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_FILE_BROWSER_ERROR_H__
+#define __GEDIT_FILE_BROWSER_ERROR_H__
+
+G_BEGIN_DECLS
+
+typedef enum {
+ GEDIT_FILE_BROWSER_ERROR_NONE,
+ GEDIT_FILE_BROWSER_ERROR_RENAME,
+ GEDIT_FILE_BROWSER_ERROR_DELETE,
+ GEDIT_FILE_BROWSER_ERROR_NEW_FILE,
+ GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY,
+ GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY,
+ GEDIT_FILE_BROWSER_ERROR_NUM
+} GeditFileBrowserError;
+
+G_END_DECLS
+
+#endif /* __GEDIT_FILE_BROWSER_ERROR_H__ */
diff --git a/plugins/filebrowser/gedit-file-browser-marshal.list b/plugins/filebrowser/gedit-file-browser-marshal.list
new file mode 100755
index 00000000..5fa72c8b
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-marshal.list
@@ -0,0 +1,5 @@
+VOID:UINT,STRING
+VOID:STRING,STRING
+BOOL:OBJECT,POINTER
+BOOL:POINTER
+BOOL:VOID
diff --git a/plugins/filebrowser/gedit-file-browser-messages.c b/plugins/filebrowser/gedit-file-browser-messages.c
new file mode 100755
index 00000000..b587edf1
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-messages.c
@@ -0,0 +1,1033 @@
+#include "gedit-file-browser-messages.h"
+#include "gedit-file-browser-store.h"
+#include <gedit/gedit-message.h>
+
+#define MESSAGE_OBJECT_PATH "/plugins/filebrowser"
+#define WINDOW_DATA_KEY "GeditFileBrowserMessagesWindowData"
+
+#define BUS_CONNECT(bus, name, data) gedit_message_bus_connect(bus, MESSAGE_OBJECT_PATH, #name, (GeditMessageCallback) message_##name##_cb, data, NULL)
+
+typedef struct
+{
+ GeditWindow *window;
+ GeditMessage *message;
+} MessageCacheData;
+
+typedef struct
+{
+ guint row_inserted_id;
+ guint row_deleted_id;
+ guint root_changed_id;
+ guint begin_loading_id;
+ guint end_loading_id;
+
+ GList *merge_ids;
+ GtkActionGroup *merged_actions;
+
+ GeditMessageBus *bus;
+ GeditFileBrowserWidget *widget;
+ GHashTable *row_tracking;
+
+ GHashTable *filters;
+} WindowData;
+
+typedef struct
+{
+ gulong id;
+
+ GeditWindow *window;
+ GeditMessage *message;
+} FilterData;
+
+static WindowData *
+window_data_new (GeditWindow *window,
+ GeditFileBrowserWidget *widget)
+{
+ WindowData *data = g_slice_new (WindowData);
+ GtkUIManager *manager;
+ GList *groups;
+
+ data->bus = gedit_window_get_message_bus (window);
+ data->widget = widget;
+ data->row_tracking = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)gtk_tree_row_reference_free);
+
+ data->filters = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify)g_free,
+ NULL);
+
+ manager = gedit_file_browser_widget_get_ui_manager (widget);
+
+ data->merge_ids = NULL;
+ data->merged_actions = gtk_action_group_new ("MessageMergedActions");
+
+ groups = gtk_ui_manager_get_action_groups (manager);
+ gtk_ui_manager_insert_action_group (manager, data->merged_actions, g_list_length (groups));
+
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, data);
+
+ return data;
+}
+
+static WindowData *
+get_window_data (GeditWindow * window)
+{
+ return (WindowData *) (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY));
+}
+
+static void
+window_data_free (GeditWindow *window)
+{
+ WindowData *data = get_window_data (window);
+ GtkUIManager *manager;
+ GList *item;
+
+ g_hash_table_destroy (data->row_tracking);
+ g_hash_table_destroy (data->filters);
+
+ manager = gedit_file_browser_widget_get_ui_manager (data->widget);
+ gtk_ui_manager_remove_action_group (manager, data->merged_actions);
+
+ for (item = data->merge_ids; item; item = item->next)
+ gtk_ui_manager_remove_ui (manager, GPOINTER_TO_INT (item->data));
+
+ g_list_free (data->merge_ids);
+ g_object_unref (data->merged_actions);
+
+ g_slice_free (WindowData, data);
+
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL);
+}
+
+static FilterData *
+filter_data_new (GeditWindow *window,
+ GeditMessage *message)
+{
+ FilterData *data = g_slice_new (FilterData);
+ WindowData *wdata;
+
+ data->window = window;
+ data->id = 0;
+ data->message = message;
+
+ wdata = get_window_data (window);
+
+ g_hash_table_insert (wdata->filters,
+ gedit_message_type_identifier (gedit_message_get_object_path (message),
+ gedit_message_get_method (message)),
+ data);
+
+ return data;
+}
+
+static void
+filter_data_free (FilterData *data)
+{
+ WindowData *wdata = get_window_data (data->window);
+ gchar *identifier;
+
+ identifier = gedit_message_type_identifier (gedit_message_get_object_path (data->message),
+ gedit_message_get_method (data->message));
+
+ g_hash_table_remove (wdata->filters, identifier);
+ g_free (identifier);
+
+ g_object_unref (data->message);
+ g_slice_free (FilterData, data);
+}
+
+static GtkTreePath *
+track_row_lookup (WindowData *data,
+ const gchar *id)
+{
+ GtkTreeRowReference *ref;
+
+ ref = (GtkTreeRowReference *)g_hash_table_lookup (data->row_tracking, id);
+
+ if (!ref)
+ return NULL;
+
+ return gtk_tree_row_reference_get_path (ref);
+}
+
+static void
+message_cache_data_free (MessageCacheData *data)
+{
+ g_object_unref (data->message);
+ g_slice_free (MessageCacheData, data);
+}
+
+static MessageCacheData *
+message_cache_data_new (GeditWindow *window,
+ GeditMessage *message)
+{
+ MessageCacheData *data = g_slice_new (MessageCacheData);
+
+ data->window = window;
+ data->message = message;
+
+ return data;
+}
+
+static void
+message_get_root_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ GeditFileBrowserStore *store;
+ gchar *uri;
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+ uri = gedit_file_browser_store_get_virtual_root (store);
+
+ gedit_message_set (message, "uri", uri, NULL);
+ g_free (uri);
+}
+
+static void
+message_set_root_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gchar *root = NULL;
+ gchar *virtual = NULL;
+
+ gedit_message_get (message, "uri", &root, NULL);
+
+ if (!root)
+ return;
+
+ if (gedit_message_has_key (message, "virtual"))
+ gedit_message_get (message, "virtual", &virtual, NULL);
+
+ if (virtual)
+ gedit_file_browser_widget_set_root_and_virtual_root (data->widget, root, virtual);
+ else
+ gedit_file_browser_widget_set_root (data->widget, root, TRUE);
+
+ g_free (root);
+ g_free (virtual);
+}
+
+static void
+message_set_emblem_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gchar *id = NULL;
+ gchar *emblem = NULL;
+ GtkTreePath *path;
+ GeditFileBrowserStore *store;
+
+ gedit_message_get (message, "id", &id, "emblem", &emblem, NULL);
+
+ if (!id || !emblem)
+ {
+ g_free (id);
+ g_free (emblem);
+
+ return;
+ }
+
+ path = track_row_lookup (data, id);
+
+ if (path != NULL)
+ {
+ GError *error = NULL;
+ GdkPixbuf *pixbuf;
+
+ pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
+ emblem,
+ 10,
+ 0,
+ &error);
+
+ if (pixbuf)
+ {
+ GValue value = { 0, };
+ GtkTreeIter iter;
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+ {
+ g_value_init (&value, GDK_TYPE_PIXBUF);
+ g_value_set_object (&value, pixbuf);
+
+ gedit_file_browser_store_set_value (store,
+ &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM,
+ &value);
+
+ g_value_unset (&value);
+ }
+
+ g_object_unref (pixbuf);
+ }
+
+ if (error)
+ g_error_free (error);
+ }
+
+ g_free (id);
+ g_free (emblem);
+}
+
+static gchar *
+item_id (const gchar *path,
+ const gchar *uri)
+{
+ return g_strconcat (path, "::", uri, NULL);
+}
+
+static gchar *
+track_row (WindowData *data,
+ GeditFileBrowserStore *store,
+ GtkTreePath *path,
+ const gchar *uri)
+{
+ GtkTreeRowReference *ref;
+ gchar *id;
+ gchar *pathstr;
+
+ pathstr = gtk_tree_path_to_string (path);
+ id = item_id (pathstr, uri);
+
+ ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (store), path);
+ g_hash_table_insert (data->row_tracking, g_strdup (id), ref);
+
+ g_free (pathstr);
+
+ return id;
+}
+
+static void
+set_item_message (WindowData *data,
+ GtkTreeIter *iter,
+ GtkTreePath *path,
+ GeditMessage *message)
+{
+ GeditFileBrowserStore *store;
+ gchar *uri = NULL;
+ guint flags = 0;
+ gchar *track_id;
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!uri)
+ return;
+
+ if (path && gtk_tree_path_get_depth (path) != 0)
+ track_id = track_row (data, store, path, uri);
+ else
+ track_id = NULL;
+
+ gedit_message_set (message,
+ "id", track_id,
+ "uri", uri,
+ NULL);
+
+ if (gedit_message_has_key (message, "is_directory"))
+ {
+ gedit_message_set (message,
+ "is_directory", FILE_IS_DIR (flags),
+ NULL);
+ }
+
+ g_free (uri);
+ g_free (track_id);
+}
+
+static gboolean
+custom_message_filter_func (GeditFileBrowserWidget *widget,
+ GeditFileBrowserStore *store,
+ GtkTreeIter *iter,
+ FilterData *data)
+{
+ WindowData *wdata = get_window_data (data->window);
+ gchar *uri = NULL;
+ guint flags = 0;
+ gboolean filter = FALSE;
+ GtkTreePath *path;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!uri || FILE_IS_DUMMY (flags))
+ {
+ g_free (uri);
+ return FALSE;
+ }
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
+ set_item_message (wdata, iter, path, data->message);
+ gtk_tree_path_free (path);
+
+ gedit_message_set (data->message, "filter", filter, NULL);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ gedit_message_get (data->message, "filter", &filter, NULL);
+
+ return !filter;
+}
+
+static void
+message_add_filter_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ GeditWindow *window)
+{
+ gchar *object_path = NULL;
+ gchar *method = NULL;
+ gulong id;
+ GeditMessageType *message_type;
+ GeditMessage *cbmessage;
+ FilterData *filter_data;
+ WindowData *data = get_window_data (window);
+
+ gedit_message_get (message,
+ "object_path", &object_path,
+ "method", &method,
+ NULL);
+
+ // Check if there exists such a 'callback' message
+ if (!object_path || !method)
+ {
+ g_free (object_path);
+ g_free (method);
+
+ return;
+ }
+
+ message_type = gedit_message_bus_lookup (bus, object_path, method);
+
+ if (!message_type)
+ {
+ g_free (object_path);
+ g_free (method);
+
+ return;
+ }
+
+ // Check if the message type has the correct arguments
+ if (gedit_message_type_lookup (message_type, "id") != G_TYPE_STRING ||
+ gedit_message_type_lookup (message_type, "uri") != G_TYPE_STRING ||
+ gedit_message_type_lookup (message_type, "is_directory") != G_TYPE_BOOLEAN ||
+ gedit_message_type_lookup (message_type, "filter") != G_TYPE_BOOLEAN)
+ {
+ return;
+ }
+
+ cbmessage = gedit_message_type_instantiate (message_type,
+ "id", NULL,
+ "uri", NULL,
+ "is_directory", FALSE,
+ "filter", FALSE,
+ NULL);
+
+ // Register the custom filter on the widget
+ filter_data = filter_data_new (window, cbmessage);
+ id = gedit_file_browser_widget_add_filter (data->widget,
+ (GeditFileBrowserWidgetFilterFunc)custom_message_filter_func,
+ filter_data,
+ (GDestroyNotify)filter_data_free);
+
+ filter_data->id = id;
+}
+
+static void
+message_remove_filter_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gulong id = 0;
+
+ gedit_message_get (message, "id", &id, NULL);
+
+ if (!id)
+ return;
+
+ gedit_file_browser_widget_remove_filter (data->widget, id);
+}
+
+static void
+message_up_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ GeditFileBrowserStore *store = gedit_file_browser_widget_get_browser_store (data->widget);
+
+ gedit_file_browser_store_set_virtual_root_up (store);
+}
+
+static void
+message_history_back_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_history_back (data->widget);
+}
+
+static void
+message_history_forward_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_history_forward (data->widget);
+}
+
+static void
+message_refresh_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_refresh (data->widget);
+}
+
+static void
+message_set_show_hidden_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gboolean active = FALSE;
+ GeditFileBrowserStore *store;
+ GeditFileBrowserStoreFilterMode mode;
+
+ gedit_message_get (message, "active", &active, NULL);
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+ mode = gedit_file_browser_store_get_filter_mode (store);
+
+ if (active)
+ mode &= ~GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+ else
+ mode |= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+
+ gedit_file_browser_store_set_filter_mode (store, mode);
+}
+
+static void
+message_set_show_binary_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gboolean active = FALSE;
+ GeditFileBrowserStore *store;
+ GeditFileBrowserStoreFilterMode mode;
+
+ gedit_message_get (message, "active", &active, NULL);
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+ mode = gedit_file_browser_store_get_filter_mode (store);
+
+ if (active)
+ mode &= ~GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY;
+ else
+ mode |= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY;
+
+ gedit_file_browser_store_set_filter_mode (store, mode);
+}
+
+static void
+message_show_bookmarks_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_show_bookmarks (data->widget);
+}
+
+static void
+message_show_files_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ gedit_file_browser_widget_show_files (data->widget);
+}
+
+static void
+message_add_context_item_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ GtkAction *action = NULL;
+ gchar *path = NULL;
+ gchar *name;
+ GtkUIManager *manager;
+ guint merge_id;
+
+ gedit_message_get (message,
+ "action", &action,
+ "path", &path,
+ NULL);
+
+ if (!action || !path)
+ {
+ if (action)
+ g_object_unref (action);
+
+ g_free (path);
+ return;
+ }
+
+ gtk_action_group_add_action (data->merged_actions, action);
+ manager = gedit_file_browser_widget_get_ui_manager (data->widget);
+ name = g_strconcat (gtk_action_get_name (action), "MenuItem", NULL);
+ merge_id = gtk_ui_manager_new_merge_id (manager);
+
+ gtk_ui_manager_add_ui (manager,
+ merge_id,
+ path,
+ name,
+ gtk_action_get_name (action),
+ GTK_UI_MANAGER_AUTO,
+ FALSE);
+
+ if (gtk_ui_manager_get_widget (manager, path))
+ {
+ data->merge_ids = g_list_prepend (data->merge_ids, GINT_TO_POINTER (merge_id));
+ gedit_message_set (message, "id", merge_id, NULL);
+ }
+ else
+ {
+ gedit_message_set (message, "id", 0, NULL);
+ }
+
+ g_object_unref (action);
+ g_free (path);
+ g_free (name);
+}
+
+static void
+message_remove_context_item_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ guint merge_id = 0;
+ GtkUIManager *manager;
+
+ gedit_message_get (message, "id", &merge_id, NULL);
+
+ if (merge_id == 0)
+ return;
+
+ manager = gedit_file_browser_widget_get_ui_manager (data->widget);
+
+ data->merge_ids = g_list_remove (data->merge_ids, GINT_TO_POINTER (merge_id));
+ gtk_ui_manager_remove_ui (manager, merge_id);
+}
+
+static void
+message_get_view_cb (GeditMessageBus *bus,
+ GeditMessage *message,
+ WindowData *data)
+{
+ GeditFileBrowserView *view;
+ view = gedit_file_browser_widget_get_browser_view (data->widget);
+
+ gedit_message_set (message, "view", view, NULL);
+}
+
+static void
+register_methods (GeditWindow *window,
+ GeditFileBrowserWidget *widget)
+{
+ GeditMessageBus *bus = gedit_window_get_message_bus (window);
+ WindowData *data = get_window_data (window);
+
+ /* Register method calls */
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "get_root",
+ 1,
+ "uri", G_TYPE_STRING,
+ NULL);
+
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "set_root",
+ 1,
+ "uri", G_TYPE_STRING,
+ "virtual", G_TYPE_STRING,
+ NULL);
+
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "set_emblem",
+ 0,
+ "id", G_TYPE_STRING,
+ "emblem", G_TYPE_STRING,
+ NULL);
+
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "add_filter",
+ 1,
+ "object_path", G_TYPE_STRING,
+ "method", G_TYPE_STRING,
+ "id", G_TYPE_ULONG,
+ NULL);
+
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "remove_filter",
+ 0,
+ "id", G_TYPE_ULONG,
+ NULL);
+
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "add_context_item",
+ 1,
+ "action", GTK_TYPE_ACTION,
+ "path", G_TYPE_STRING,
+ "id", G_TYPE_UINT,
+ NULL);
+
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "remove_context_item",
+ 0,
+ "id", G_TYPE_UINT,
+ NULL);
+
+ gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "up", 0, NULL);
+
+ gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "history_back", 0, NULL);
+ gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "history_forward", 0, NULL);
+
+ gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "refresh", 0, NULL);
+
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "set_show_hidden",
+ 0,
+ "active", G_TYPE_BOOLEAN,
+ NULL);
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "set_show_binary",
+ 0,
+ "active", G_TYPE_BOOLEAN,
+ NULL);
+
+ gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "show_bookmarks", 0, NULL);
+ gedit_message_bus_register (bus, MESSAGE_OBJECT_PATH, "show_files", 0, NULL);
+
+ gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "get_view",
+ 1,
+ "view", GEDIT_TYPE_FILE_BROWSER_VIEW,
+ NULL);
+
+ BUS_CONNECT (bus, get_root, data);
+ BUS_CONNECT (bus, set_root, data);
+ BUS_CONNECT (bus, set_emblem, data);
+ BUS_CONNECT (bus, add_filter, window);
+ BUS_CONNECT (bus, remove_filter, data);
+
+ BUS_CONNECT (bus, add_context_item, data);
+ BUS_CONNECT (bus, remove_context_item, data);
+
+ BUS_CONNECT (bus, up, data);
+ BUS_CONNECT (bus, history_back, data);
+ BUS_CONNECT (bus, history_forward, data);
+
+ BUS_CONNECT (bus, refresh, data);
+
+ BUS_CONNECT (bus, set_show_hidden, data);
+ BUS_CONNECT (bus, set_show_binary, data);
+
+ BUS_CONNECT (bus, show_bookmarks, data);
+ BUS_CONNECT (bus, show_files, data);
+
+ BUS_CONNECT (bus, get_view, data);
+}
+
+static void
+store_row_inserted (GeditFileBrowserStore *store,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ MessageCacheData *data)
+{
+ gchar *uri = NULL;
+ guint flags = 0;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags))
+ {
+ WindowData *wdata = get_window_data (data->window);
+
+ set_item_message (wdata, iter, path, data->message);
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ }
+
+ g_free (uri);
+}
+
+static void
+store_row_deleted (GeditFileBrowserStore *store,
+ GtkTreePath *path,
+ MessageCacheData *data)
+{
+ GtkTreeIter iter;
+ gchar *uri = NULL;
+ guint flags = 0;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!FILE_IS_DUMMY (flags) && !FILE_IS_FILTERED (flags))
+ {
+ WindowData *wdata = get_window_data (data->window);
+
+ set_item_message (wdata, &iter, path, data->message);
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ }
+
+ g_free (uri);
+}
+
+static void
+store_virtual_root_changed (GeditFileBrowserStore *store,
+ GParamSpec *spec,
+ MessageCacheData *data)
+{
+ WindowData *wdata = get_window_data (data->window);
+ gchar *uri;
+
+ uri = gedit_file_browser_store_get_virtual_root (store);
+
+ if (!uri)
+ return;
+
+ gedit_message_set (data->message,
+ "uri", uri,
+ NULL);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+
+ g_free (uri);
+}
+
+static void
+store_begin_loading (GeditFileBrowserStore *store,
+ GtkTreeIter *iter,
+ MessageCacheData *data)
+{
+ GtkTreePath *path;
+ WindowData *wdata = get_window_data (data->window);
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
+
+ set_item_message (wdata, iter, path, data->message);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ gtk_tree_path_free (path);
+}
+
+static void
+store_end_loading (GeditFileBrowserStore *store,
+ GtkTreeIter *iter,
+ MessageCacheData *data)
+{
+ GtkTreePath *path;
+ WindowData *wdata = get_window_data (data->window);
+
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter);
+
+ set_item_message (wdata, iter, path, data->message);
+
+ gedit_message_bus_send_message_sync (wdata->bus, data->message);
+ gtk_tree_path_free (path);
+}
+
+static void
+register_signals (GeditWindow *window,
+ GeditFileBrowserWidget *widget)
+{
+ GeditMessageBus *bus = gedit_window_get_message_bus (window);
+ GeditFileBrowserStore *store;
+ GeditMessageType *inserted_type;
+ GeditMessageType *deleted_type;
+ GeditMessageType *begin_loading_type;
+ GeditMessageType *end_loading_type;
+ GeditMessageType *root_changed_type;
+
+ GeditMessage *message;
+ WindowData *data;
+
+ /* Register signals */
+ root_changed_type = gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "root_changed",
+ 0,
+ "id", G_TYPE_STRING,
+ "uri", G_TYPE_STRING,
+ NULL);
+
+ begin_loading_type = gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "begin_loading",
+ 0,
+ "id", G_TYPE_STRING,
+ "uri", G_TYPE_STRING,
+ NULL);
+
+ end_loading_type = gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "end_loading",
+ 0,
+ "id", G_TYPE_STRING,
+ "uri", G_TYPE_STRING,
+ NULL);
+
+ inserted_type = gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "inserted",
+ 0,
+ "id", G_TYPE_STRING,
+ "uri", G_TYPE_STRING,
+ "is_directory", G_TYPE_BOOLEAN,
+ NULL);
+
+ deleted_type = gedit_message_bus_register (bus,
+ MESSAGE_OBJECT_PATH, "deleted",
+ 0,
+ "id", G_TYPE_STRING,
+ "uri", G_TYPE_STRING,
+ "is_directory", G_TYPE_BOOLEAN,
+ NULL);
+
+ store = gedit_file_browser_widget_get_browser_store (widget);
+
+ message = gedit_message_type_instantiate (inserted_type,
+ "id", NULL,
+ "uri", NULL,
+ "is_directory", FALSE,
+ NULL);
+
+ data = get_window_data (window);
+
+ data->row_inserted_id =
+ g_signal_connect_data (store,
+ "row-inserted",
+ G_CALLBACK (store_row_inserted),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+
+ message = gedit_message_type_instantiate (deleted_type,
+ "id", NULL,
+ "uri", NULL,
+ "is_directory", FALSE,
+ NULL);
+ data->row_deleted_id =
+ g_signal_connect_data (store,
+ "row-deleted",
+ G_CALLBACK (store_row_deleted),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+
+ message = gedit_message_type_instantiate (root_changed_type,
+ "id", NULL,
+ "uri", NULL,
+ NULL);
+ data->root_changed_id =
+ g_signal_connect_data (store,
+ "notify::virtual-root",
+ G_CALLBACK (store_virtual_root_changed),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+
+ message = gedit_message_type_instantiate (begin_loading_type,
+ "id", NULL,
+ "uri", NULL,
+ NULL);
+ data->begin_loading_id =
+ g_signal_connect_data (store,
+ "begin_loading",
+ G_CALLBACK (store_begin_loading),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+
+ message = gedit_message_type_instantiate (end_loading_type,
+ "id", NULL,
+ "uri", NULL,
+ NULL);
+ data->end_loading_id =
+ g_signal_connect_data (store,
+ "end_loading",
+ G_CALLBACK (store_end_loading),
+ message_cache_data_new (window, message),
+ (GClosureNotify)message_cache_data_free,
+ 0);
+}
+
+static void
+message_unregistered (GeditMessageBus *bus,
+ GeditMessageType *message_type,
+ GeditWindow *window)
+{
+ gchar *identifier = gedit_message_type_identifier (gedit_message_type_get_object_path (message_type),
+ gedit_message_type_get_method (message_type));
+ FilterData *data;
+ WindowData *wdata = get_window_data (window);
+
+ data = g_hash_table_lookup (wdata->filters, identifier);
+
+ if (data)
+ gedit_file_browser_widget_remove_filter (wdata->widget, data->id);
+
+ g_free (identifier);
+}
+
+void
+gedit_file_browser_messages_register (GeditWindow *window,
+ GeditFileBrowserWidget *widget)
+{
+ window_data_new (window, widget);
+
+ register_methods (window, widget);
+ register_signals (window, widget);
+
+ g_signal_connect (gedit_window_get_message_bus (window),
+ "unregistered",
+ G_CALLBACK (message_unregistered),
+ window);
+}
+
+static void
+cleanup_signals (GeditWindow *window)
+{
+ WindowData *data = get_window_data (window);
+ GeditFileBrowserStore *store;
+
+ store = gedit_file_browser_widget_get_browser_store (data->widget);
+
+ g_signal_handler_disconnect (store, data->row_inserted_id);
+ g_signal_handler_disconnect (store, data->row_deleted_id);
+ g_signal_handler_disconnect (store, data->root_changed_id);
+ g_signal_handler_disconnect (store, data->begin_loading_id);
+ g_signal_handler_disconnect (store, data->end_loading_id);
+
+ g_signal_handlers_disconnect_by_func (data->bus, message_unregistered, window);
+}
+
+void
+gedit_file_browser_messages_unregister (GeditWindow *window)
+{
+ GeditMessageBus *bus = gedit_window_get_message_bus (window);
+
+ cleanup_signals (window);
+ gedit_message_bus_unregister_all (bus, MESSAGE_OBJECT_PATH);
+
+ window_data_free (window);
+}
diff --git a/plugins/filebrowser/gedit-file-browser-messages.h b/plugins/filebrowser/gedit-file-browser-messages.h
new file mode 100755
index 00000000..e62094e8
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-messages.h
@@ -0,0 +1,35 @@
+/*
+ * gedit-file-browser-messages.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2008 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_FILE_BROWSER_MESSAGES_H__
+#define __GEDIT_FILE_BROWSER_MESSAGES_H__
+
+#include <gedit/gedit-window.h>
+#include <gedit/gedit-message-bus.h>
+#include "gedit-file-browser-widget.h"
+
+void gedit_file_browser_messages_register (GeditWindow *window,
+ GeditFileBrowserWidget *widget);
+void gedit_file_browser_messages_unregister (GeditWindow *window);
+
+#endif /* __GEDIT_FILE_BROWSER_MESSAGES_H__ */
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-plugin.c b/plugins/filebrowser/gedit-file-browser-plugin.c
new file mode 100755
index 00000000..f2da19f5
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-plugin.c
@@ -0,0 +1,1254 @@
+/*
+ * gedit-file-browser-plugin.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gedit/gedit-commands.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-app.h>
+#include <glib/gi18n-lib.h>
+#include <gedit/gedit-debug.h>
+#include <mateconf/mateconf-client.h>
+#include <string.h>
+
+#include "gedit-file-browser-enum-types.h"
+#include "gedit-file-browser-plugin.h"
+#include "gedit-file-browser-utils.h"
+#include "gedit-file-browser-error.h"
+#include "gedit-file-browser-widget.h"
+#include "gedit-file-browser-messages.h"
+
+#define WINDOW_DATA_KEY "GeditFileBrowserPluginWindowData"
+#define FILE_BROWSER_BASE_KEY "/apps/gedit-2/plugins/filebrowser"
+#define CAJA_CLICK_POLICY_BASE_KEY "/apps/caja/preferences"
+#define CAJA_CLICK_POLICY_KEY "click_policy"
+#define CAJA_ENABLE_DELETE_KEY "enable_delete"
+#define CAJA_CONFIRM_TRASH_KEY "confirm_trash"
+#define TERMINAL_EXEC_KEY "/desktop/mate/applications/terminal/exec"
+
+#define GEDIT_FILE_BROWSER_PLUGIN_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginPrivate))
+
+struct _GeditFileBrowserPluginPrivate
+{
+ gpointer dummy;
+};
+
+typedef struct _GeditFileBrowserPluginData
+{
+ GeditFileBrowserWidget * tree_widget;
+ gulong merge_id;
+ GtkActionGroup * action_group;
+ GtkActionGroup * single_selection_action_group;
+ gboolean auto_root;
+ gulong end_loading_handle;
+ gboolean confirm_trash;
+
+ guint click_policy_handle;
+ guint enable_delete_handle;
+ guint confirm_trash_handle;
+} GeditFileBrowserPluginData;
+
+static void on_uri_activated_cb (GeditFileBrowserWidget * widget,
+ gchar const *uri,
+ GeditWindow * window);
+static void on_error_cb (GeditFileBrowserWidget * widget,
+ guint code,
+ gchar const *message,
+ GeditWindow * window);
+static void on_model_set_cb (GeditFileBrowserView * widget,
+ GParamSpec *arg1,
+ GeditWindow * window);
+static void on_virtual_root_changed_cb (GeditFileBrowserStore * model,
+ GParamSpec * param,
+ GeditWindow * window);
+static void on_filter_mode_changed_cb (GeditFileBrowserStore * model,
+ GParamSpec * param,
+ GeditWindow * window);
+static void on_rename_cb (GeditFileBrowserStore * model,
+ const gchar * olduri,
+ const gchar * newuri,
+ GeditWindow * window);
+static void on_filter_pattern_changed_cb (GeditFileBrowserWidget * widget,
+ GParamSpec * param,
+ GeditWindow * window);
+static void on_tab_added_cb (GeditWindow * window,
+ GeditTab * tab,
+ GeditFileBrowserPluginData * data);
+static gboolean on_confirm_delete_cb (GeditFileBrowserWidget * widget,
+ GeditFileBrowserStore * store,
+ GList * rows,
+ GeditWindow * window);
+static gboolean on_confirm_no_trash_cb (GeditFileBrowserWidget * widget,
+ GList * files,
+ GeditWindow * window);
+
+GEDIT_PLUGIN_REGISTER_TYPE_WITH_CODE (GeditFileBrowserPlugin, filetree_plugin, \
+ gedit_file_browser_enum_and_flag_register_type (type_module); \
+ gedit_file_browser_store_register_type (type_module); \
+ gedit_file_bookmarks_store_register_type (type_module); \
+ gedit_file_browser_view_register_type (type_module); \
+ gedit_file_browser_widget_register_type (type_module); \
+)
+
+
+static void
+filetree_plugin_init (GeditFileBrowserPlugin * plugin)
+{
+ plugin->priv = GEDIT_FILE_BROWSER_PLUGIN_GET_PRIVATE (plugin);
+}
+
+static void
+filetree_plugin_finalize (GObject * object)
+{
+ //GeditFileBrowserPlugin * plugin = GEDIT_FILE_BROWSER_PLUGIN (object);
+
+ G_OBJECT_CLASS (filetree_plugin_parent_class)->finalize (object);
+}
+
+static GeditFileBrowserPluginData *
+get_plugin_data (GeditWindow * window)
+{
+ return (GeditFileBrowserPluginData *) (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY));
+}
+
+static void
+on_end_loading_cb (GeditFileBrowserStore * store,
+ GtkTreeIter * iter,
+ GeditFileBrowserPluginData * data)
+{
+ /* Disconnect the signal */
+ g_signal_handler_disconnect (store, data->end_loading_handle);
+ data->end_loading_handle = 0;
+ data->auto_root = FALSE;
+}
+
+static void
+prepare_auto_root (GeditFileBrowserPluginData *data)
+{
+ GeditFileBrowserStore *store;
+
+ data->auto_root = TRUE;
+
+ store = gedit_file_browser_widget_get_browser_store (data->tree_widget);
+
+ if (data->end_loading_handle != 0) {
+ g_signal_handler_disconnect (store, data->end_loading_handle);
+ data->end_loading_handle = 0;
+ }
+
+ data->end_loading_handle = g_signal_connect (store,
+ "end-loading",
+ G_CALLBACK (on_end_loading_cb),
+ data);
+}
+
+static void
+restore_default_location (GeditFileBrowserPluginData *data)
+{
+ gchar * root;
+ gchar * virtual_root;
+ gboolean bookmarks;
+ gboolean remote;
+ MateConfClient * client;
+
+ client = mateconf_client_get_default ();
+ if (!client)
+ return;
+
+ bookmarks = !mateconf_client_get_bool (client,
+ FILE_BROWSER_BASE_KEY "/on_load/tree_view",
+ NULL);
+
+ if (bookmarks) {
+ g_object_unref (client);
+ gedit_file_browser_widget_show_bookmarks (data->tree_widget);
+ return;
+ }
+
+ root = mateconf_client_get_string (client,
+ FILE_BROWSER_BASE_KEY "/on_load/root",
+ NULL);
+ virtual_root = mateconf_client_get_string (client,
+ FILE_BROWSER_BASE_KEY "/on_load/virtual_root",
+ NULL);
+
+ remote = mateconf_client_get_bool (client,
+ FILE_BROWSER_BASE_KEY "/on_load/enable_remote",
+ NULL);
+
+ if (root != NULL && *root != '\0') {
+ GFile *file;
+
+ file = g_file_new_for_uri (root);
+
+ if (remote || g_file_is_native (file)) {
+ if (virtual_root != NULL && *virtual_root != '\0') {
+ prepare_auto_root (data);
+ gedit_file_browser_widget_set_root_and_virtual_root (data->tree_widget,
+ root,
+ virtual_root);
+ } else {
+ prepare_auto_root (data);
+ gedit_file_browser_widget_set_root (data->tree_widget,
+ root,
+ TRUE);
+ }
+ }
+
+ g_object_unref (file);
+ }
+
+ g_object_unref (client);
+ g_free (root);
+ g_free (virtual_root);
+}
+
+static void
+restore_filter (GeditFileBrowserPluginData * data)
+{
+ MateConfClient * client;
+ gchar *filter_mode;
+ GeditFileBrowserStoreFilterMode mode;
+ gchar *pattern;
+
+ client = mateconf_client_get_default ();
+ if (!client)
+ return;
+
+ /* Get filter_mode */
+ filter_mode = mateconf_client_get_string (client,
+ FILE_BROWSER_BASE_KEY "/filter_mode",
+ NULL);
+
+ /* Filter mode */
+ mode = gedit_file_browser_store_filter_mode_get_default ();
+
+ if (filter_mode != NULL) {
+ if (strcmp (filter_mode, "hidden") == 0) {
+ mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+ } else if (strcmp (filter_mode, "binary") == 0) {
+ mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY;
+ } else if (strcmp (filter_mode, "hidden_and_binary") == 0 ||
+ strcmp (filter_mode, "binary_and_hidden") == 0) {
+ mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN |
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY;
+ } else if (strcmp (filter_mode, "none") == 0 ||
+ *filter_mode == '\0') {
+ mode = GEDIT_FILE_BROWSER_STORE_FILTER_MODE_NONE;
+ }
+ }
+
+ /* Set the filter mode */
+ gedit_file_browser_store_set_filter_mode (
+ gedit_file_browser_widget_get_browser_store (data->tree_widget),
+ mode);
+
+ pattern = mateconf_client_get_string (client,
+ FILE_BROWSER_BASE_KEY "/filter_pattern",
+ NULL);
+
+ gedit_file_browser_widget_set_filter_pattern (data->tree_widget,
+ pattern);
+
+ g_object_unref (client);
+ g_free (filter_mode);
+ g_free (pattern);
+}
+
+static GeditFileBrowserViewClickPolicy
+click_policy_from_string (gchar const *click_policy)
+{
+ if (click_policy && strcmp (click_policy, "single") == 0)
+ return GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE;
+ else
+ return GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE;
+}
+
+static void
+on_click_policy_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ MateConfValue *value;
+ GeditFileBrowserPluginData * data;
+ gchar const *click_policy;
+ GeditFileBrowserViewClickPolicy policy = GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE;
+ GeditFileBrowserView *view;
+
+ data = (GeditFileBrowserPluginData *)(user_data);
+ value = mateconf_entry_get_value (entry);
+
+ if (value && value->type == MATECONF_VALUE_STRING) {
+ click_policy = mateconf_value_get_string (value);
+
+ policy = click_policy_from_string (click_policy);
+ }
+
+ view = gedit_file_browser_widget_get_browser_view (data->tree_widget);
+ gedit_file_browser_view_set_click_policy (view, policy);
+}
+
+static void
+on_enable_delete_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ MateConfValue *value;
+ GeditFileBrowserPluginData *data;
+ gboolean enable = FALSE;
+
+ data = (GeditFileBrowserPluginData *)(user_data);
+ value = mateconf_entry_get_value (entry);
+
+ if (value && value->type == MATECONF_VALUE_BOOL)
+ enable = mateconf_value_get_bool (value);
+
+ g_object_set (G_OBJECT (data->tree_widget), "enable-delete", enable, NULL);
+}
+
+static void
+on_confirm_trash_changed (MateConfClient *client,
+ guint cnxn_id,
+ MateConfEntry *entry,
+ gpointer user_data)
+{
+ MateConfValue *value;
+ GeditFileBrowserPluginData *data;
+ gboolean enable = FALSE;
+
+ data = (GeditFileBrowserPluginData *)(user_data);
+ value = mateconf_entry_get_value (entry);
+
+ if (value && value->type == MATECONF_VALUE_BOOL)
+ enable = mateconf_value_get_bool (value);
+
+ data->confirm_trash = enable;
+}
+
+static void
+install_caja_prefs (GeditFileBrowserPluginData *data)
+{
+ MateConfClient *client;
+ gchar *pref;
+ gboolean prefb;
+ GeditFileBrowserViewClickPolicy policy;
+ GeditFileBrowserView *view;
+
+ client = mateconf_client_get_default ();
+ if (!client)
+ return;
+
+ mateconf_client_add_dir (client,
+ CAJA_CLICK_POLICY_BASE_KEY,
+ MATECONF_CLIENT_PRELOAD_NONE,
+ NULL);
+
+ /* Get click_policy */
+ pref = mateconf_client_get_string (client,
+ CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CLICK_POLICY_KEY,
+ NULL);
+
+ policy = click_policy_from_string (pref);
+
+ view = gedit_file_browser_widget_get_browser_view (data->tree_widget);
+ gedit_file_browser_view_set_click_policy (view, policy);
+
+ if (pref) {
+ data->click_policy_handle =
+ mateconf_client_notify_add (client,
+ CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CLICK_POLICY_KEY,
+ on_click_policy_changed,
+ data,
+ NULL,
+ NULL);
+ g_free (pref);
+ }
+
+ /* Get enable_delete */
+ prefb = mateconf_client_get_bool (client,
+ CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_ENABLE_DELETE_KEY,
+ NULL);
+
+ g_object_set (G_OBJECT (data->tree_widget), "enable-delete", prefb, NULL);
+
+ data->enable_delete_handle =
+ mateconf_client_notify_add (client,
+ CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_ENABLE_DELETE_KEY,
+ on_enable_delete_changed,
+ data,
+ NULL,
+ NULL);
+
+ /* Get confirm_trash */
+ prefb = mateconf_client_get_bool (client,
+ CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CONFIRM_TRASH_KEY,
+ NULL);
+
+ data->confirm_trash = prefb;
+
+ data->confirm_trash_handle =
+ mateconf_client_notify_add (client,
+ CAJA_CLICK_POLICY_BASE_KEY "/" CAJA_CONFIRM_TRASH_KEY,
+ on_confirm_trash_changed,
+ data,
+ NULL,
+ NULL);
+ g_object_unref (client);
+}
+
+static void
+set_root_from_doc (GeditFileBrowserPluginData * data,
+ GeditDocument * doc)
+{
+ GFile *file;
+ GFile *parent;
+
+ if (doc == NULL)
+ return;
+
+ file = gedit_document_get_location (doc);
+ if (file == NULL)
+ return;
+
+ parent = g_file_get_parent (file);
+
+ if (parent != NULL) {
+ gchar * root;
+
+ root = g_file_get_uri (parent);
+
+ gedit_file_browser_widget_set_root (data->tree_widget,
+ root,
+ TRUE);
+
+ g_object_unref (parent);
+ g_free (root);
+ }
+
+ g_object_unref (file);
+}
+
+static void
+on_action_set_active_root (GtkAction * action,
+ GeditWindow * window)
+{
+ GeditFileBrowserPluginData *data;
+
+ data = get_plugin_data (window);
+ set_root_from_doc (data,
+ gedit_window_get_active_document (window));
+}
+
+static gchar *
+get_terminal (void)
+{
+ MateConfClient * client;
+ gchar * terminal;
+
+ client = mateconf_client_get_default ();
+ terminal = mateconf_client_get_string (client,
+ TERMINAL_EXEC_KEY,
+ NULL);
+ g_object_unref (client);
+
+ if (terminal == NULL) {
+ const gchar *term = g_getenv ("TERM");
+
+ if (term != NULL)
+ terminal = g_strdup (term);
+ else
+ terminal = g_strdup ("xterm");
+ }
+
+ return terminal;
+}
+
+static void
+on_action_open_terminal (GtkAction * action,
+ GeditWindow * window)
+{
+ GeditFileBrowserPluginData * data;
+ gchar * terminal;
+ gchar * wd = NULL;
+ gchar * local;
+ gchar * argv[2];
+ GFile * file;
+
+ GtkTreeIter iter;
+ GeditFileBrowserStore * store;
+
+ data = get_plugin_data (window);
+
+ /* Get the current directory */
+ if (!gedit_file_browser_widget_get_selected_directory (data->tree_widget, &iter))
+ return;
+
+ store = gedit_file_browser_widget_get_browser_store (data->tree_widget);
+ gtk_tree_model_get (GTK_TREE_MODEL (store),
+ &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI,
+ &wd,
+ -1);
+
+ if (wd == NULL)
+ return;
+
+ terminal = get_terminal ();
+
+ file = g_file_new_for_uri (wd);
+ local = g_file_get_path (file);
+ g_object_unref (file);
+
+ argv[0] = terminal;
+ argv[1] = NULL;
+
+ g_spawn_async (local,
+ argv,
+ NULL,
+ G_SPAWN_SEARCH_PATH,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ g_free (terminal);
+ g_free (wd);
+ g_free (local);
+}
+
+static void
+on_selection_changed_cb (GtkTreeSelection *selection,
+ GeditWindow *window)
+{
+ GeditFileBrowserPluginData * data;
+ GtkTreeView * tree_view;
+ GtkTreeModel * model;
+ GtkTreeIter iter;
+ gboolean sensitive;
+ gchar * uri;
+
+ data = get_plugin_data (window);
+
+ tree_view = GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (data->tree_widget));
+ model = gtk_tree_view_get_model (tree_view);
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ sensitive = gedit_file_browser_widget_get_selected_directory (data->tree_widget, &iter);
+
+ if (sensitive) {
+ gtk_tree_model_get (model, &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI,
+ &uri, -1);
+
+ sensitive = gedit_utils_uri_has_file_scheme (uri);
+ g_free (uri);
+ }
+
+ gtk_action_set_sensitive (
+ gtk_action_group_get_action (data->single_selection_action_group,
+ "OpenTerminal"),
+ sensitive);
+}
+
+#define POPUP_UI "" \
+"<ui>" \
+" <popup name=\"FilePopup\">" \
+" <placeholder name=\"FilePopup_Opt1\">" \
+" <menuitem action=\"SetActiveRoot\"/>" \
+" </placeholder>" \
+" <placeholder name=\"FilePopup_Opt4\">" \
+" <menuitem action=\"OpenTerminal\"/>" \
+" </placeholder>" \
+" </popup>" \
+" <popup name=\"BookmarkPopup\">" \
+" <placeholder name=\"BookmarkPopup_Opt1\">" \
+" <menuitem action=\"SetActiveRoot\"/>" \
+" </placeholder>" \
+" </popup>" \
+"</ui>"
+
+static GtkActionEntry extra_actions[] =
+{
+ {"SetActiveRoot", GTK_STOCK_JUMP_TO, N_("_Set root to active document"),
+ NULL,
+ N_("Set the root to the active document location"),
+ G_CALLBACK (on_action_set_active_root)}
+};
+
+static GtkActionEntry extra_single_selection_actions[] = {
+ {"OpenTerminal", "utilities-terminal", N_("_Open terminal here"),
+ NULL,
+ N_("Open a terminal at the currently opened directory"),
+ G_CALLBACK (on_action_open_terminal)}
+};
+
+static void
+add_popup_ui (GeditWindow * window)
+{
+ GeditFileBrowserPluginData * data;
+ GtkUIManager * manager;
+ GtkActionGroup * action_group;
+ GError * error = NULL;
+
+ data = get_plugin_data (window);
+ manager = gedit_file_browser_widget_get_ui_manager (data->tree_widget);
+
+ action_group = gtk_action_group_new ("FileBrowserPluginExtra");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ extra_actions,
+ G_N_ELEMENTS (extra_actions),
+ window);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ data->action_group = action_group;
+
+ action_group = gtk_action_group_new ("FileBrowserPluginSingleSelectionExtra");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ extra_single_selection_actions,
+ G_N_ELEMENTS (extra_single_selection_actions),
+ window);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ data->single_selection_action_group = action_group;
+
+ data->merge_id = gtk_ui_manager_add_ui_from_string (manager,
+ POPUP_UI,
+ -1,
+ &error);
+
+ if (data->merge_id == 0) {
+ g_warning("Unable to merge UI: %s", error->message);
+ g_error_free(error);
+ }
+}
+
+static void
+remove_popup_ui (GeditWindow * window)
+{
+ GeditFileBrowserPluginData * data;
+ GtkUIManager * manager;
+
+ data = get_plugin_data (window);
+ manager = gedit_file_browser_widget_get_ui_manager (data->tree_widget);
+ gtk_ui_manager_remove_ui (manager, data->merge_id);
+
+ gtk_ui_manager_remove_action_group (manager, data->action_group);
+ g_object_unref (data->action_group);
+
+ gtk_ui_manager_remove_action_group (manager, data->single_selection_action_group);
+ g_object_unref (data->single_selection_action_group);
+}
+
+static void
+impl_updateui (GeditPlugin * plugin, GeditWindow * window)
+{
+ GeditFileBrowserPluginData * data;
+ GeditDocument * doc;
+
+ data = get_plugin_data (window);
+
+ doc = gedit_window_get_active_document (window);
+
+ gtk_action_set_sensitive (gtk_action_group_get_action (data->action_group,
+ "SetActiveRoot"),
+ doc != NULL &&
+ !gedit_document_is_untitled (doc));
+}
+
+static void
+impl_activate (GeditPlugin * plugin, GeditWindow * window)
+{
+ GeditPanel * panel;
+ GeditFileBrowserPluginData * data;
+ GtkWidget * image;
+ GdkPixbuf * pixbuf;
+ GeditFileBrowserStore * store;
+ gchar *data_dir;
+
+ data = g_new0 (GeditFileBrowserPluginData, 1);
+
+ data_dir = gedit_plugin_get_data_dir (plugin);
+ data->tree_widget = GEDIT_FILE_BROWSER_WIDGET (gedit_file_browser_widget_new (data_dir));
+ g_free (data_dir);
+
+ g_signal_connect (data->tree_widget,
+ "uri-activated",
+ G_CALLBACK (on_uri_activated_cb), window);
+
+ g_signal_connect (data->tree_widget,
+ "error", G_CALLBACK (on_error_cb), window);
+
+ g_signal_connect (data->tree_widget,
+ "notify::filter-pattern",
+ G_CALLBACK (on_filter_pattern_changed_cb),
+ window);
+
+ g_signal_connect (data->tree_widget,
+ "confirm-delete",
+ G_CALLBACK (on_confirm_delete_cb),
+ window);
+
+ g_signal_connect (data->tree_widget,
+ "confirm-no-trash",
+ G_CALLBACK (on_confirm_no_trash_cb),
+ window);
+
+ g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW
+ (gedit_file_browser_widget_get_browser_view
+ (data->tree_widget))),
+ "changed",
+ G_CALLBACK (on_selection_changed_cb),
+ window);
+
+ panel = gedit_window_get_side_panel (window);
+ pixbuf = gedit_file_browser_utils_pixbuf_from_theme("system-file-manager",
+ GTK_ICON_SIZE_MENU);
+
+ if (pixbuf) {
+ image = gtk_image_new_from_pixbuf(pixbuf);
+ g_object_unref(pixbuf);
+ } else {
+ image = gtk_image_new_from_stock(GTK_STOCK_INDEX, GTK_ICON_SIZE_MENU);
+ }
+
+ gtk_widget_show(image);
+ gedit_panel_add_item (panel,
+ GTK_WIDGET (data->tree_widget),
+ _("File Browser"),
+ image);
+ gtk_widget_show (GTK_WIDGET (data->tree_widget));
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, data);
+
+ add_popup_ui (window);
+
+ /* Restore filter options */
+ restore_filter (data);
+
+ /* Install caja preferences */
+ install_caja_prefs (data);
+
+ /* Connect signals to store the last visited location */
+ g_signal_connect (gedit_file_browser_widget_get_browser_view (data->tree_widget),
+ "notify::model",
+ G_CALLBACK (on_model_set_cb),
+ window);
+
+ store = gedit_file_browser_widget_get_browser_store (data->tree_widget);
+ g_signal_connect (store,
+ "notify::virtual-root",
+ G_CALLBACK (on_virtual_root_changed_cb),
+ window);
+
+ g_signal_connect (store,
+ "notify::filter-mode",
+ G_CALLBACK (on_filter_mode_changed_cb),
+ window);
+
+ g_signal_connect (store,
+ "rename",
+ G_CALLBACK (on_rename_cb),
+ window);
+
+ g_signal_connect (window,
+ "tab-added",
+ G_CALLBACK (on_tab_added_cb),
+ data);
+
+ /* Register messages on the bus */
+ gedit_file_browser_messages_register (window, data->tree_widget);
+
+ impl_updateui (plugin, window);
+}
+
+static void
+impl_deactivate (GeditPlugin * plugin, GeditWindow * window)
+{
+ GeditFileBrowserPluginData * data;
+ GeditPanel * panel;
+ MateConfClient *client;
+
+ data = get_plugin_data (window);
+
+ /* Unregister messages from the bus */
+ gedit_file_browser_messages_unregister (window);
+
+ /* Disconnect signals */
+ g_signal_handlers_disconnect_by_func (window,
+ G_CALLBACK (on_tab_added_cb),
+ data);
+
+ client = mateconf_client_get_default ();
+ mateconf_client_remove_dir (client, CAJA_CLICK_POLICY_BASE_KEY, NULL);
+
+ if (data->click_policy_handle)
+ mateconf_client_notify_remove (client, data->click_policy_handle);
+
+ if (data->enable_delete_handle)
+ mateconf_client_notify_remove (client, data->enable_delete_handle);
+
+ if (data->confirm_trash_handle)
+ mateconf_client_notify_remove (client, data->confirm_trash_handle);
+
+ g_object_unref (client);
+ remove_popup_ui (window);
+
+ panel = gedit_window_get_side_panel (window);
+ gedit_panel_remove_item (panel, GTK_WIDGET (data->tree_widget));
+
+ g_free (data);
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL);
+}
+
+static void
+filetree_plugin_class_init (GeditFileBrowserPluginClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass * plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ object_class->finalize = filetree_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+ plugin_class->update_ui = impl_updateui;
+
+ g_type_class_add_private (object_class,
+ sizeof (GeditFileBrowserPluginPrivate));
+}
+
+/* Callbacks */
+static void
+on_uri_activated_cb (GeditFileBrowserWidget * tree_widget,
+ gchar const *uri, GeditWindow * window)
+{
+ gedit_commands_load_uri (window, uri, NULL, 0);
+}
+
+static void
+on_error_cb (GeditFileBrowserWidget * tree_widget,
+ guint code, gchar const *message, GeditWindow * window)
+{
+ gchar * title;
+ GtkWidget * dlg;
+ GeditFileBrowserPluginData * data;
+
+ data = get_plugin_data (window);
+
+ /* Do not show the error when the root has been set automatically */
+ if (data->auto_root && (code == GEDIT_FILE_BROWSER_ERROR_SET_ROOT ||
+ code == GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY))
+ {
+ /* Show bookmarks */
+ gedit_file_browser_widget_show_bookmarks (data->tree_widget);
+ return;
+ }
+
+ switch (code) {
+ case GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY:
+ title =
+ _("An error occurred while creating a new directory");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_NEW_FILE:
+ title = _("An error occurred while creating a new file");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_RENAME:
+ title =
+ _
+ ("An error occurred while renaming a file or directory");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_DELETE:
+ title =
+ _
+ ("An error occurred while deleting a file or directory");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY:
+ title =
+ _
+ ("An error occurred while opening a directory in the file manager");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_SET_ROOT:
+ title =
+ _("An error occurred while setting a root directory");
+ break;
+ case GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY:
+ title =
+ _("An error occurred while loading a directory");
+ break;
+ default:
+ title = _("An error occurred");
+ break;
+ }
+
+ dlg = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "%s", title);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dlg),
+ "%s", message);
+
+ gtk_dialog_run (GTK_DIALOG (dlg));
+ gtk_widget_destroy (dlg);
+}
+
+static void
+on_model_set_cb (GeditFileBrowserView * widget,
+ GParamSpec *arg1,
+ GeditWindow * window)
+{
+ GeditFileBrowserPluginData * data = get_plugin_data (window);
+ GtkTreeModel * model;
+ MateConfClient * client;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (gedit_file_browser_widget_get_browser_view (data->tree_widget)));
+
+ if (model == NULL)
+ return;
+
+ client = mateconf_client_get_default ();
+ mateconf_client_set_bool (client,
+ FILE_BROWSER_BASE_KEY "/on_load/tree_view",
+ GEDIT_IS_FILE_BROWSER_STORE (model),
+ NULL);
+ g_object_unref (client);
+}
+
+static void
+on_filter_mode_changed_cb (GeditFileBrowserStore * model,
+ GParamSpec * param,
+ GeditWindow * window)
+{
+ MateConfClient * client;
+ GeditFileBrowserStoreFilterMode mode;
+
+ client = mateconf_client_get_default ();
+
+ if (!client)
+ return;
+
+ mode = gedit_file_browser_store_get_filter_mode (model);
+
+ if ((mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) &&
+ (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY)) {
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/filter_mode",
+ "hidden_and_binary",
+ NULL);
+ } else if (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN) {
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/filter_mode",
+ "hidden",
+ NULL);
+ } else if (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY) {
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/filter_mode",
+ "binary",
+ NULL);
+ } else {
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/filter_mode",
+ "none",
+ NULL);
+ }
+
+ g_object_unref (client);
+
+}
+
+static void
+on_rename_cb (GeditFileBrowserStore * store,
+ const gchar * olduri,
+ const gchar * newuri,
+ GeditWindow * window)
+{
+ GeditApp * app;
+ GList * documents;
+ GList * item;
+ GeditDocument * doc;
+ GFile * docfile;
+ GFile * oldfile;
+ GFile * newfile;
+ gchar * uri;
+
+ /* Find all documents and set its uri to newuri where it matches olduri */
+ app = gedit_app_get_default ();
+ documents = gedit_app_get_documents (app);
+
+ oldfile = g_file_new_for_uri (olduri);
+ newfile = g_file_new_for_uri (newuri);
+
+ for (item = documents; item; item = item->next) {
+ doc = GEDIT_DOCUMENT (item->data);
+ uri = gedit_document_get_uri (doc);
+
+ if (!uri)
+ continue;
+
+ docfile = g_file_new_for_uri (uri);
+
+ if (g_file_equal (docfile, oldfile)) {
+ gedit_document_set_uri (doc, newuri);
+ } else {
+ gchar *relative;
+
+ relative = g_file_get_relative_path (oldfile, docfile);
+
+ if (relative) {
+ /* relative now contains the part in docfile without
+ the prefix oldfile */
+
+ g_object_unref (docfile);
+ g_free (uri);
+
+ docfile = g_file_get_child (newfile, relative);
+ uri = g_file_get_uri (docfile);
+
+ gedit_document_set_uri (doc, uri);
+ }
+
+ g_free (relative);
+ }
+
+ g_free (uri);
+ g_object_unref (docfile);
+ }
+
+ g_object_unref (oldfile);
+ g_object_unref (newfile);
+
+ g_list_free (documents);
+}
+
+static void
+on_filter_pattern_changed_cb (GeditFileBrowserWidget * widget,
+ GParamSpec * param,
+ GeditWindow * window)
+{
+ MateConfClient * client;
+ gchar * pattern;
+
+ client = mateconf_client_get_default ();
+
+ if (!client)
+ return;
+
+ g_object_get (G_OBJECT (widget), "filter-pattern", &pattern, NULL);
+
+ if (pattern == NULL)
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/filter_pattern",
+ "",
+ NULL);
+ else
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/filter_pattern",
+ pattern,
+ NULL);
+
+ g_free (pattern);
+}
+
+static void
+on_virtual_root_changed_cb (GeditFileBrowserStore * store,
+ GParamSpec * param,
+ GeditWindow * window)
+{
+ GeditFileBrowserPluginData * data = get_plugin_data (window);
+ gchar * root;
+ gchar * virtual_root;
+ MateConfClient * client;
+
+ root = gedit_file_browser_store_get_root (store);
+
+ if (!root)
+ return;
+
+ client = mateconf_client_get_default ();
+
+ if (!client)
+ return;
+
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/on_load/root",
+ root,
+ NULL);
+
+ virtual_root = gedit_file_browser_store_get_virtual_root (store);
+
+ if (!virtual_root) {
+ /* Set virtual to same as root then */
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/on_load/virtual_root",
+ root,
+ NULL);
+ } else {
+ mateconf_client_set_string (client,
+ FILE_BROWSER_BASE_KEY "/on_load/virtual_root",
+ virtual_root,
+ NULL);
+ }
+
+ g_signal_handlers_disconnect_by_func (window,
+ G_CALLBACK (on_tab_added_cb),
+ data);
+
+ g_object_unref (client);
+ g_free (root);
+ g_free (virtual_root);
+}
+
+static void
+on_tab_added_cb (GeditWindow * window,
+ GeditTab * tab,
+ GeditFileBrowserPluginData * data)
+{
+ MateConfClient *client;
+ gboolean open;
+ gboolean load_default = TRUE;
+
+ client = mateconf_client_get_default ();
+
+ if (!client)
+ return;
+
+ open = mateconf_client_get_bool (client,
+ FILE_BROWSER_BASE_KEY "/open_at_first_doc",
+ NULL);
+
+ if (open) {
+ GeditDocument *doc;
+ gchar *uri;
+
+ doc = gedit_tab_get_document (tab);
+
+ uri = gedit_document_get_uri (doc);
+
+ if (uri != NULL && gedit_utils_uri_has_file_scheme (uri)) {
+ prepare_auto_root (data);
+ set_root_from_doc (data, doc);
+ load_default = FALSE;
+ }
+
+ g_free (uri);
+ }
+
+ if (load_default)
+ restore_default_location (data);
+
+ g_object_unref (client);
+
+ /* Disconnect this signal, it's only called once */
+ g_signal_handlers_disconnect_by_func (window,
+ G_CALLBACK (on_tab_added_cb),
+ data);
+}
+
+static gchar *
+get_filename_from_path (GtkTreeModel *model, GtkTreePath *path)
+{
+ GtkTreeIter iter;
+ gchar *uri;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri,
+ -1);
+
+ return gedit_file_browser_utils_uri_basename (uri);
+}
+
+static gboolean
+on_confirm_no_trash_cb (GeditFileBrowserWidget * widget,
+ GList * files,
+ GeditWindow * window)
+{
+ gchar *normal;
+ gchar *message;
+ gchar *secondary;
+ gboolean result;
+
+ message = _("Cannot move file to trash, do you\nwant to delete permanently?");
+
+ if (files->next == NULL) {
+ normal = gedit_file_browser_utils_file_basename (G_FILE (files->data));
+ secondary = g_strdup_printf (_("The file \"%s\" cannot be moved to the trash."), normal);
+ g_free (normal);
+ } else {
+ secondary = g_strdup (_("The selected files cannot be moved to the trash."));
+ }
+
+ result = gedit_file_browser_utils_confirmation_dialog (window,
+ GTK_MESSAGE_QUESTION,
+ message,
+ secondary,
+ GTK_STOCK_DELETE,
+ NULL);
+ g_free (secondary);
+
+ return result;
+}
+
+static gboolean
+on_confirm_delete_cb (GeditFileBrowserWidget *widget,
+ GeditFileBrowserStore *store,
+ GList *paths,
+ GeditWindow *window)
+{
+ gchar *normal;
+ gchar *message;
+ gchar *secondary;
+ gboolean result;
+ GeditFileBrowserPluginData *data;
+
+ data = get_plugin_data (window);
+
+ if (!data->confirm_trash)
+ return TRUE;
+
+ if (paths->next == NULL) {
+ normal = get_filename_from_path (GTK_TREE_MODEL (store), (GtkTreePath *)(paths->data));
+ message = g_strdup_printf (_("Are you sure you want to permanently delete \"%s\"?"), normal);
+ g_free (normal);
+ } else {
+ message = g_strdup (_("Are you sure you want to permanently delete the selected files?"));
+ }
+
+ secondary = _("If you delete an item, it is permanently lost.");
+
+ result = gedit_file_browser_utils_confirmation_dialog (window,
+ GTK_MESSAGE_QUESTION,
+ message,
+ secondary,
+ GTK_STOCK_DELETE,
+ NULL);
+
+ g_free (message);
+
+ return result;
+}
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-plugin.h b/plugins/filebrowser/gedit-file-browser-plugin.h
new file mode 100755
index 00000000..19ca86bf
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-plugin.h
@@ -0,0 +1,71 @@
+/*
+ * gedit-file-browser-plugin.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_FILE_BROWSER_PLUGIN_H__
+#define __GEDIT_FILE_BROWSER_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_FILE_BROWSER_PLUGIN (filetree_plugin_get_type ())
+#define GEDIT_FILE_BROWSER_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPlugin))
+#define GEDIT_FILE_BROWSER_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginClass))
+#define GEDIT_IS_FILE_BROWSER_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN))
+#define GEDIT_IS_FILE_BROWSER_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_FILE_BROWSER_PLUGIN))
+#define GEDIT_FILE_BROWSER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_FILE_BROWSER_PLUGIN, GeditFileBrowserPluginClass))
+
+/* Private structure type */
+typedef struct _GeditFileBrowserPluginPrivate GeditFileBrowserPluginPrivate;
+typedef struct _GeditFileBrowserPlugin GeditFileBrowserPlugin;
+typedef struct _GeditFileBrowserPluginClass GeditFileBrowserPluginClass;
+
+struct _GeditFileBrowserPlugin
+{
+ GeditPlugin parent_instance;
+
+ /*< private > */
+ GeditFileBrowserPluginPrivate *priv;
+};
+
+
+
+struct _GeditFileBrowserPluginClass
+{
+ GeditPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType filetree_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule * module);
+
+G_END_DECLS
+#endif /* __GEDIT_FILE_BROWSER_PLUGIN_H__ */
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-store.c b/plugins/filebrowser/gedit-file-browser-store.c
new file mode 100755
index 00000000..6c4f5b51
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-store.c
@@ -0,0 +1,3625 @@
+/*
+ * gedit-file-browser-store.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gio/gio.h>
+#include <gedit/gedit-plugin.h>
+#include <gedit/gedit-utils.h>
+
+#include "gedit-file-browser-store.h"
+#include "gedit-file-browser-marshal.h"
+#include "gedit-file-browser-enum-types.h"
+#include "gedit-file-browser-error.h"
+#include "gedit-file-browser-utils.h"
+
+#define GEDIT_FILE_BROWSER_STORE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GEDIT_TYPE_FILE_BROWSER_STORE, \
+ GeditFileBrowserStorePrivate))
+
+#define NODE_IS_DIR(node) (FILE_IS_DIR((node)->flags))
+#define NODE_IS_HIDDEN(node) (FILE_IS_HIDDEN((node)->flags))
+#define NODE_IS_TEXT(node) (FILE_IS_TEXT((node)->flags))
+#define NODE_LOADED(node) (FILE_LOADED((node)->flags))
+#define NODE_IS_FILTERED(node) (FILE_IS_FILTERED((node)->flags))
+#define NODE_IS_DUMMY(node) (FILE_IS_DUMMY((node)->flags))
+
+#define FILE_BROWSER_NODE_DIR(node) ((FileBrowserNodeDir *)(node))
+
+#define DIRECTORY_LOAD_ITEMS_PER_CALLBACK 100
+#define STANDARD_ATTRIBUTE_TYPES G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," \
+ G_FILE_ATTRIBUTE_STANDARD_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_ICON
+
+typedef struct _FileBrowserNode FileBrowserNode;
+typedef struct _FileBrowserNodeDir FileBrowserNodeDir;
+typedef struct _AsyncData AsyncData;
+typedef struct _AsyncNode AsyncNode;
+
+typedef gint (*SortFunc) (FileBrowserNode * node1,
+ FileBrowserNode * node2);
+
+struct _AsyncData
+{
+ GeditFileBrowserStore * model;
+ GCancellable * cancellable;
+ gboolean trash;
+ GList * files;
+ GList * iter;
+ gboolean removed;
+};
+
+struct _AsyncNode
+{
+ FileBrowserNodeDir *dir;
+ GCancellable *cancellable;
+ GSList *original_children;
+};
+
+typedef struct {
+ GeditFileBrowserStore * model;
+ gchar * virtual_root;
+ GMountOperation * operation;
+ GCancellable * cancellable;
+} MountInfo;
+
+struct _FileBrowserNode
+{
+ GFile *file;
+ guint flags;
+ gchar *name;
+
+ GdkPixbuf *icon;
+ GdkPixbuf *emblem;
+
+ FileBrowserNode *parent;
+ gint pos;
+ gboolean inserted;
+};
+
+struct _FileBrowserNodeDir
+{
+ FileBrowserNode node;
+ GSList *children;
+ GHashTable *hidden_file_hash;
+
+ GCancellable *cancellable;
+ GFileMonitor *monitor;
+ GeditFileBrowserStore *model;
+};
+
+struct _GeditFileBrowserStorePrivate
+{
+ FileBrowserNode *root;
+ FileBrowserNode *virtual_root;
+ GType column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NUM];
+
+ GeditFileBrowserStoreFilterMode filter_mode;
+ GeditFileBrowserStoreFilterFunc filter_func;
+ gpointer filter_user_data;
+
+ SortFunc sort_func;
+
+ GSList *async_handles;
+ MountInfo *mount_info;
+};
+
+static FileBrowserNode *model_find_node (GeditFileBrowserStore *model,
+ FileBrowserNode *node,
+ GFile *uri);
+static void model_remove_node (GeditFileBrowserStore * model,
+ FileBrowserNode * node,
+ GtkTreePath * path,
+ gboolean free_nodes);
+
+static void set_virtual_root_from_node (GeditFileBrowserStore * model,
+ FileBrowserNode * node);
+
+static void gedit_file_browser_store_iface_init (GtkTreeModelIface * iface);
+static GtkTreeModelFlags gedit_file_browser_store_get_flags (GtkTreeModel * tree_model);
+static gint gedit_file_browser_store_get_n_columns (GtkTreeModel * tree_model);
+static GType gedit_file_browser_store_get_column_type (GtkTreeModel * tree_model,
+ gint index);
+static gboolean gedit_file_browser_store_get_iter (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ GtkTreePath * path);
+static GtkTreePath *gedit_file_browser_store_get_path (GtkTreeModel * tree_model,
+ GtkTreeIter * iter);
+static void gedit_file_browser_store_get_value (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ gint column,
+ GValue * value);
+static gboolean gedit_file_browser_store_iter_next (GtkTreeModel * tree_model,
+ GtkTreeIter * iter);
+static gboolean gedit_file_browser_store_iter_children (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ GtkTreeIter * parent);
+static gboolean gedit_file_browser_store_iter_has_child (GtkTreeModel * tree_model,
+ GtkTreeIter * iter);
+static gint gedit_file_browser_store_iter_n_children (GtkTreeModel * tree_model,
+ GtkTreeIter * iter);
+static gboolean gedit_file_browser_store_iter_nth_child (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ GtkTreeIter * parent,
+ gint n);
+static gboolean gedit_file_browser_store_iter_parent (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ GtkTreeIter * child);
+static void gedit_file_browser_store_row_inserted (GtkTreeModel * tree_model,
+ GtkTreePath * path,
+ GtkTreeIter * iter);
+
+static void gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface * iface);
+static gboolean gedit_file_browser_store_row_draggable (GtkTreeDragSource * drag_source,
+ GtkTreePath * path);
+static gboolean gedit_file_browser_store_drag_data_delete (GtkTreeDragSource * drag_source,
+ GtkTreePath * path);
+static gboolean gedit_file_browser_store_drag_data_get (GtkTreeDragSource * drag_source,
+ GtkTreePath * path,
+ GtkSelectionData * selection_data);
+
+static void file_browser_node_free (GeditFileBrowserStore * model,
+ FileBrowserNode * node);
+static void model_add_node (GeditFileBrowserStore * model,
+ FileBrowserNode * child,
+ FileBrowserNode * parent);
+static void model_clear (GeditFileBrowserStore * model,
+ gboolean free_nodes);
+static gint model_sort_default (FileBrowserNode * node1,
+ FileBrowserNode * node2);
+static void model_check_dummy (GeditFileBrowserStore * model,
+ FileBrowserNode * node);
+static void next_files_async (GFileEnumerator * enumerator,
+ AsyncNode * async);
+
+GEDIT_PLUGIN_DEFINE_TYPE_WITH_CODE (GeditFileBrowserStore, gedit_file_browser_store,
+ G_TYPE_OBJECT,
+ GEDIT_PLUGIN_IMPLEMENT_INTERFACE (gedit_file_browser_store_tree_model,
+ GTK_TYPE_TREE_MODEL,
+ gedit_file_browser_store_iface_init)
+ GEDIT_PLUGIN_IMPLEMENT_INTERFACE (gedit_file_browser_store_drag_source,
+ GTK_TYPE_TREE_DRAG_SOURCE,
+ gedit_file_browser_store_drag_source_init))
+
+/* Properties */
+enum {
+ PROP_0,
+
+ PROP_ROOT,
+ PROP_VIRTUAL_ROOT,
+ PROP_FILTER_MODE
+};
+
+/* Signals */
+enum
+{
+ BEGIN_LOADING,
+ END_LOADING,
+ ERROR,
+ NO_TRASH,
+ RENAME,
+ BEGIN_REFRESH,
+ END_REFRESH,
+ UNLOAD,
+ NUM_SIGNALS
+};
+
+static guint model_signals[NUM_SIGNALS] = { 0 };
+
+static void
+cancel_mount_operation (GeditFileBrowserStore *obj)
+{
+ if (obj->priv->mount_info != NULL)
+ {
+ obj->priv->mount_info->model = NULL;
+ g_cancellable_cancel (obj->priv->mount_info->cancellable);
+ obj->priv->mount_info = NULL;
+ }
+}
+
+static void
+gedit_file_browser_store_finalize (GObject * object)
+{
+ GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object);
+ GSList *item;
+
+ /* Free all the nodes */
+ file_browser_node_free (obj, obj->priv->root);
+
+ /* Cancel any asynchronous operations */
+ for (item = obj->priv->async_handles; item; item = item->next)
+ {
+ AsyncData *data = (AsyncData *) (item->data);
+ g_cancellable_cancel (data->cancellable);
+
+ data->removed = TRUE;
+ }
+
+ cancel_mount_operation (obj);
+
+ g_slist_free (obj->priv->async_handles);
+ G_OBJECT_CLASS (gedit_file_browser_store_parent_class)->finalize (object);
+}
+
+static void
+set_gvalue_from_node (GValue *value,
+ FileBrowserNode *node)
+{
+ gchar * uri;
+
+ if (node == NULL || !node->file) {
+ g_value_set_string (value, NULL);
+ } else {
+ uri = g_file_get_uri (node->file);
+ g_value_take_string (value, uri);
+ }
+}
+
+static void
+gedit_file_browser_store_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object);
+
+ switch (prop_id)
+ {
+ case PROP_ROOT:
+ set_gvalue_from_node (value, obj->priv->root);
+ break;
+ case PROP_VIRTUAL_ROOT:
+ set_gvalue_from_node (value, obj->priv->virtual_root);
+ break;
+ case PROP_FILTER_MODE:
+ g_value_set_flags (value, obj->priv->filter_mode);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_store_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserStore *obj = GEDIT_FILE_BROWSER_STORE (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILTER_MODE:
+ gedit_file_browser_store_set_filter_mode (obj,
+ g_value_get_flags (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_store_class_init (GeditFileBrowserStoreClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gedit_file_browser_store_finalize;
+
+ object_class->get_property = gedit_file_browser_store_get_property;
+ object_class->set_property = gedit_file_browser_store_set_property;
+
+ g_object_class_install_property (object_class, PROP_ROOT,
+ g_param_spec_string ("root",
+ "Root",
+ "The root uri",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_VIRTUAL_ROOT,
+ g_param_spec_string ("virtual-root",
+ "Virtual Root",
+ "The virtual root uri",
+ NULL,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (object_class, PROP_FILTER_MODE,
+ g_param_spec_flags ("filter-mode",
+ "Filter Mode",
+ "The filter mode",
+ GEDIT_TYPE_FILE_BROWSER_STORE_FILTER_MODE,
+ gedit_file_browser_store_filter_mode_get_default (),
+ G_PARAM_READWRITE));
+
+ model_signals[BEGIN_LOADING] =
+ g_signal_new ("begin-loading",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass,
+ begin_loading), NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1,
+ GTK_TYPE_TREE_ITER);
+ model_signals[END_LOADING] =
+ g_signal_new ("end-loading",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass,
+ end_loading), NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED, G_TYPE_NONE, 1,
+ GTK_TYPE_TREE_ITER);
+ model_signals[ERROR] =
+ g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass,
+ error), NULL, NULL,
+ gedit_file_browser_marshal_VOID__UINT_STRING,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+ model_signals[NO_TRASH] =
+ g_signal_new ("no-trash", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass,
+ no_trash), g_signal_accumulator_true_handled, NULL,
+ gedit_file_browser_marshal_BOOL__POINTER,
+ G_TYPE_BOOLEAN, 1, G_TYPE_POINTER);
+ model_signals[RENAME] =
+ g_signal_new ("rename",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass,
+ rename), NULL, NULL,
+ gedit_file_browser_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+ model_signals[BEGIN_REFRESH] =
+ g_signal_new ("begin-refresh",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass,
+ begin_refresh), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ model_signals[END_REFRESH] =
+ g_signal_new ("end-refresh",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass,
+ end_refresh), NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+ model_signals[UNLOAD] =
+ g_signal_new ("unload",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserStoreClass,
+ unload), NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ g_type_class_add_private (object_class,
+ sizeof (GeditFileBrowserStorePrivate));
+}
+
+static void
+gedit_file_browser_store_iface_init (GtkTreeModelIface * iface)
+{
+ iface->get_flags = gedit_file_browser_store_get_flags;
+ iface->get_n_columns = gedit_file_browser_store_get_n_columns;
+ iface->get_column_type = gedit_file_browser_store_get_column_type;
+ iface->get_iter = gedit_file_browser_store_get_iter;
+ iface->get_path = gedit_file_browser_store_get_path;
+ iface->get_value = gedit_file_browser_store_get_value;
+ iface->iter_next = gedit_file_browser_store_iter_next;
+ iface->iter_children = gedit_file_browser_store_iter_children;
+ iface->iter_has_child = gedit_file_browser_store_iter_has_child;
+ iface->iter_n_children = gedit_file_browser_store_iter_n_children;
+ iface->iter_nth_child = gedit_file_browser_store_iter_nth_child;
+ iface->iter_parent = gedit_file_browser_store_iter_parent;
+ iface->row_inserted = gedit_file_browser_store_row_inserted;
+}
+
+static void
+gedit_file_browser_store_drag_source_init (GtkTreeDragSourceIface * iface)
+{
+ iface->row_draggable = gedit_file_browser_store_row_draggable;
+ iface->drag_data_delete = gedit_file_browser_store_drag_data_delete;
+ iface->drag_data_get = gedit_file_browser_store_drag_data_get;
+}
+
+static void
+gedit_file_browser_store_init (GeditFileBrowserStore * obj)
+{
+ obj->priv = GEDIT_FILE_BROWSER_STORE_GET_PRIVATE (obj);
+
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_URI] =
+ G_TYPE_STRING;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_NAME] =
+ G_TYPE_STRING;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS] =
+ G_TYPE_UINT;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_ICON] =
+ GDK_TYPE_PIXBUF;
+ obj->priv->column_types[GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM] =
+ GDK_TYPE_PIXBUF;
+
+ // Default filter mode is hiding the hidden files
+ obj->priv->filter_mode = gedit_file_browser_store_filter_mode_get_default ();
+ obj->priv->sort_func = model_sort_default;
+}
+
+static gboolean
+node_has_parent (FileBrowserNode * node, FileBrowserNode * parent)
+{
+ if (node->parent == NULL)
+ return FALSE;
+
+ if (node->parent == parent)
+ return TRUE;
+
+ return node_has_parent (node->parent, parent);
+}
+
+static gboolean
+node_in_tree (GeditFileBrowserStore * model, FileBrowserNode * node)
+{
+ return node_has_parent (node, model->priv->virtual_root);
+}
+
+static gboolean
+model_node_visibility (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ if (node == NULL)
+ return FALSE;
+
+ if (NODE_IS_DUMMY (node))
+ return !NODE_IS_HIDDEN (node);
+
+ if (node == model->priv->virtual_root)
+ return TRUE;
+
+ if (!node_has_parent (node, model->priv->virtual_root))
+ return FALSE;
+
+ return !NODE_IS_FILTERED (node);
+}
+
+static gboolean
+model_node_inserted (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ return node == model->priv->virtual_root || (model_node_visibility (model, node) && node->inserted);
+}
+
+/* Interface implementation */
+
+static GtkTreeModelFlags
+gedit_file_browser_store_get_flags (GtkTreeModel * tree_model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model),
+ (GtkTreeModelFlags) 0);
+
+ return GTK_TREE_MODEL_ITERS_PERSIST;
+}
+
+static gint
+gedit_file_browser_store_get_n_columns (GtkTreeModel * tree_model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), 0);
+
+ return GEDIT_FILE_BROWSER_STORE_COLUMN_NUM;
+}
+
+static GType
+gedit_file_browser_store_get_column_type (GtkTreeModel * tree_model, gint idx)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model),
+ G_TYPE_INVALID);
+ g_return_val_if_fail (idx < GEDIT_FILE_BROWSER_STORE_COLUMN_NUM &&
+ idx >= 0, G_TYPE_INVALID);
+
+ return GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[idx];
+}
+
+static gboolean
+gedit_file_browser_store_get_iter (GtkTreeModel * tree_model,
+ GtkTreeIter * iter, GtkTreePath * path)
+{
+ gint * indices, depth, i;
+ FileBrowserNode * node;
+ GeditFileBrowserStore * model;
+ gint num;
+
+ g_assert (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_assert (path != NULL);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+ indices = gtk_tree_path_get_indices (path);
+ depth = gtk_tree_path_get_depth (path);
+ node = model->priv->virtual_root;
+
+ for (i = 0; i < depth; ++i) {
+ GSList * item;
+
+ if (node == NULL)
+ return FALSE;
+
+ num = 0;
+
+ if (!NODE_IS_DIR (node))
+ return FALSE;
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) {
+ FileBrowserNode * child;
+
+ child = (FileBrowserNode *) (item->data);
+
+ if (model_node_inserted (model, child)) {
+ if (num == indices[i]) {
+ node = child;
+ break;
+ }
+
+ num++;
+ }
+ }
+
+ if (item == NULL)
+ return FALSE;
+
+ node = (FileBrowserNode *) (item->data);
+ }
+
+ iter->user_data = node;
+ iter->user_data2 = NULL;
+ iter->user_data3 = NULL;
+
+ return node != NULL;
+}
+
+static GtkTreePath *
+gedit_file_browser_store_get_path_real (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ GtkTreePath *path;
+ gint num = 0;
+
+ path = gtk_tree_path_new ();
+
+ while (node != model->priv->virtual_root) {
+ GSList *item;
+
+ if (node->parent == NULL) {
+ gtk_tree_path_free (path);
+ return NULL;
+ }
+
+ num = 0;
+
+ for (item = FILE_BROWSER_NODE_DIR (node->parent)->children; item; item = item->next) {
+ FileBrowserNode *check;
+
+ check = (FileBrowserNode *) (item->data);
+
+ if (model_node_visibility (model, check) && (check == node || check->inserted)) {
+ if (check == node) {
+ gtk_tree_path_prepend_index (path,
+ num);
+ break;
+ }
+
+ ++num;
+ } else if (check == node) {
+ if (NODE_IS_DUMMY (node))
+ g_warning ("Dummy not visible???");
+
+ gtk_tree_path_free (path);
+ return NULL;
+ }
+ }
+
+ node = node->parent;
+ }
+
+ return path;
+}
+
+static GtkTreePath *
+gedit_file_browser_store_get_path (GtkTreeModel * tree_model,
+ GtkTreeIter * iter)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), NULL);
+ g_return_val_if_fail (iter != NULL, NULL);
+ g_return_val_if_fail (iter->user_data != NULL, NULL);
+
+ return gedit_file_browser_store_get_path_real (GEDIT_FILE_BROWSER_STORE (tree_model),
+ (FileBrowserNode *) (iter->user_data));
+}
+
+static void
+gedit_file_browser_store_get_value (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ gint column,
+ GValue * value)
+{
+ FileBrowserNode *node;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ node = (FileBrowserNode *) (iter->user_data);
+
+ g_value_init (value, GEDIT_FILE_BROWSER_STORE (tree_model)->priv->column_types[column]);
+
+ switch (column) {
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_URI:
+ set_gvalue_from_node (value, node);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_NAME:
+ g_value_set_string (value, node->name);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS:
+ g_value_set_uint (value, node->flags);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_ICON:
+ g_value_set_object (value, node->icon);
+ break;
+ case GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM:
+ g_value_set_object (value, node->emblem);
+ break;
+ default:
+ g_return_if_reached ();
+ }
+}
+
+static gboolean
+gedit_file_browser_store_iter_next (GtkTreeModel * tree_model,
+ GtkTreeIter * iter)
+{
+ GeditFileBrowserStore * model;
+ FileBrowserNode * node;
+ GSList * item;
+ GSList * first;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model),
+ FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (iter->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+ node = (FileBrowserNode *) (iter->user_data);
+
+ if (node->parent == NULL)
+ return FALSE;
+
+ first = g_slist_next (g_slist_find (FILE_BROWSER_NODE_DIR (node->parent)->children, node));
+
+ for (item = first; item; item = item->next) {
+ if (model_node_inserted (model, (FileBrowserNode *) (item->data))) {
+ iter->user_data = item->data;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gedit_file_browser_store_iter_children (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ GtkTreeIter * parent)
+{
+ FileBrowserNode * node;
+ GeditFileBrowserStore * model;
+ GSList * item;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model),
+ FALSE);
+ g_return_val_if_fail (parent == NULL
+ || parent->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (parent == NULL)
+ node = model->priv->virtual_root;
+ else
+ node = (FileBrowserNode *) (parent->user_data);
+
+ if (node == NULL)
+ return FALSE;
+
+ if (!NODE_IS_DIR (node))
+ return FALSE;
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) {
+ if (model_node_inserted (model, (FileBrowserNode *) (item->data))) {
+ iter->user_data = item->data;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+filter_tree_model_iter_has_child_real (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ GSList *item;
+
+ if (!NODE_IS_DIR (node))
+ return FALSE;
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next) {
+ if (model_node_inserted (model, (FileBrowserNode *) (item->data)))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gedit_file_browser_store_iter_has_child (GtkTreeModel * tree_model,
+ GtkTreeIter * iter)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model),
+ FALSE);
+ g_return_val_if_fail (iter == NULL
+ || iter->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (iter == NULL)
+ node = model->priv->virtual_root;
+ else
+ node = (FileBrowserNode *) (iter->user_data);
+
+ return filter_tree_model_iter_has_child_real (model, node);
+}
+
+static gint
+gedit_file_browser_store_iter_n_children (GtkTreeModel * tree_model,
+ GtkTreeIter * iter)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+ GSList *item;
+ gint num = 0;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model),
+ FALSE);
+ g_return_val_if_fail (iter == NULL
+ || iter->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (iter == NULL)
+ node = model->priv->virtual_root;
+ else
+ node = (FileBrowserNode *) (iter->user_data);
+
+ if (!NODE_IS_DIR (node))
+ return 0;
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item; item = item->next)
+ if (model_node_inserted (model, (FileBrowserNode *) (item->data)))
+ ++num;
+
+ return num;
+}
+
+static gboolean
+gedit_file_browser_store_iter_nth_child (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ GtkTreeIter * parent, gint n)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+ GSList *item;
+ gint num = 0;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model),
+ FALSE);
+ g_return_val_if_fail (parent == NULL
+ || parent->user_data != NULL, FALSE);
+
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (parent == NULL)
+ node = model->priv->virtual_root;
+ else
+ node = (FileBrowserNode *) (parent->user_data);
+
+ if (!NODE_IS_DIR (node))
+ return FALSE;
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item;
+ item = item->next) {
+ if (model_node_inserted (model, (FileBrowserNode *) (item->data))) {
+ if (num == n) {
+ iter->user_data = item->data;
+ return TRUE;
+ }
+
+ ++num;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gedit_file_browser_store_iter_parent (GtkTreeModel * tree_model,
+ GtkTreeIter * iter,
+ GtkTreeIter * child)
+{
+ FileBrowserNode *node;
+ GeditFileBrowserStore *model;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model), FALSE);
+ g_return_val_if_fail (child != NULL, FALSE);
+ g_return_val_if_fail (child->user_data != NULL, FALSE);
+
+ node = (FileBrowserNode *) (child->user_data);
+ model = GEDIT_FILE_BROWSER_STORE (tree_model);
+
+ if (!node_in_tree (model, node))
+ return FALSE;
+
+ if (node->parent == NULL)
+ return FALSE;
+
+ iter->user_data = node->parent;
+ return TRUE;
+}
+
+static void
+gedit_file_browser_store_row_inserted (GtkTreeModel * tree_model,
+ GtkTreePath * path,
+ GtkTreeIter * iter)
+{
+ FileBrowserNode * node = (FileBrowserNode *)(iter->user_data);
+
+ node->inserted = TRUE;
+}
+
+static gboolean
+gedit_file_browser_store_row_draggable (GtkTreeDragSource * drag_source,
+ GtkTreePath * path)
+{
+ GtkTreeIter iter;
+ GeditFileBrowserStoreFlag flags;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source),
+ &iter, path))
+ {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ return !FILE_IS_DUMMY(flags);
+}
+
+static gboolean
+gedit_file_browser_store_drag_data_delete (GtkTreeDragSource * drag_source,
+ GtkTreePath * path)
+{
+ return FALSE;
+}
+
+static gboolean
+gedit_file_browser_store_drag_data_get (GtkTreeDragSource * drag_source,
+ GtkTreePath * path,
+ GtkSelectionData * selection_data)
+{
+ GtkTreeIter iter;
+ gchar *uri;
+ gchar *uris[2] = {0, };
+ gboolean ret;
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source),
+ &iter, path))
+ {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri,
+ -1);
+
+ g_assert (uri);
+
+ uris[0] = uri;
+ ret = gtk_selection_data_set_uris (selection_data, uris);
+
+ g_free (uri);
+
+ return ret;
+}
+
+#define FILTER_HIDDEN(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN)
+#define FILTER_BINARY(mode) (mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY)
+
+/* Private */
+static void
+model_begin_loading (GeditFileBrowserStore * model, FileBrowserNode * node)
+{
+ GtkTreeIter iter;
+
+ iter.user_data = node;
+ g_signal_emit (model, model_signals[BEGIN_LOADING], 0, &iter);
+}
+
+static void
+model_end_loading (GeditFileBrowserStore * model, FileBrowserNode * node)
+{
+ GtkTreeIter iter;
+
+ iter.user_data = node;
+ g_signal_emit (model, model_signals[END_LOADING], 0, &iter);
+}
+
+static void
+model_node_update_visibility (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ GtkTreeIter iter;
+
+ node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+
+ if (FILTER_HIDDEN (model->priv->filter_mode) &&
+ NODE_IS_HIDDEN (node))
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+ else if (FILTER_BINARY (model->priv->filter_mode) &&
+ (!NODE_IS_TEXT (node) && !NODE_IS_DIR (node)))
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+ else if (model->priv->filter_func) {
+ iter.user_data = node;
+
+ if (!model->priv->
+ filter_func (model, &iter,
+ model->priv->filter_user_data))
+ node->flags |=
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED;
+ }
+}
+
+static gint
+collate_nodes (FileBrowserNode * node1, FileBrowserNode * node2)
+{
+ if (node1->name == NULL)
+ return -1;
+ else if (node2->name == NULL)
+ return 1;
+ else {
+ gchar *k1, *k2;
+ gint result;
+
+ k1 = g_utf8_collate_key_for_filename (node1->name, -1);
+ k2 = g_utf8_collate_key_for_filename (node2->name, -1);
+
+ result = strcmp (k1, k2);
+
+ g_free (k1);
+ g_free (k2);
+
+ return result;
+ }
+}
+
+static gint
+model_sort_default (FileBrowserNode * node1, FileBrowserNode * node2)
+{
+ gint f1;
+ gint f2;
+
+ f1 = NODE_IS_DUMMY (node1);
+ f2 = NODE_IS_DUMMY (node2);
+
+ if (f1 && f2)
+ {
+ return 0;
+ }
+ else if (f1 || f2)
+ {
+ return f1 ? -1 : 1;
+ }
+
+ f1 = NODE_IS_DIR (node1);
+ f2 = NODE_IS_DIR (node2);
+
+ if (f1 != f2)
+ {
+ return f1 ? -1 : 1;
+ }
+
+ f1 = NODE_IS_HIDDEN (node1);
+ f2 = NODE_IS_HIDDEN (node2);
+
+ if (f1 != f2)
+ {
+ return f2 ? -1 : 1;
+ }
+
+ return collate_nodes (node1, node2);
+}
+
+static void
+model_resort_node (GeditFileBrowserStore * model, FileBrowserNode * node)
+{
+ FileBrowserNodeDir *dir;
+ GSList *item;
+ FileBrowserNode *child;
+ gint pos = 0;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ gint *neworder;
+
+ dir = FILE_BROWSER_NODE_DIR (node->parent);
+
+ if (!model_node_visibility (model, node->parent)) {
+ /* Just sort the children of the parent */
+ dir->children = g_slist_sort (dir->children,
+ (GCompareFunc) (model->priv->
+ sort_func));
+ } else {
+ /* Store current positions */
+ for (item = dir->children; item; item = item->next) {
+ child = (FileBrowserNode *) (item->data);
+
+ if (model_node_visibility (model, child))
+ child->pos = pos++;
+ }
+
+ dir->children = g_slist_sort (dir->children,
+ (GCompareFunc) (model->priv->
+ sort_func));
+ neworder = g_new (gint, pos);
+ pos = 0;
+
+ /* Store the new positions */
+ for (item = dir->children; item; item = item->next) {
+ child = (FileBrowserNode *) (item->data);
+
+ if (model_node_visibility (model, child))
+ neworder[pos++] = child->pos;
+ }
+
+ iter.user_data = node->parent;
+ path =
+ gedit_file_browser_store_get_path_real (model,
+ node->parent);
+
+ gtk_tree_model_rows_reordered (GTK_TREE_MODEL (model),
+ path, &iter, neworder);
+
+ g_free (neworder);
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+row_changed (GeditFileBrowserStore * model,
+ GtkTreePath ** path,
+ GtkTreeIter * iter)
+{
+ GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path);
+
+ /* Insert a copy of the actual path here because the row-inserted
+ signal may alter the path */
+ gtk_tree_model_row_changed (GTK_TREE_MODEL(model), *path, iter);
+ gtk_tree_path_free (*path);
+
+ *path = gtk_tree_row_reference_get_path (ref);
+ gtk_tree_row_reference_free (ref);
+}
+
+static void
+row_inserted (GeditFileBrowserStore * model,
+ GtkTreePath ** path,
+ GtkTreeIter * iter)
+{
+ /* This function creates a row reference for the path because it's
+ uncertain what might change the actual model/view when we insert
+ a node, maybe another directory load is triggered for example.
+ Because functions that use this function rely on the notion that
+ the path remains pointed towards the inserted node, we use the
+ reference to keep track. */
+ GtkTreeRowReference *ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), *path);
+ GtkTreePath * copy = gtk_tree_path_copy (*path);
+
+ gtk_tree_model_row_inserted (GTK_TREE_MODEL(model), copy, iter);
+ gtk_tree_path_free (copy);
+
+ if (ref)
+ {
+ gtk_tree_path_free (*path);
+
+ /* To restore the path, we get the path from the reference. But, since
+ we inserted a row, the path will be one index further than the
+ actual path of our node. We therefore call gtk_tree_path_prev */
+ *path = gtk_tree_row_reference_get_path (ref);
+ gtk_tree_path_prev (*path);
+ }
+
+ gtk_tree_row_reference_free (ref);
+}
+
+static void
+row_deleted (GeditFileBrowserStore * model,
+ const GtkTreePath * path)
+{
+ GtkTreePath *copy = gtk_tree_path_copy (path);
+
+ /* Delete a copy of the actual path here because the row-deleted
+ signal may alter the path */
+ gtk_tree_model_row_deleted (GTK_TREE_MODEL(model), copy);
+ gtk_tree_path_free (copy);
+}
+
+static void
+model_refilter_node (GeditFileBrowserStore * model,
+ FileBrowserNode * node,
+ GtkTreePath ** path)
+{
+ gboolean old_visible;
+ gboolean new_visible;
+ FileBrowserNodeDir *dir;
+ GSList *item;
+ GtkTreeIter iter;
+ GtkTreePath *tmppath = NULL;
+ gboolean in_tree;
+
+ if (node == NULL)
+ return;
+
+ old_visible = model_node_visibility (model, node);
+ model_node_update_visibility (model, node);
+
+ in_tree = node_in_tree (model, node);
+
+ if (path == NULL)
+ {
+ if (in_tree)
+ tmppath = gedit_file_browser_store_get_path_real (model,
+ node);
+ else
+ tmppath = gtk_tree_path_new_first ();
+
+ path = &tmppath;
+ }
+
+ if (NODE_IS_DIR (node)) {
+ if (in_tree)
+ gtk_tree_path_down (*path);
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ for (item = dir->children; item; item = item->next) {
+ model_refilter_node (model,
+ (FileBrowserNode *) (item->data),
+ path);
+ }
+
+ if (in_tree)
+ gtk_tree_path_up (*path);
+ }
+
+ if (in_tree) {
+ new_visible = model_node_visibility (model, node);
+
+ if (old_visible != new_visible) {
+ if (old_visible) {
+ node->inserted = FALSE;
+ row_deleted (model, *path);
+ } else {
+ iter.user_data = node;
+ row_inserted (model, path, &iter);
+ gtk_tree_path_next (*path);
+ }
+ } else if (old_visible) {
+ gtk_tree_path_next (*path);
+ }
+ }
+
+ model_check_dummy (model, node);
+
+ if (tmppath)
+ gtk_tree_path_free (tmppath);
+}
+
+static void
+model_refilter (GeditFileBrowserStore * model)
+{
+ model_refilter_node (model, model->priv->root, NULL);
+}
+
+static void
+file_browser_node_set_name (FileBrowserNode * node)
+{
+ g_free (node->name);
+
+ if (node->file) {
+ node->name = gedit_file_browser_utils_file_basename (node->file);
+ } else {
+ node->name = NULL;
+ }
+}
+
+static void
+file_browser_node_init (FileBrowserNode * node, GFile * file,
+ FileBrowserNode * parent)
+{
+ if (file != NULL) {
+ node->file = g_object_ref (file);
+ file_browser_node_set_name (node);
+ }
+
+ node->parent = parent;
+}
+
+static FileBrowserNode *
+file_browser_node_new (GFile * file, FileBrowserNode * parent)
+{
+ FileBrowserNode *node = g_slice_new0 (FileBrowserNode);
+
+ file_browser_node_init (node, file, parent);
+ return node;
+}
+
+static FileBrowserNode *
+file_browser_node_dir_new (GeditFileBrowserStore * model,
+ GFile * file, FileBrowserNode * parent)
+{
+ FileBrowserNode *node =
+ (FileBrowserNode *) g_slice_new0 (FileBrowserNodeDir);
+
+ file_browser_node_init (node, file, parent);
+
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY;
+
+ FILE_BROWSER_NODE_DIR (node)->model = model;
+
+ return node;
+}
+
+static void
+file_browser_node_free_children (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ GSList *item;
+
+ if (node == NULL)
+ return;
+
+ if (NODE_IS_DIR (node)) {
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item;
+ item = item->next)
+ file_browser_node_free (model,
+ (FileBrowserNode *) (item->
+ data));
+
+ g_slist_free (FILE_BROWSER_NODE_DIR (node)->children);
+ FILE_BROWSER_NODE_DIR (node)->children = NULL;
+
+ /* This node is no longer loaded */
+ node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED;
+ }
+}
+
+static void
+file_browser_node_free (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ gchar *uri;
+
+ if (node == NULL)
+ return;
+
+ if (NODE_IS_DIR (node))
+ {
+ FileBrowserNodeDir *dir;
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ if (dir->cancellable) {
+ g_cancellable_cancel (dir->cancellable);
+ g_object_unref (dir->cancellable);
+
+ model_end_loading (model, node);
+ }
+
+ file_browser_node_free_children (model, node);
+
+ if (dir->monitor) {
+ g_file_monitor_cancel (dir->monitor);
+ g_object_unref (dir->monitor);
+ }
+
+ if (dir->hidden_file_hash)
+ g_hash_table_destroy (dir->hidden_file_hash);
+ }
+
+ if (node->file)
+ {
+ uri = g_file_get_uri (node->file);
+ g_signal_emit (model, model_signals[UNLOAD], 0, uri);
+
+ g_free (uri);
+ g_object_unref (node->file);
+ }
+
+ if (node->icon)
+ g_object_unref (node->icon);
+
+ if (node->emblem)
+ g_object_unref (node->emblem);
+
+ g_free (node->name);
+
+ if (NODE_IS_DIR (node))
+ g_slice_free (FileBrowserNodeDir, (FileBrowserNodeDir *)node);
+ else
+ g_slice_free (FileBrowserNode, (FileBrowserNode *)node);
+}
+
+/**
+ * model_remove_node_children:
+ * @model: the #GeditFileBrowserStore
+ * @node: the FileBrowserNode to remove
+ * @path: the path of the node, or NULL to let the path be calculated
+ * @free_nodes: whether to also remove the nodes from memory
+ *
+ * Removes all the children of node from the model. This function is used
+ * to remove the child nodes from the _model_. Don't use it to just free
+ * a node.
+ **/
+static void
+model_remove_node_children (GeditFileBrowserStore * model,
+ FileBrowserNode * node,
+ GtkTreePath * path,
+ gboolean free_nodes)
+{
+ FileBrowserNodeDir *dir;
+ GtkTreePath *path_child;
+ GSList *list;
+ GSList *item;
+
+ if (node == NULL || !NODE_IS_DIR (node))
+ return;
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ if (dir->children == NULL)
+ return;
+
+ if (!model_node_visibility (model, node)) {
+ // Node is invisible and therefore the children can just
+ // be freed
+ if (free_nodes)
+ file_browser_node_free_children (model, node);
+
+ return;
+ }
+
+ if (path == NULL)
+ path_child =
+ gedit_file_browser_store_get_path_real (model, node);
+ else
+ path_child = gtk_tree_path_copy (path);
+
+ gtk_tree_path_down (path_child);
+
+ list = g_slist_copy (dir->children);
+
+ for (item = list; item; item = item->next) {
+ model_remove_node (model, (FileBrowserNode *) (item->data),
+ path_child, free_nodes);
+ }
+
+ g_slist_free (list);
+ gtk_tree_path_free (path_child);
+}
+
+/**
+ * model_remove_node:
+ * @model: the #GeditFileBrowserStore
+ * @node: the FileBrowserNode to remove
+ * @path: the path to use to remove this node, or NULL to use the path
+ * calculated from the node itself
+ * @free_nodes: whether to also remove the nodes from memory
+ *
+ * Removes this node and all its children from the model. This function is used
+ * to remove the node from the _model_. Don't use it to just free
+ * a node.
+ **/
+static void
+model_remove_node (GeditFileBrowserStore * model,
+ FileBrowserNode * node,
+ GtkTreePath * path,
+ gboolean free_nodes)
+{
+ gboolean free_path = FALSE;
+ FileBrowserNode *parent;
+
+ if (path == NULL) {
+ path =
+ gedit_file_browser_store_get_path_real (model, node);
+ free_path = TRUE;
+ }
+
+ model_remove_node_children (model, node, path, free_nodes);
+
+ /* Only delete if the node is visible in the tree (but only when it's
+ not the virtual root) */
+ if (model_node_visibility (model, node) && node != model->priv->virtual_root)
+ {
+ node->inserted = FALSE;
+ row_deleted (model, path);
+ }
+
+ if (free_path)
+ gtk_tree_path_free (path);
+
+ parent = node->parent;
+
+ if (free_nodes) {
+ /* Remove the node from the parents children list */
+ if (parent)
+ FILE_BROWSER_NODE_DIR (node->parent)->children =
+ g_slist_remove (FILE_BROWSER_NODE_DIR
+ (node->parent)->children,
+ node);
+ }
+
+ /* If this is the virtual root, than set the parent as the virtual root */
+ if (node == model->priv->virtual_root)
+ set_virtual_root_from_node (model, parent);
+ else if (parent && model_node_visibility (model, parent) && !(free_nodes && NODE_IS_DUMMY(node)))
+ model_check_dummy (model, parent);
+
+ /* Now free the node if necessary */
+ if (free_nodes)
+ file_browser_node_free (model, node);
+}
+
+/**
+ * model_clear:
+ * @model: the #GeditFileBrowserStore
+ * @free_nodes: whether to also remove the nodes from memory
+ *
+ * Removes all nodes from the model. This function is used
+ * to remove all the nodes from the _model_. Don't use it to just free the
+ * nodes in the model.
+ **/
+static void
+model_clear (GeditFileBrowserStore * model, gboolean free_nodes)
+{
+ GtkTreePath *path;
+ FileBrowserNodeDir *dir;
+ FileBrowserNode *dummy;
+
+ path = gtk_tree_path_new ();
+ model_remove_node_children (model, model->priv->virtual_root, path,
+ free_nodes);
+ gtk_tree_path_free (path);
+
+ /* Remove the dummy if there is one */
+ if (model->priv->virtual_root) {
+ dir = FILE_BROWSER_NODE_DIR (model->priv->virtual_root);
+
+ if (dir->children != NULL) {
+ dummy = (FileBrowserNode *) (dir->children->data);
+
+ if (NODE_IS_DUMMY (dummy)
+ && model_node_visibility (model, dummy)) {
+ path = gtk_tree_path_new_first ();
+
+ dummy->inserted = FALSE;
+ row_deleted (model, path);
+ gtk_tree_path_free (path);
+ }
+ }
+ }
+}
+
+static void
+file_browser_node_unload (GeditFileBrowserStore * model,
+ FileBrowserNode * node, gboolean remove_children)
+{
+ FileBrowserNodeDir *dir;
+
+ if (node == NULL)
+ return;
+
+ if (!NODE_IS_DIR (node) || !NODE_LOADED (node))
+ return;
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ if (remove_children)
+ model_remove_node_children (model, node, NULL, TRUE);
+
+ if (dir->cancellable) {
+ g_cancellable_cancel (dir->cancellable);
+ g_object_unref (dir->cancellable);
+
+ model_end_loading (model, node);
+ dir->cancellable = NULL;
+ }
+
+ if (dir->monitor) {
+ g_file_monitor_cancel (dir->monitor);
+ g_object_unref (dir->monitor);
+
+ dir->monitor = NULL;
+ }
+
+ node->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_LOADED;
+}
+
+static void
+model_recomposite_icon_real (GeditFileBrowserStore * tree_model,
+ FileBrowserNode * node,
+ GFileInfo * info)
+{
+ GdkPixbuf *icon;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_return_if_fail (node != NULL);
+
+ if (node->file == NULL)
+ return;
+
+ if (info) {
+ GIcon *gicon = g_file_info_get_icon (info);
+ if (gicon != NULL)
+ icon = gedit_file_browser_utils_pixbuf_from_icon (gicon, GTK_ICON_SIZE_MENU);
+ else
+ icon = NULL;
+ } else {
+ icon = gedit_file_browser_utils_pixbuf_from_file (node->file, GTK_ICON_SIZE_MENU);
+ }
+
+ if (node->icon)
+ g_object_unref (node->icon);
+
+ if (node->emblem) {
+ gint icon_size;
+
+ gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, NULL, &icon_size);
+
+ if (icon == NULL) {
+ node->icon =
+ gdk_pixbuf_new (gdk_pixbuf_get_colorspace (node->emblem),
+ gdk_pixbuf_get_has_alpha (node->emblem),
+ gdk_pixbuf_get_bits_per_sample (node->emblem),
+ icon_size,
+ icon_size);
+ } else {
+ node->icon = gdk_pixbuf_copy (icon);
+ g_object_unref (icon);
+ }
+
+ gdk_pixbuf_composite (node->emblem, node->icon,
+ icon_size - 10, icon_size - 10, 10,
+ 10, icon_size - 10, icon_size - 10,
+ 1, 1, GDK_INTERP_NEAREST, 255);
+ } else {
+ node->icon = icon;
+ }
+}
+
+static void
+model_recomposite_icon (GeditFileBrowserStore * tree_model,
+ GtkTreeIter * iter)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ model_recomposite_icon_real (tree_model,
+ (FileBrowserNode *) (iter->user_data),
+ NULL);
+}
+
+static FileBrowserNode *
+model_create_dummy_node (GeditFileBrowserStore * model,
+ FileBrowserNode * parent)
+{
+ FileBrowserNode *dummy;
+
+ dummy = file_browser_node_new (NULL, parent);
+ dummy->name = g_strdup (_("(Empty)"));
+
+ dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY;
+ dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ return dummy;
+}
+
+static FileBrowserNode *
+model_add_dummy_node (GeditFileBrowserStore * model,
+ FileBrowserNode * parent)
+{
+ FileBrowserNode *dummy;
+
+ dummy = model_create_dummy_node (model, parent);
+
+ if (model_node_visibility (model, parent))
+ dummy->flags &= ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ model_add_node (model, dummy, parent);
+
+ return dummy;
+}
+
+static void
+model_check_dummy (GeditFileBrowserStore * model, FileBrowserNode * node)
+{
+ // Hide the dummy child if needed
+ if (NODE_IS_DIR (node)) {
+ FileBrowserNode *dummy;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ guint flags;
+ FileBrowserNodeDir *dir;
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ if (dir->children == NULL) {
+ model_add_dummy_node (model, node);
+ return;
+ }
+
+ dummy = (FileBrowserNode *) (dir->children->data);
+
+ if (!NODE_IS_DUMMY (dummy)) {
+ dummy = model_create_dummy_node (model, node);
+ dir->children = g_slist_prepend (dir->children, dummy);
+ }
+
+ if (!model_node_visibility (model, node)) {
+ dummy->flags |=
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+ return;
+ }
+
+ /* Temporarily set the node to invisible to check
+ * for real children */
+ flags = dummy->flags;
+ dummy->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ if (!filter_tree_model_iter_has_child_real (model, node)) {
+ dummy->flags &=
+ ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ if (FILE_IS_HIDDEN (flags)) {
+ // Was hidden, needs to be inserted
+ iter.user_data = dummy;
+ path =
+ gedit_file_browser_store_get_path_real
+ (model, dummy);
+
+ row_inserted (model, &path, &iter);
+ gtk_tree_path_free (path);
+ }
+ } else {
+ if (!FILE_IS_HIDDEN (flags)) {
+ // Was shown, needs to be removed
+
+ // To get the path we need to set it to visible temporarily
+ dummy->flags &=
+ ~GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+ path =
+ gedit_file_browser_store_get_path_real
+ (model, dummy);
+ dummy->flags |=
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ dummy->inserted = FALSE;
+ row_deleted (model, path);
+ gtk_tree_path_free (path);
+ }
+ }
+ }
+}
+
+static void
+insert_node_sorted (GeditFileBrowserStore * model,
+ FileBrowserNode * child,
+ FileBrowserNode * parent)
+{
+ FileBrowserNodeDir *dir;
+
+ dir = FILE_BROWSER_NODE_DIR (parent);
+
+ if (model->priv->sort_func == NULL) {
+ dir->children = g_slist_append (dir->children, child);
+ } else {
+ dir->children =
+ g_slist_insert_sorted (dir->children, child,
+ (GCompareFunc) (model->priv->
+ sort_func));
+ }
+}
+
+static void
+model_add_node (GeditFileBrowserStore * model, FileBrowserNode * child,
+ FileBrowserNode * parent)
+{
+ /* Add child to parents children */
+ insert_node_sorted (model, child, parent);
+
+ if (model_node_visibility (model, parent) &&
+ model_node_visibility (model, child)) {
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ iter.user_data = child;
+ path = gedit_file_browser_store_get_path_real (model, child);
+
+ /* Emit row inserted */
+ row_inserted (model, &path, &iter);
+ gtk_tree_path_free (path);
+ }
+
+ model_check_dummy (model, parent);
+ model_check_dummy (model, child);
+}
+
+static void
+model_add_nodes_batch (GeditFileBrowserStore * model,
+ GSList * children,
+ FileBrowserNode * parent)
+{
+ GSList *sorted_children;
+ GSList *child;
+ GSList *prev;
+ GSList *l;
+ FileBrowserNodeDir *dir;
+
+ dir = FILE_BROWSER_NODE_DIR (parent);
+
+ sorted_children = g_slist_sort (children, (GCompareFunc) model->priv->sort_func);
+
+ child = sorted_children;
+ l = dir->children;
+ prev = NULL;
+
+ model_check_dummy (model, parent);
+
+ while (child) {
+ FileBrowserNode *node = child->data;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ /* reached the end of the first list, just append the second */
+ if (l == NULL) {
+
+ dir->children = g_slist_concat (dir->children, child);
+
+ for (l = child; l; l = l->next) {
+ if (model_node_visibility (model, parent) &&
+ model_node_visibility (model, l->data)) {
+ iter.user_data = l->data;
+ path = gedit_file_browser_store_get_path_real (model, l->data);
+
+ // Emit row inserted
+ row_inserted (model, &path, &iter);
+ gtk_tree_path_free (path);
+ }
+
+ model_check_dummy (model, l->data);
+ }
+
+ break;
+ }
+
+ if (model->priv->sort_func (l->data, node) > 0) {
+ GSList *next_child;
+
+ if (prev == NULL) {
+ /* prepend to the list */
+ dir->children = g_slist_prepend (dir->children, child);
+ } else {
+ prev->next = child;
+ }
+
+ next_child = child->next;
+ prev = child;
+ child->next = l;
+ child = next_child;
+
+ if (model_node_visibility (model, parent) &&
+ model_node_visibility (model, node)) {
+ iter.user_data = node;
+ path = gedit_file_browser_store_get_path_real (model, node);
+
+ // Emit row inserted
+ row_inserted (model, &path, &iter);
+ gtk_tree_path_free (path);
+ }
+
+ model_check_dummy (model, node);
+
+ /* try again at the same l position with the
+ * next child */
+ } else {
+
+ /* Move to the next item in the list */
+ prev = l;
+ l = l->next;
+ }
+ }
+}
+
+static gchar const *
+backup_content_type (GFileInfo * info)
+{
+ gchar const * content;
+
+ if (!g_file_info_get_is_backup (info))
+ return NULL;
+
+ content = g_file_info_get_content_type (info);
+
+ if (!content || g_content_type_equals (content, "application/x-trash"))
+ return "text/plain";
+
+ return content;
+}
+
+static void
+file_browser_node_set_from_info (GeditFileBrowserStore * model,
+ FileBrowserNode * node,
+ GFileInfo * info,
+ gboolean isadded)
+{
+ FileBrowserNodeDir * dir;
+ gchar const * content;
+ gchar const * name;
+ gboolean free_info = FALSE;
+ GtkTreePath * path;
+ gchar * uri;
+ GError * error = NULL;
+
+ if (info == NULL) {
+ info = g_file_query_info (node->file,
+ STANDARD_ATTRIBUTE_TYPES,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (!info) {
+ if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)) {
+ uri = g_file_get_uri (node->file);
+ g_warning ("Could not get info for %s: %s", uri, error->message);
+ g_free (uri);
+ }
+ g_error_free (error);
+
+ return;
+ }
+
+ free_info = TRUE;
+ }
+
+ dir = FILE_BROWSER_NODE_DIR (node->parent);
+ name = g_file_info_get_name (info);
+
+ if (g_file_info_get_is_hidden (info) || g_file_info_get_is_backup (info))
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+ else if (dir != NULL && dir->hidden_file_hash != NULL &&
+ g_hash_table_lookup (dir->hidden_file_hash, name) != NULL)
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY;
+ else {
+ if (!(content = backup_content_type (info)))
+ content = g_file_info_get_content_type (info);
+
+ if (!content ||
+ g_content_type_is_unknown (content) ||
+ g_content_type_is_a (content, "text/plain"))
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT;
+ }
+
+ model_recomposite_icon_real (model, node, info);
+
+ if (free_info)
+ g_object_unref (info);
+
+ if (isadded) {
+ path = gedit_file_browser_store_get_path_real (model, node);
+ model_refilter_node (model, node, &path);
+ gtk_tree_path_free (path);
+
+ model_check_dummy (model, node->parent);
+ } else {
+ model_node_update_visibility (model, node);
+ }
+}
+
+static FileBrowserNode *
+node_list_contains_file (GSList *children, GFile * file)
+{
+ GSList *item;
+
+ for (item = children; item; item = item->next) {
+ FileBrowserNode *node;
+
+ node = (FileBrowserNode *) (item->data);
+
+ if (node->file != NULL
+ && g_file_equal (node->file, file))
+ return node;
+ }
+
+ return NULL;
+}
+
+static FileBrowserNode *
+model_add_node_from_file (GeditFileBrowserStore * model,
+ FileBrowserNode * parent,
+ GFile * file,
+ GFileInfo * info)
+{
+ FileBrowserNode *node;
+ gboolean free_info = FALSE;
+ GError * error = NULL;
+
+ if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL) {
+ if (info == NULL) {
+ info = g_file_query_info (file,
+ STANDARD_ATTRIBUTE_TYPES,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+ free_info = TRUE;
+ }
+
+ if (!info) {
+ g_warning ("Error querying file info: %s", error->message);
+ g_error_free (error);
+
+ /* FIXME: What to do now then... */
+ node = file_browser_node_new (file, parent);
+ } else if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+ node = file_browser_node_dir_new (model, file, parent);
+ } else {
+ node = file_browser_node_new (file, parent);
+ }
+
+ file_browser_node_set_from_info (model, node, info, FALSE);
+ model_add_node (model, node, parent);
+
+ if (info && free_info)
+ g_object_unref (info);
+ }
+
+ return node;
+}
+
+/* We pass in a copy of the list of parent->children so that we do
+ * not have to check if a file already exists among the ones we just
+ * added */
+static void
+model_add_nodes_from_files (GeditFileBrowserStore * model,
+ FileBrowserNode * parent,
+ GSList * original_children,
+ GList * files)
+{
+ GList *item;
+ GSList *nodes = NULL;
+
+ for (item = files; item; item = item->next) {
+ GFileInfo *info = G_FILE_INFO (item->data);
+ GFileType type;
+ gchar const * name;
+ GFile * file;
+ FileBrowserNode *node;
+
+ type = g_file_info_get_file_type (info);
+
+ /* Skip all non regular, non directory files */
+ if (type != G_FILE_TYPE_REGULAR &&
+ type != G_FILE_TYPE_DIRECTORY &&
+ type != G_FILE_TYPE_SYMBOLIC_LINK) {
+ g_object_unref (info);
+ continue;
+ }
+
+ name = g_file_info_get_name (info);
+
+ /* Skip '.' and '..' directories */
+ if (type == G_FILE_TYPE_DIRECTORY &&
+ (strcmp (name, ".") == 0 ||
+ strcmp (name, "..") == 0)) {
+ continue;
+ }
+
+ file = g_file_get_child (parent->file, name);
+
+ if ((node = node_list_contains_file (original_children, file)) == NULL) {
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+ node = file_browser_node_dir_new (model, file, parent);
+ } else {
+ node = file_browser_node_new (file, parent);
+ }
+
+ file_browser_node_set_from_info (model, node, info, FALSE);
+
+ nodes = g_slist_prepend (nodes, node);
+ }
+
+ g_object_unref (file);
+ g_object_unref (info);
+ }
+
+ if (nodes)
+ model_add_nodes_batch (model, nodes, parent);
+}
+
+static FileBrowserNode *
+model_add_node_from_dir (GeditFileBrowserStore * model,
+ FileBrowserNode * parent,
+ GFile * file)
+{
+ FileBrowserNode *node;
+
+ /* Check if it already exists */
+ if ((node = node_list_contains_file (FILE_BROWSER_NODE_DIR (parent)->children, file)) == NULL) {
+ node = file_browser_node_dir_new (model, file, parent);
+ file_browser_node_set_from_info (model, node, NULL, FALSE);
+
+ if (node->name == NULL) {
+ file_browser_node_set_name (node);
+ }
+
+ if (node->icon == NULL) {
+ node->icon = gedit_file_browser_utils_pixbuf_from_theme ("folder", GTK_ICON_SIZE_MENU);
+ }
+
+ model_add_node (model, node, parent);
+ }
+
+ return node;
+}
+
+/* Read is sync, but we only do it for local files */
+static void
+parse_dot_hidden_file (FileBrowserNode *directory)
+{
+ gsize file_size;
+ char *file_contents;
+ GFile *child;
+ GFileInfo *info;
+ GFileType type;
+ int i;
+ FileBrowserNodeDir * dir = FILE_BROWSER_NODE_DIR (directory);
+
+ /* FIXME: We only support .hidden on file: uri's for the moment.
+ * Need to figure out if we should do this async or sync to extend
+ * it to all types of uris.
+ */
+ if (directory->file == NULL || !g_file_is_native (directory->file)) {
+ return;
+ }
+
+ child = g_file_get_child (directory->file, ".hidden");
+ info = g_file_query_info (child, G_FILE_ATTRIBUTE_STANDARD_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+
+ type = info ? g_file_info_get_file_type (info) : G_FILE_TYPE_UNKNOWN;
+
+ if (info)
+ g_object_unref (info);
+
+ if (type != G_FILE_TYPE_REGULAR) {
+ g_object_unref (child);
+
+ return;
+ }
+
+ if (!g_file_load_contents (child, NULL, &file_contents, &file_size, NULL, NULL)) {
+ g_object_unref (child);
+ return;
+ }
+
+ g_object_unref (child);
+
+ if (dir->hidden_file_hash == NULL) {
+ dir->hidden_file_hash =
+ g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ }
+
+ /* Now parse the data */
+ i = 0;
+ while (i < file_size) {
+ int start;
+
+ start = i;
+ while (i < file_size && file_contents[i] != '\n') {
+ i++;
+ }
+
+ if (i > start) {
+ char *hidden_filename;
+
+ hidden_filename = g_strndup (file_contents + start, i - start);
+ g_hash_table_insert (dir->hidden_file_hash,
+ hidden_filename, hidden_filename);
+ }
+
+ i++;
+
+ }
+
+ g_free (file_contents);
+}
+
+static void
+on_directory_monitor_event (GFileMonitor * monitor,
+ GFile * file,
+ GFile * other_file,
+ GFileMonitorEvent event_type,
+ FileBrowserNode * parent)
+{
+ FileBrowserNode *node;
+ FileBrowserNodeDir *dir = FILE_BROWSER_NODE_DIR (parent);
+
+ switch (event_type) {
+ case G_FILE_MONITOR_EVENT_DELETED:
+ node = node_list_contains_file (dir->children, file);
+
+ if (node != NULL) {
+ model_remove_node (dir->model, node, NULL, TRUE);
+ }
+ break;
+ case G_FILE_MONITOR_EVENT_CREATED:
+ if (g_file_query_exists (file, NULL)) {
+ model_add_node_from_file (dir->model, parent, file, NULL);
+ }
+
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+async_node_free (AsyncNode *async)
+{
+ g_object_unref (async->cancellable);
+ g_slist_free (async->original_children);
+ g_free (async);
+}
+
+static void
+model_iterate_next_files_cb (GFileEnumerator * enumerator,
+ GAsyncResult * result,
+ AsyncNode * async)
+{
+ GList * files;
+ GError * error = NULL;
+ FileBrowserNodeDir * dir = async->dir;
+ FileBrowserNode * parent = (FileBrowserNode *)dir;
+
+ files = g_file_enumerator_next_files_finish (enumerator, result, &error);
+
+ if (files == NULL) {
+ g_file_enumerator_close (enumerator, NULL, NULL);
+ async_node_free (async);
+
+ if (!error)
+ {
+ /* We're done loading */
+ g_object_unref (dir->cancellable);
+ dir->cancellable = NULL;
+
+/*
+ * FIXME: This is temporarly, it is a bug in gio:
+ * http://bugzilla.mate.org/show_bug.cgi?id=565924
+ */
+#ifndef G_OS_WIN32
+ if (g_file_is_native (parent->file) && dir->monitor == NULL) {
+ dir->monitor = g_file_monitor_directory (parent->file,
+ G_FILE_MONITOR_NONE,
+ NULL,
+ NULL);
+ if (dir->monitor != NULL)
+ {
+ g_signal_connect (dir->monitor,
+ "changed",
+ G_CALLBACK (on_directory_monitor_event),
+ parent);
+ }
+ }
+#endif
+
+ model_check_dummy (dir->model, parent);
+ model_end_loading (dir->model, parent);
+ } else {
+ /* Simply return if we were cancelled */
+ if (error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED)
+ return;
+
+ /* Otherwise handle the error appropriately */
+ g_signal_emit (dir->model,
+ model_signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY,
+ error->message);
+
+ file_browser_node_unload (dir->model, (FileBrowserNode *)parent, TRUE);
+ g_error_free (error);
+ }
+ } else if (g_cancellable_is_cancelled (async->cancellable)) {
+ /* Check cancel state manually */
+ g_file_enumerator_close (enumerator, NULL, NULL);
+ async_node_free (async);
+ } else {
+ model_add_nodes_from_files (dir->model, parent, async->original_children, files);
+
+ g_list_free (files);
+ next_files_async (enumerator, async);
+ }
+}
+
+static void
+next_files_async (GFileEnumerator * enumerator,
+ AsyncNode * async)
+{
+ g_file_enumerator_next_files_async (enumerator,
+ DIRECTORY_LOAD_ITEMS_PER_CALLBACK,
+ G_PRIORITY_DEFAULT,
+ async->cancellable,
+ (GAsyncReadyCallback)model_iterate_next_files_cb,
+ async);
+}
+
+static void
+model_iterate_children_cb (GFile * file,
+ GAsyncResult * result,
+ AsyncNode * async)
+{
+ GError * error = NULL;
+ GFileEnumerator * enumerator;
+
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_node_free (async);
+ return;
+ }
+
+ enumerator = g_file_enumerate_children_finish (file, result, &error);
+
+ if (enumerator == NULL) {
+ /* Simply return if we were cancelled or if the dir is not there */
+ FileBrowserNodeDir *dir = async->dir;
+
+ /* Otherwise handle the error appropriately */
+ g_signal_emit (dir->model,
+ model_signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_LOAD_DIRECTORY,
+ error->message);
+
+ file_browser_node_unload (dir->model, (FileBrowserNode *)dir, TRUE);
+ g_error_free (error);
+ async_node_free (async);
+ } else {
+ next_files_async (enumerator, async);
+ }
+}
+
+static void
+model_load_directory (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ FileBrowserNodeDir *dir;
+ AsyncNode *async;
+
+ g_return_if_fail (NODE_IS_DIR (node));
+
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ /* Cancel a previous load */
+ if (dir->cancellable != NULL) {
+ file_browser_node_unload (dir->model, node, TRUE);
+ }
+
+ node->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED;
+ model_begin_loading (model, node);
+
+ /* Read the '.hidden' file first (if any) */
+ parse_dot_hidden_file (node);
+
+ dir->cancellable = g_cancellable_new ();
+
+ async = g_new (AsyncNode, 1);
+ async->dir = dir;
+ async->cancellable = g_object_ref (dir->cancellable);
+ async->original_children = g_slist_copy (dir->children);
+
+ /* Start loading async */
+ g_file_enumerate_children_async (node->file,
+ STANDARD_ATTRIBUTE_TYPES,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_DEFAULT,
+ async->cancellable,
+ (GAsyncReadyCallback)model_iterate_children_cb,
+ async);
+}
+
+static GList *
+get_parent_files (GeditFileBrowserStore * model, GFile * file)
+{
+ GList * result = NULL;
+
+ result = g_list_prepend (result, g_object_ref (file));
+
+ while ((file = g_file_get_parent (file))) {
+ if (g_file_equal (file, model->priv->root->file)) {
+ g_object_unref (file);
+ break;
+ }
+
+ result = g_list_prepend (result, file);
+ }
+
+ return result;
+}
+
+static void
+model_fill (GeditFileBrowserStore * model, FileBrowserNode * node,
+ GtkTreePath ** path)
+{
+ gboolean free_path = FALSE;
+ GtkTreeIter iter = {0,};
+ GSList *item;
+ FileBrowserNode *child;
+
+ if (node == NULL) {
+ node = model->priv->virtual_root;
+ *path = gtk_tree_path_new ();
+ free_path = TRUE;
+ }
+
+ if (*path == NULL) {
+ *path =
+ gedit_file_browser_store_get_path_real (model, node);
+ free_path = TRUE;
+ }
+
+ if (!model_node_visibility (model, node)) {
+ if (free_path)
+ gtk_tree_path_free (*path);
+
+ return;
+ }
+
+ if (node != model->priv->virtual_root) {
+ /* Insert node */
+ iter.user_data = node;
+
+ row_inserted(model, path, &iter);
+ }
+
+ if (NODE_IS_DIR (node)) {
+ /* Go to the first child */
+ gtk_tree_path_down (*path);
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item;
+ item = item->next) {
+ child = (FileBrowserNode *) (item->data);
+
+ if (model_node_visibility (model, child)) {
+ model_fill (model, child, path);
+
+ /* Increase path for next child */
+ gtk_tree_path_next (*path);
+ }
+ }
+
+ /* Move back up to node path */
+ gtk_tree_path_up (*path);
+ }
+
+ model_check_dummy (model, node);
+
+ if (free_path)
+ gtk_tree_path_free (*path);
+}
+
+static void
+set_virtual_root_from_node (GeditFileBrowserStore * model,
+ FileBrowserNode * node)
+{
+ FileBrowserNode *next;
+ FileBrowserNode *prev;
+ FileBrowserNode *check;
+ FileBrowserNodeDir *dir;
+ GSList *item;
+ GSList *copy;
+ GtkTreePath *empty = NULL;
+
+ prev = node;
+ next = prev->parent;
+
+ /* Free all the nodes below that we don't need in cache */
+ while (prev != model->priv->root) {
+ dir = FILE_BROWSER_NODE_DIR (next);
+ copy = g_slist_copy (dir->children);
+
+ for (item = copy; item; item = item->next) {
+ check = (FileBrowserNode *) (item->data);
+
+ if (prev == node) {
+ /* Only free the children, keeping this depth in cache */
+ if (check != node) {
+ file_browser_node_free_children
+ (model, check);
+ file_browser_node_unload (model,
+ check,
+ FALSE);
+ }
+ } else if (check != prev) {
+ /* Only free when the node is not in the chain */
+ dir->children =
+ g_slist_remove (dir->children, check);
+ file_browser_node_free (model, check);
+ }
+ }
+
+ if (prev != node)
+ file_browser_node_unload (model, next, FALSE);
+
+ g_slist_free (copy);
+ prev = next;
+ next = prev->parent;
+ }
+
+ /* Free all the nodes up that we don't need in cache */
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item;
+ item = item->next) {
+ check = (FileBrowserNode *) (item->data);
+
+ if (NODE_IS_DIR (check)) {
+ for (copy =
+ FILE_BROWSER_NODE_DIR (check)->children; copy;
+ copy = copy->next) {
+ file_browser_node_free_children (model,
+ (FileBrowserNode
+ *)
+ (copy->
+ data));
+ file_browser_node_unload (model,
+ (FileBrowserNode
+ *) (copy->data),
+ FALSE);
+ }
+ } else if (NODE_IS_DUMMY (check)) {
+ check->flags |=
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN;
+ }
+ }
+
+ /* Now finally, set the virtual root, and load it up! */
+ model->priv->virtual_root = node;
+
+ /* Notify that the virtual-root has changed before loading up new nodes so that the
+ "root_changed" signal can be emitted before any "inserted" signals */
+ g_object_notify (G_OBJECT (model), "virtual-root");
+
+ model_fill (model, NULL, &empty);
+
+ if (!NODE_LOADED (node))
+ model_load_directory (model, node);
+}
+
+static void
+set_virtual_root_from_file (GeditFileBrowserStore * model,
+ GFile * file)
+{
+ GList * files;
+ GList * item;
+ FileBrowserNode * parent;
+ GFile * check;
+
+ /* Always clear the model before altering the nodes */
+ model_clear (model, FALSE);
+
+ /* Create the node path, get all the uri's */
+ files = get_parent_files (model, file);
+ parent = model->priv->root;
+
+ for (item = files; item; item = item->next) {
+ check = G_FILE (item->data);
+
+ parent = model_add_node_from_dir (model, parent, check);
+ g_object_unref (check);
+ }
+
+ g_list_free (files);
+ set_virtual_root_from_node (model, parent);
+}
+
+static FileBrowserNode *
+model_find_node_children (GeditFileBrowserStore * model,
+ FileBrowserNode * parent,
+ GFile * file)
+{
+ FileBrowserNodeDir *dir;
+ FileBrowserNode *child;
+ FileBrowserNode *result;
+ GSList *children;
+
+ if (!NODE_IS_DIR (parent))
+ return NULL;
+
+ dir = FILE_BROWSER_NODE_DIR (parent);
+
+ for (children = dir->children; children; children = children->next) {
+ child = (FileBrowserNode *)(children->data);
+
+ result = model_find_node (model, child, file);
+
+ if (result)
+ return result;
+ }
+
+ return NULL;
+}
+
+static FileBrowserNode *
+model_find_node (GeditFileBrowserStore * model,
+ FileBrowserNode * node,
+ GFile * file)
+{
+ if (node == NULL)
+ node = model->priv->root;
+
+ if (node->file && g_file_equal (node->file, file))
+ return node;
+
+ if (NODE_IS_DIR (node) && g_file_has_prefix (file, node->file))
+ return model_find_node_children (model, node, file);
+
+ return NULL;
+}
+
+static GQuark
+gedit_file_browser_store_error_quark (void)
+{
+ static GQuark quark = 0;
+
+ if (G_UNLIKELY (quark == 0)) {
+ quark = g_quark_from_string ("gedit_file_browser_store_error");
+ }
+
+ return quark;
+}
+
+static GFile *
+unique_new_name (GFile * directory, gchar const * name)
+{
+ GFile * newuri = NULL;
+ guint num = 0;
+ gchar * newname;
+
+ while (newuri == NULL || g_file_query_exists (newuri, NULL)) {
+ if (newuri != NULL)
+ g_object_unref (newuri);
+
+ if (num == 0)
+ newname = g_strdup (name);
+ else
+ newname = g_strdup_printf ("%s(%d)", name, num);
+
+ newuri = g_file_get_child (directory, newname);
+ g_free (newname);
+
+ ++num;
+ }
+
+ return newuri;
+}
+
+static GeditFileBrowserStoreResult
+model_root_mounted (GeditFileBrowserStore * model, gchar const * virtual_root)
+{
+ model_check_dummy (model, model->priv->root);
+ g_object_notify (G_OBJECT (model), "root");
+
+ if (virtual_root != NULL)
+ return
+ gedit_file_browser_store_set_virtual_root_from_string
+ (model, virtual_root);
+ else
+ set_virtual_root_from_node (model,
+ model->priv->root);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+static void
+handle_root_error (GeditFileBrowserStore * model, GError *error)
+{
+ FileBrowserNode * root;
+
+ g_signal_emit (model,
+ model_signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ error->message);
+
+ /* Set the virtual root to the root */
+ root = model->priv->root;
+ model->priv->virtual_root = root;
+
+ /* Set the root to be loaded */
+ root->flags |= GEDIT_FILE_BROWSER_STORE_FLAG_LOADED;
+
+ /* Check the dummy */
+ model_check_dummy (model, root);
+
+ g_object_notify (G_OBJECT (model), "root");
+ g_object_notify (G_OBJECT (model), "virtual-root");
+}
+
+static void
+mount_cb (GFile * file,
+ GAsyncResult * res,
+ MountInfo * mount_info)
+{
+ gboolean mounted;
+ GError * error = NULL;
+ GeditFileBrowserStore * model = mount_info->model;
+
+ mounted = g_file_mount_enclosing_volume_finish (file, res, &error);
+
+ if (mount_info->model)
+ {
+ model->priv->mount_info = NULL;
+ model_end_loading (model, model->priv->root);
+ }
+
+ if (!mount_info->model || g_cancellable_is_cancelled (mount_info->cancellable))
+ {
+ // Reset because it might be reused?
+ g_cancellable_reset (mount_info->cancellable);
+ }
+ else if (mounted)
+ {
+ model_root_mounted (model, mount_info->virtual_root);
+ }
+ else if (error->code != G_IO_ERROR_CANCELLED)
+ {
+ handle_root_error (model, error);
+ }
+
+ if (error)
+ g_error_free (error);
+
+ g_object_unref (mount_info->operation);
+ g_object_unref (mount_info->cancellable);
+ g_free (mount_info->virtual_root);
+
+ g_free (mount_info);
+}
+
+static GeditFileBrowserStoreResult
+model_mount_root (GeditFileBrowserStore * model, gchar const * virtual_root)
+{
+ GFileInfo * info;
+ GError * error = NULL;
+ MountInfo * mount_info;
+
+ info = g_file_query_info (model->priv->root->file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (!info) {
+ if (error->code == G_IO_ERROR_NOT_MOUNTED) {
+ /* Try to mount it */
+ FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable = g_cancellable_new ();
+
+ mount_info = g_new(MountInfo, 1);
+ mount_info->model = model;
+ mount_info->virtual_root = g_strdup (virtual_root);
+
+ /* FIXME: we should be setting the correct window */
+ mount_info->operation = gtk_mount_operation_new (NULL);
+ mount_info->cancellable = g_object_ref (FILE_BROWSER_NODE_DIR (model->priv->root)->cancellable);
+
+ model_begin_loading (model, model->priv->root);
+ g_file_mount_enclosing_volume (model->priv->root->file,
+ G_MOUNT_MOUNT_NONE,
+ mount_info->operation,
+ mount_info->cancellable,
+ (GAsyncReadyCallback)mount_cb,
+ mount_info);
+
+ model->priv->mount_info = mount_info;
+ return GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING;
+ }
+ else
+ {
+ handle_root_error (model, error);
+ }
+
+ g_error_free (error);
+ } else {
+ g_object_unref (info);
+
+ return model_root_mounted (model, virtual_root);
+ }
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+/* Public */
+GeditFileBrowserStore *
+gedit_file_browser_store_new (gchar const *root)
+{
+ GeditFileBrowserStore *obj =
+ GEDIT_FILE_BROWSER_STORE (g_object_new
+ (GEDIT_TYPE_FILE_BROWSER_STORE,
+ NULL));
+
+ gedit_file_browser_store_set_root (obj, root);
+ return obj;
+}
+
+void
+gedit_file_browser_store_set_value (GeditFileBrowserStore * tree_model,
+ GtkTreeIter * iter, gint column,
+ GValue * value)
+{
+ gpointer data;
+ FileBrowserNode *node;
+ GtkTreePath *path;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (tree_model));
+ g_return_if_fail (column ==
+ GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM);
+ g_return_if_fail (G_VALUE_HOLDS_OBJECT (value));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ data = g_value_get_object (value);
+
+ if (data)
+ g_return_if_fail (GDK_IS_PIXBUF (data));
+
+ node = (FileBrowserNode *) (iter->user_data);
+
+ if (node->emblem)
+ g_object_unref (node->emblem);
+
+ if (data)
+ node->emblem = g_object_ref (GDK_PIXBUF (data));
+ else
+ node->emblem = NULL;
+
+ model_recomposite_icon (tree_model, iter);
+
+ if (model_node_visibility (tree_model, node)) {
+ path = gedit_file_browser_store_get_path (GTK_TREE_MODEL (tree_model),
+ iter);
+ row_changed (tree_model, &path, iter);
+ gtk_tree_path_free (path);
+ }
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore * model,
+ GtkTreeIter * iter)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model),
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ g_return_val_if_fail (iter != NULL,
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ g_return_val_if_fail (iter->user_data != NULL,
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ model_clear (model, FALSE);
+ set_virtual_root_from_node (model,
+ (FileBrowserNode *) (iter->user_data));
+
+ return TRUE;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_from_string
+ (GeditFileBrowserStore * model, gchar const *root) {
+ GFile *file;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model),
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ file = g_file_new_for_uri (root);
+ if (file == NULL) {
+ g_warning ("Invalid uri (%s)", root);
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+ }
+
+ /* Check if uri is already the virtual root */
+ if (model->priv->virtual_root &&
+ g_file_equal (model->priv->virtual_root->file, file)) {
+ g_object_unref (file);
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+ }
+
+ /* Check if uri is the root itself */
+ if (g_file_equal (model->priv->root->file, file)) {
+ g_object_unref (file);
+
+ /* Always clear the model before altering the nodes */
+ model_clear (model, FALSE);
+ set_virtual_root_from_node (model, model->priv->root);
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+ }
+
+ if (!g_file_has_prefix (file, model->priv->root->file)) {
+ gchar *str, *str1;
+
+ str = g_file_get_parse_name (model->priv->root->file);
+ str1 = g_file_get_parse_name (file);
+
+ g_warning
+ ("Virtual root (%s) is not below actual root (%s)",
+ str1, str);
+
+ g_free (str);
+ g_free (str1);
+
+ g_object_unref (file);
+ return GEDIT_FILE_BROWSER_STORE_RESULT_ERROR;
+ }
+
+ set_virtual_root_from_file (model, file);
+ g_object_unref (file);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore *
+ model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model),
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (model->priv->virtual_root == model->priv->root)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ model_clear (model, FALSE);
+ set_virtual_root_from_node (model, model->priv->root);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore *
+ model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model),
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (model->priv->virtual_root == model->priv->root)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ model_clear (model, FALSE);
+ set_virtual_root_from_node (model,
+ model->priv->virtual_root->parent);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+gboolean
+gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore *
+ model, GtkTreeIter * iter)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ if (model->priv->virtual_root == NULL)
+ return FALSE;
+
+ iter->user_data = model->priv->virtual_root;
+ return TRUE;
+}
+
+gboolean
+gedit_file_browser_store_get_iter_root (GeditFileBrowserStore * model,
+ GtkTreeIter * iter)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ if (model->priv->root == NULL)
+ return FALSE;
+
+ iter->user_data = model->priv->root;
+ return TRUE;
+}
+
+gboolean
+gedit_file_browser_store_iter_equal (GeditFileBrowserStore * model,
+ GtkTreeIter * iter1,
+ GtkTreeIter * iter2)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (iter1 != NULL, FALSE);
+ g_return_val_if_fail (iter2 != NULL, FALSE);
+ g_return_val_if_fail (iter1->user_data != NULL, FALSE);
+ g_return_val_if_fail (iter2->user_data != NULL, FALSE);
+
+ return (iter1->user_data == iter2->user_data);
+}
+
+void
+gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (store));
+
+ cancel_mount_operation (store);
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore *
+ model,
+ gchar const *root,
+ gchar const *virtual_root)
+{
+ GFile * file = NULL;
+ GFile * vfile = NULL;
+ FileBrowserNode * node;
+ gboolean equal = FALSE;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model),
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (root == NULL && model->priv->root == NULL)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ if (root != NULL) {
+ file = g_file_new_for_uri (root);
+ }
+
+ if (root != NULL && model->priv->root != NULL) {
+ equal = g_file_equal (file, model->priv->root->file);
+
+ if (equal && virtual_root == NULL) {
+ g_object_unref (file);
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+ }
+ }
+
+ if (virtual_root) {
+ vfile = g_file_new_for_uri (virtual_root);
+
+ if (equal && g_file_equal (vfile, model->priv->virtual_root->file)) {
+ if (file)
+ g_object_unref (file);
+
+ g_object_unref (vfile);
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+ }
+
+ g_object_unref (vfile);
+ }
+
+ /* make sure to cancel any previous mount operations */
+ cancel_mount_operation (model);
+
+ /* Always clear the model before altering the nodes */
+ model_clear (model, TRUE);
+ file_browser_node_free (model, model->priv->root);
+
+ model->priv->root = NULL;
+ model->priv->virtual_root = NULL;
+
+ if (file != NULL) {
+ /* Create the root node */
+ node = file_browser_node_dir_new (model, file, NULL);
+
+ g_object_unref (file);
+
+ model->priv->root = node;
+ return model_mount_root (model, virtual_root);
+ } else {
+ g_object_notify (G_OBJECT (model), "root");
+ g_object_notify (G_OBJECT (model), "virtual-root");
+ }
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_root (GeditFileBrowserStore * model,
+ gchar const *root)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model),
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ return gedit_file_browser_store_set_root_and_virtual_root (model,
+ root,
+ NULL);
+}
+
+gchar *
+gedit_file_browser_store_get_root (GeditFileBrowserStore * model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL);
+
+ if (model->priv->root == NULL || model->priv->root->file == NULL)
+ return NULL;
+ else
+ return g_file_get_uri (model->priv->root->file);
+}
+
+gchar *
+gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore * model)
+{
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), NULL);
+
+ if (model->priv->virtual_root == NULL || model->priv->virtual_root->file == NULL)
+ return NULL;
+ else
+ return g_file_get_uri (model->priv->virtual_root->file);
+}
+
+void
+_gedit_file_browser_store_iter_expanded (GeditFileBrowserStore * model,
+ GtkTreeIter * iter)
+{
+ FileBrowserNode *node;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ node = (FileBrowserNode *) (iter->user_data);
+
+ if (NODE_IS_DIR (node) && !NODE_LOADED (node)) {
+ /* Load it now */
+ model_load_directory (model, node);
+ }
+}
+
+void
+_gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore * model,
+ GtkTreeIter * iter)
+{
+ FileBrowserNode *node;
+ GSList *item;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (iter->user_data != NULL);
+
+ node = (FileBrowserNode *) (iter->user_data);
+
+ if (NODE_IS_DIR (node) && NODE_LOADED (node)) {
+ /* Unload children of the children, keeping 1 depth in cache */
+
+ for (item = FILE_BROWSER_NODE_DIR (node)->children; item;
+ item = item->next) {
+ node = (FileBrowserNode *) (item->data);
+
+ if (NODE_IS_DIR (node) && NODE_LOADED (node)) {
+ file_browser_node_unload (model, node,
+ TRUE);
+ model_check_dummy (model, node);
+ }
+ }
+ }
+}
+
+GeditFileBrowserStoreFilterMode
+gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore * model)
+{
+ return model->priv->filter_mode;
+}
+
+void
+gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore * model,
+ GeditFileBrowserStoreFilterMode
+ mode)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+
+ if (model->priv->filter_mode == mode)
+ return;
+
+ model->priv->filter_mode = mode;
+ model_refilter (model);
+
+ g_object_notify (G_OBJECT (model), "filter-mode");
+}
+
+void
+gedit_file_browser_store_set_filter_func (GeditFileBrowserStore * model,
+ GeditFileBrowserStoreFilterFunc
+ func, gpointer user_data)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+
+ model->priv->filter_func = func;
+ model->priv->filter_user_data = user_data;
+ model_refilter (model);
+}
+
+void
+gedit_file_browser_store_refilter (GeditFileBrowserStore * model)
+{
+ model_refilter (model);
+}
+
+GeditFileBrowserStoreFilterMode
+gedit_file_browser_store_filter_mode_get_default (void)
+{
+ return GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+}
+
+void
+gedit_file_browser_store_refresh (GeditFileBrowserStore * model)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model));
+
+ if (model->priv->root == NULL || model->priv->virtual_root == NULL)
+ return;
+
+ /* Clear the model */
+ g_signal_emit (model, model_signals[BEGIN_REFRESH], 0);
+ file_browser_node_unload (model, model->priv->virtual_root, TRUE);
+ model_load_directory (model, model->priv->virtual_root);
+ g_signal_emit (model, model_signals[END_REFRESH], 0);
+}
+
+static void
+reparent_node (FileBrowserNode * node, gboolean reparent)
+{
+ FileBrowserNodeDir * dir;
+ GSList * child;
+ GFile * parent;
+ gchar * base;
+
+ if (!node->file) {
+ return;
+ }
+
+ if (reparent) {
+ parent = node->parent->file;
+ base = g_file_get_basename (node->file);
+ g_object_unref (node->file);
+
+ node->file = g_file_get_child (parent, base);
+ g_free (base);
+ }
+
+ if (NODE_IS_DIR (node)) {
+ dir = FILE_BROWSER_NODE_DIR (node);
+
+ for (child = dir->children; child; child = child->next) {
+ reparent_node ((FileBrowserNode *)child->data, TRUE);
+ }
+ }
+}
+
+gboolean
+gedit_file_browser_store_rename (GeditFileBrowserStore * model,
+ GtkTreeIter * iter,
+ const gchar * new_name,
+ GError ** error)
+{
+ FileBrowserNode *node;
+ GFile * file;
+ GFile * parent;
+ GFile * previous;
+ GError * err = NULL;
+ gchar * olduri;
+ gchar * newuri;
+ GtkTreePath *path;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (iter->user_data != NULL, FALSE);
+
+ node = (FileBrowserNode *) (iter->user_data);
+
+ parent = g_file_get_parent (node->file);
+ g_return_val_if_fail (parent != NULL, FALSE);
+
+ file = g_file_get_child (parent, new_name);
+ g_object_unref (parent);
+
+ if (g_file_equal (node->file, file)) {
+ g_object_unref (file);
+ return TRUE;
+ }
+
+ if (g_file_move (node->file, file, G_FILE_COPY_NONE, NULL, NULL, NULL, &err)) {
+ previous = node->file;
+ node->file = file;
+
+ /* This makes sure the actual info for the node is requeried */
+ file_browser_node_set_name (node);
+ file_browser_node_set_from_info (model, node, NULL, TRUE);
+
+ reparent_node (node, FALSE);
+
+ if (model_node_visibility (model, node)) {
+ path = gedit_file_browser_store_get_path_real (model, node);
+ row_changed (model, &path, iter);
+ gtk_tree_path_free (path);
+
+ /* Reorder this item */
+ model_resort_node (model, node);
+ } else {
+ g_object_unref (previous);
+
+ if (error != NULL)
+ *error = g_error_new_literal (gedit_file_browser_store_error_quark (),
+ GEDIT_FILE_BROWSER_ERROR_RENAME,
+ _("The renamed file is currently filtered out. You need to adjust your filter settings to make the file visible"));
+ return FALSE;
+ }
+
+ olduri = g_file_get_uri (previous);
+ newuri = g_file_get_uri (node->file);
+
+ g_signal_emit (model, model_signals[RENAME], 0, olduri, newuri);
+
+ g_object_unref (previous);
+ g_free (olduri);
+ g_free (newuri);
+
+ return TRUE;
+ } else {
+ g_object_unref (file);
+
+ if (err) {
+ if (error != NULL) {
+ *error =
+ g_error_new_literal
+ (gedit_file_browser_store_error_quark (),
+ GEDIT_FILE_BROWSER_ERROR_RENAME,
+ err->message);
+ }
+
+ g_error_free (err);
+ }
+
+ return FALSE;
+ }
+}
+
+static void
+async_data_free (AsyncData * data)
+{
+ g_object_unref (data->cancellable);
+
+ g_list_foreach (data->files, (GFunc)g_object_unref, NULL);
+ g_list_free (data->files);
+
+ if (!data->removed)
+ data->model->priv->async_handles = g_slist_remove (data->model->priv->async_handles, data);
+
+ g_free (data);
+}
+
+static gboolean
+emit_no_trash (AsyncData * data)
+{
+ /* Emit the no trash error */
+ gboolean ret;
+
+ g_signal_emit (data->model, model_signals[NO_TRASH], 0, data->files, &ret);
+ return ret;
+}
+
+typedef struct {
+ GeditFileBrowserStore * model;
+ GFile * file;
+} IdleDelete;
+
+static gboolean
+file_deleted (IdleDelete * data)
+{
+ FileBrowserNode * node;
+ node = model_find_node (data->model, NULL, data->file);
+
+ if (node != NULL)
+ model_remove_node (data->model, node, NULL, TRUE);
+
+ return FALSE;
+}
+
+static gboolean
+delete_files (GIOSchedulerJob * job,
+ GCancellable * cancellable,
+ AsyncData * data)
+{
+ GFile * file;
+ GError * error = NULL;
+ gboolean ret;
+ gint code;
+ IdleDelete delete;
+
+ /* Check if our job is done */
+ if (!data->iter)
+ return FALSE;
+
+ /* Move a file to the trash */
+ file = G_FILE (data->iter->data);
+
+ if (data->trash)
+ ret = g_file_trash (file, cancellable, &error);
+ else
+ ret = g_file_delete (file, cancellable, &error);
+
+ if (ret) {
+ delete.model = data->model;
+ delete.file = file;
+
+ /* Remove the file from the model in the main loop */
+ g_io_scheduler_job_send_to_mainloop (job, (GSourceFunc)file_deleted, &delete, NULL);
+ } else if (!ret && error) {
+ code = error->code;
+ g_error_free (error);
+
+ if (data->trash && code == G_IO_ERROR_NOT_SUPPORTED) {
+ /* Trash is not supported on this system ... */
+ if (g_io_scheduler_job_send_to_mainloop (job, (GSourceFunc)emit_no_trash, data, NULL))
+ {
+ /* Changes this into a delete job */
+ data->trash = FALSE;
+ data->iter = data->files;
+
+ return TRUE;
+ }
+
+ /* End the job */
+ return FALSE;
+ } else if (code == G_IO_ERROR_CANCELLED) {
+ /* Job has been cancelled, just let the job end */
+ return FALSE;
+ }
+ }
+
+ /* Process the next item */
+ data->iter = data->iter->next;
+ return TRUE;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_delete_all (GeditFileBrowserStore *model,
+ GList *rows, gboolean trash)
+{
+ FileBrowserNode * node;
+ AsyncData * data;
+ GList * files = NULL;
+ GList * row;
+ GtkTreeIter iter;
+ GtkTreePath * prev = NULL;
+ GtkTreePath * path;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ if (rows == NULL)
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ /* First we sort the paths so that we can later on remove any
+ files/directories that are actually subfiles/directories of
+ a directory that's also deleted */
+ rows = g_list_sort (g_list_copy (rows), (GCompareFunc)gtk_tree_path_compare);
+
+ for (row = rows; row; row = row->next) {
+ path = (GtkTreePath *)(row->data);
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path))
+ continue;
+
+ /* Skip if the current path is actually a descendant of the
+ previous path */
+ if (prev != NULL && gtk_tree_path_is_descendant (path, prev))
+ continue;
+
+ prev = path;
+ node = (FileBrowserNode *)(iter.user_data);
+ files = g_list_prepend (files, g_object_ref (node->file));
+ }
+
+ data = g_new (AsyncData, 1);
+
+ data->model = model;
+ data->cancellable = g_cancellable_new ();
+ data->files = files;
+ data->trash = trash;
+ data->iter = files;
+ data->removed = FALSE;
+
+ model->priv->async_handles =
+ g_slist_prepend (model->priv->async_handles, data);
+
+ g_io_scheduler_push_job ((GIOSchedulerJobFunc)delete_files,
+ data,
+ (GDestroyNotify)async_data_free,
+ G_PRIORITY_DEFAULT,
+ data->cancellable);
+ g_list_free (rows);
+
+ return GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_delete (GeditFileBrowserStore * model,
+ GtkTreeIter * iter, gboolean trash)
+{
+ FileBrowserNode *node;
+ GList *rows = NULL;
+ GeditFileBrowserStoreResult result;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ g_return_val_if_fail (iter != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+ g_return_val_if_fail (iter->user_data != NULL, GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE);
+
+ node = (FileBrowserNode *) (iter->user_data);
+
+ if (NODE_IS_DUMMY (node))
+ return GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE;
+
+ rows = g_list_append(NULL, gedit_file_browser_store_get_path_real (model, node));
+ result = gedit_file_browser_store_delete_all (model, rows, trash);
+
+ g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free (rows);
+
+ return result;
+}
+
+gboolean
+gedit_file_browser_store_new_file (GeditFileBrowserStore * model,
+ GtkTreeIter * parent,
+ GtkTreeIter * iter)
+{
+ GFile * file;
+ GFileOutputStream * stream;
+ FileBrowserNodeDir *parent_node;
+ gboolean result = FALSE;
+ FileBrowserNode *node;
+ GError * error = NULL;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (parent != NULL, FALSE);
+ g_return_val_if_fail (parent->user_data != NULL, FALSE);
+ g_return_val_if_fail (NODE_IS_DIR
+ ((FileBrowserNode *) (parent->user_data)),
+ FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ parent_node = FILE_BROWSER_NODE_DIR (parent->user_data);
+ /* Translators: This is the default name of new files created by the file browser pane. */
+ file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("file"));
+
+ stream = g_file_create (file, G_FILE_CREATE_NONE, NULL, &error);
+
+ if (!stream)
+ {
+ g_signal_emit (model, model_signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_NEW_FILE,
+ error->message);
+ g_error_free (error);
+ } else {
+ g_object_unref (stream);
+ node = model_add_node_from_file (model,
+ (FileBrowserNode *)parent_node,
+ file,
+ NULL);
+
+ if (model_node_visibility (model, node)) {
+ iter->user_data = node;
+ result = TRUE;
+ } else {
+ g_signal_emit (model, model_signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_NEW_FILE,
+ _
+ ("The new file is currently filtered out. You need to adjust your filter settings to make the file visible"));
+ }
+ }
+
+ g_object_unref (file);
+ return result;
+}
+
+gboolean
+gedit_file_browser_store_new_directory (GeditFileBrowserStore * model,
+ GtkTreeIter * parent,
+ GtkTreeIter * iter)
+{
+ GFile * file;
+ FileBrowserNodeDir *parent_node;
+ GError * error = NULL;
+ FileBrowserNode *node;
+ gboolean result = FALSE;
+
+ g_return_val_if_fail (GEDIT_IS_FILE_BROWSER_STORE (model), FALSE);
+ g_return_val_if_fail (parent != NULL, FALSE);
+ g_return_val_if_fail (parent->user_data != NULL, FALSE);
+ g_return_val_if_fail (NODE_IS_DIR
+ ((FileBrowserNode *) (parent->user_data)),
+ FALSE);
+ g_return_val_if_fail (iter != NULL, FALSE);
+
+ parent_node = FILE_BROWSER_NODE_DIR (parent->user_data);
+ /* Translators: This is the default name of new directories created by the file browser pane. */
+ file = unique_new_name (((FileBrowserNode *) parent_node)->file, _("directory"));
+
+ if (!g_file_make_directory (file, NULL, &error)) {
+ g_signal_emit (model, model_signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_NEW_DIRECTORY,
+ error->message);
+ g_error_free (error);
+ } else {
+ node = model_add_node_from_file (model,
+ (FileBrowserNode *)parent_node,
+ file,
+ NULL);
+
+ if (model_node_visibility (model, node)) {
+ iter->user_data = node;
+ result = TRUE;
+ } else {
+ g_signal_emit (model, model_signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_NEW_FILE,
+ _
+ ("The new directory is currently filtered out. You need to adjust your filter settings to make the directory visible"));
+ }
+ }
+
+ g_object_unref (file);
+ return result;
+}
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-store.h b/plugins/filebrowser/gedit-file-browser-store.h
new file mode 100755
index 00000000..f31da327
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-store.h
@@ -0,0 +1,200 @@
+/*
+ * gedit-file-browser-store.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_FILE_BROWSER_STORE_H__
+#define __GEDIT_FILE_BROWSER_STORE_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define GEDIT_TYPE_FILE_BROWSER_STORE (gedit_file_browser_store_get_type ())
+#define GEDIT_FILE_BROWSER_STORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStore))
+#define GEDIT_FILE_BROWSER_STORE_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStore const))
+#define GEDIT_FILE_BROWSER_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStoreClass))
+#define GEDIT_IS_FILE_BROWSER_STORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_STORE))
+#define GEDIT_IS_FILE_BROWSER_STORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_STORE))
+#define GEDIT_FILE_BROWSER_STORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_STORE, GeditFileBrowserStoreClass))
+
+typedef enum
+{
+ GEDIT_FILE_BROWSER_STORE_COLUMN_ICON = 0,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NAME,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_EMBLEM,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NUM
+} GeditFileBrowserStoreColumn;
+
+typedef enum
+{
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY = 1 << 0,
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN = 1 << 1,
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT = 1 << 2,
+ GEDIT_FILE_BROWSER_STORE_FLAG_LOADED = 1 << 3,
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED = 1 << 4,
+ GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY = 1 << 5
+} GeditFileBrowserStoreFlag;
+
+typedef enum
+{
+ GEDIT_FILE_BROWSER_STORE_RESULT_OK,
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE,
+ GEDIT_FILE_BROWSER_STORE_RESULT_ERROR,
+ GEDIT_FILE_BROWSER_STORE_RESULT_NO_TRASH,
+ GEDIT_FILE_BROWSER_STORE_RESULT_MOUNTING,
+ GEDIT_FILE_BROWSER_STORE_RESULT_NUM
+} GeditFileBrowserStoreResult;
+
+typedef enum
+{
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_NONE = 0,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN = 1 << 0,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY = 1 << 1
+} GeditFileBrowserStoreFilterMode;
+
+#define FILE_IS_DIR(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_DIRECTORY)
+#define FILE_IS_HIDDEN(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_HIDDEN)
+#define FILE_IS_TEXT(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_TEXT)
+#define FILE_LOADED(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_LOADED)
+#define FILE_IS_FILTERED(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_FILTERED)
+#define FILE_IS_DUMMY(flags) (flags & GEDIT_FILE_BROWSER_STORE_FLAG_IS_DUMMY)
+
+typedef struct _GeditFileBrowserStore GeditFileBrowserStore;
+typedef struct _GeditFileBrowserStoreClass GeditFileBrowserStoreClass;
+typedef struct _GeditFileBrowserStorePrivate GeditFileBrowserStorePrivate;
+
+typedef gboolean (*GeditFileBrowserStoreFilterFunc) (GeditFileBrowserStore
+ * model,
+ GtkTreeIter * iter,
+ gpointer user_data);
+
+struct _GeditFileBrowserStore
+{
+ GObject parent;
+
+ GeditFileBrowserStorePrivate *priv;
+};
+
+struct _GeditFileBrowserStoreClass {
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*begin_loading) (GeditFileBrowserStore * model,
+ GtkTreeIter * iter);
+ void (*end_loading) (GeditFileBrowserStore * model,
+ GtkTreeIter * iter);
+ void (*error) (GeditFileBrowserStore * model,
+ guint code,
+ gchar * message);
+ gboolean (*no_trash) (GeditFileBrowserStore * model,
+ GList * files);
+ void (*rename) (GeditFileBrowserStore * model,
+ const gchar * olduri,
+ const gchar * newuri);
+ void (*begin_refresh) (GeditFileBrowserStore * model);
+ void (*end_refresh) (GeditFileBrowserStore * model);
+ void (*unload) (GeditFileBrowserStore * model,
+ const gchar * uri);
+};
+
+GType gedit_file_browser_store_get_type (void) G_GNUC_CONST;
+GType gedit_file_browser_store_register_type (GTypeModule * module);
+
+GeditFileBrowserStore *gedit_file_browser_store_new (gchar const *root);
+
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_root_and_virtual_root (GeditFileBrowserStore * model,
+ gchar const *root,
+ gchar const *virtual_root);
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_root (GeditFileBrowserStore * model,
+ gchar const *root);
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root (GeditFileBrowserStore * model,
+ GtkTreeIter * iter);
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_from_string (GeditFileBrowserStore * model,
+ gchar const *root);
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_up (GeditFileBrowserStore * model);
+GeditFileBrowserStoreResult
+gedit_file_browser_store_set_virtual_root_top (GeditFileBrowserStore * model);
+
+gboolean
+gedit_file_browser_store_get_iter_virtual_root (GeditFileBrowserStore * model,
+ GtkTreeIter * iter);
+gboolean gedit_file_browser_store_get_iter_root (GeditFileBrowserStore * model,
+ GtkTreeIter * iter);
+gchar * gedit_file_browser_store_get_root (GeditFileBrowserStore * model);
+gchar * gedit_file_browser_store_get_virtual_root (GeditFileBrowserStore * model);
+
+gboolean gedit_file_browser_store_iter_equal (GeditFileBrowserStore * model,
+ GtkTreeIter * iter1,
+ GtkTreeIter * iter2);
+
+void gedit_file_browser_store_set_value (GeditFileBrowserStore * tree_model,
+ GtkTreeIter * iter,
+ gint column,
+ GValue * value);
+
+void _gedit_file_browser_store_iter_expanded (GeditFileBrowserStore * model,
+ GtkTreeIter * iter);
+void _gedit_file_browser_store_iter_collapsed (GeditFileBrowserStore * model,
+ GtkTreeIter * iter);
+
+GeditFileBrowserStoreFilterMode
+gedit_file_browser_store_get_filter_mode (GeditFileBrowserStore * model);
+void gedit_file_browser_store_set_filter_mode (GeditFileBrowserStore * model,
+ GeditFileBrowserStoreFilterMode mode);
+void gedit_file_browser_store_set_filter_func (GeditFileBrowserStore * model,
+ GeditFileBrowserStoreFilterFunc func,
+ gpointer user_data);
+void gedit_file_browser_store_refilter (GeditFileBrowserStore * model);
+GeditFileBrowserStoreFilterMode
+gedit_file_browser_store_filter_mode_get_default (void);
+
+void gedit_file_browser_store_refresh (GeditFileBrowserStore * model);
+gboolean gedit_file_browser_store_rename (GeditFileBrowserStore * model,
+ GtkTreeIter * iter,
+ gchar const *new_name,
+ GError ** error);
+GeditFileBrowserStoreResult
+gedit_file_browser_store_delete (GeditFileBrowserStore * model,
+ GtkTreeIter * iter,
+ gboolean trash);
+GeditFileBrowserStoreResult
+gedit_file_browser_store_delete_all (GeditFileBrowserStore * model,
+ GList *rows,
+ gboolean trash);
+
+gboolean gedit_file_browser_store_new_file (GeditFileBrowserStore * model,
+ GtkTreeIter * parent,
+ GtkTreeIter * iter);
+gboolean gedit_file_browser_store_new_directory (GeditFileBrowserStore * model,
+ GtkTreeIter * parent,
+ GtkTreeIter * iter);
+
+void gedit_file_browser_store_cancel_mount_operation (GeditFileBrowserStore *store);
+
+G_END_DECLS
+#endif /* __GEDIT_FILE_BROWSER_STORE_H__ */
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-utils.c b/plugins/filebrowser/gedit-file-browser-utils.c
new file mode 100755
index 00000000..d8f4028a
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-utils.c
@@ -0,0 +1,198 @@
+/*
+ * gedit-file-bookmarks-store.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gedit-file-browser-utils.h"
+#include <gedit/gedit-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);
+ gtk_icon_info_free (info);
+
+ return ret;
+}
+
+GdkPixbuf *
+gedit_file_browser_utils_pixbuf_from_file (GFile * file,
+ GtkIconSize size)
+{
+ GIcon * icon;
+ GFileInfo * info;
+ GdkPixbuf * ret = NULL;
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_ICON,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ NULL);
+
+ if (!info)
+ return NULL;
+
+ icon = g_file_info_get_icon (info);
+ if (icon != NULL)
+ ret = gedit_file_browser_utils_pixbuf_from_icon (icon, size);
+
+ g_object_unref (info);
+
+ return ret;
+}
+
+gchar *
+gedit_file_browser_utils_file_basename (GFile * file)
+{
+ gchar *uri;
+ gchar *ret;
+
+ uri = g_file_get_uri (file);
+ ret = gedit_file_browser_utils_uri_basename (uri);
+ g_free (uri);
+
+ return ret;
+}
+
+gchar *
+gedit_file_browser_utils_uri_basename (gchar const * uri)
+{
+ return gedit_utils_basename_for_display (uri);
+}
+
+gboolean
+gedit_file_browser_utils_confirmation_dialog (GeditWindow * window,
+ GtkMessageType type,
+ gchar const *message,
+ gchar const *secondary,
+ gchar const * button_stock,
+ gchar const * button_label)
+{
+ GtkWidget *dlg;
+ gint ret;
+ GtkWidget *button;
+
+ dlg = gtk_message_dialog_new (GTK_WINDOW (window),
+ GTK_DIALOG_MODAL |
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ type,
+ GTK_BUTTONS_NONE, "%s", message);
+
+ if (secondary)
+ gtk_message_dialog_format_secondary_text
+ (GTK_MESSAGE_DIALOG (dlg), "%s", secondary);
+
+ /* Add a cancel button */
+ button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
+ gtk_widget_show (button);
+
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+ gtk_dialog_add_action_widget (GTK_DIALOG (dlg),
+ button,
+ GTK_RESPONSE_CANCEL);
+
+ /* Add custom button */
+ button = gtk_button_new_from_stock (button_stock);
+
+ if (button_label) {
+ gtk_button_set_use_stock (GTK_BUTTON (button), FALSE);
+ gtk_button_set_label (GTK_BUTTON (button), button_label);
+ }
+
+ gtk_widget_show (button);
+ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
+ gtk_dialog_add_action_widget (GTK_DIALOG (dlg),
+ button,
+ GTK_RESPONSE_OK);
+
+ ret = gtk_dialog_run (GTK_DIALOG (dlg));
+ gtk_widget_destroy (dlg);
+
+ return (ret == GTK_RESPONSE_OK);
+}
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-utils.h b/plugins/filebrowser/gedit-file-browser-utils.h
new file mode 100755
index 00000000..fc9acbce
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-utils.h
@@ -0,0 +1,27 @@
+#ifndef __GEDIT_FILE_BROWSER_UTILS_H__
+#define __GEDIT_FILE_BROWSER_UTILS_H__
+
+#include <gedit/gedit-window.h>
+#include <gio/gio.h>
+
+GdkPixbuf *gedit_file_browser_utils_pixbuf_from_theme (gchar const *name,
+ GtkIconSize size);
+
+GdkPixbuf *gedit_file_browser_utils_pixbuf_from_icon (GIcon * icon,
+ GtkIconSize size);
+GdkPixbuf *gedit_file_browser_utils_pixbuf_from_file (GFile * file,
+ GtkIconSize size);
+
+gchar * gedit_file_browser_utils_file_basename (GFile * file);
+gchar * gedit_file_browser_utils_uri_basename (gchar const * uri);
+
+gboolean gedit_file_browser_utils_confirmation_dialog (GeditWindow * window,
+ GtkMessageType type,
+ gchar const *message,
+ gchar const *secondary,
+ gchar const * button_stock,
+ gchar const * button_label);
+
+#endif /* __GEDIT_FILE_BROWSER_UTILS_H__ */
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-view.c b/plugins/filebrowser/gedit-file-browser-view.c
new file mode 100755
index 00000000..05733da1
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-view.c
@@ -0,0 +1,1256 @@
+/*
+ * gedit-file-browser-view.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <gio/gio.h>
+#include <gedit/gedit-plugin.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "gedit-file-browser-store.h"
+#include "gedit-file-bookmarks-store.h"
+#include "gedit-file-browser-view.h"
+#include "gedit-file-browser-marshal.h"
+#include "gedit-file-browser-enum-types.h"
+
+#define GEDIT_FILE_BROWSER_VIEW_GET_PRIVATE(object)( \
+ G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewPrivate))
+
+struct _GeditFileBrowserViewPrivate
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *pixbuf_renderer;
+ GtkCellRenderer *text_renderer;
+
+ GtkTreeModel *model;
+ GtkTreeRowReference *editable;
+
+ GdkCursor *busy_cursor;
+
+ /* CLick policy */
+ GeditFileBrowserViewClickPolicy click_policy;
+ GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */
+ GtkTreePath *hover_path;
+ GdkCursor *hand_cursor;
+ gboolean ignore_release;
+ gboolean selected_on_button_down;
+ gint drag_button;
+ gboolean drag_started;
+
+ gboolean restore_expand_state;
+ gboolean is_refresh;
+ GHashTable * expand_state;
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+
+ PROP_CLICK_POLICY,
+ PROP_RESTORE_EXPAND_STATE
+};
+
+/* Signals */
+enum
+{
+ ERROR,
+ FILE_ACTIVATED,
+ DIRECTORY_ACTIVATED,
+ BOOKMARK_ACTIVATED,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0 };
+
+static const GtkTargetEntry drag_source_targets[] = {
+ { "text/uri-list", 0, 0 }
+};
+
+GEDIT_PLUGIN_DEFINE_TYPE (GeditFileBrowserView, gedit_file_browser_view,
+ GTK_TYPE_TREE_VIEW)
+
+static void on_cell_edited (GtkCellRendererText * cell,
+ gchar * path,
+ gchar * new_text,
+ GeditFileBrowserView * tree_view);
+
+static void on_begin_refresh (GeditFileBrowserStore * model,
+ GeditFileBrowserView * view);
+static void on_end_refresh (GeditFileBrowserStore * model,
+ GeditFileBrowserView * view);
+
+static void on_unload (GeditFileBrowserStore * model,
+ gchar const * uri,
+ GeditFileBrowserView * view);
+
+static void on_row_inserted (GeditFileBrowserStore * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter,
+ GeditFileBrowserView * view);
+
+static void
+gedit_file_browser_view_finalize (GObject * object)
+{
+ GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW(object);
+
+ if (obj->priv->hand_cursor)
+ gdk_cursor_unref(obj->priv->hand_cursor);
+
+ if (obj->priv->hover_path)
+ gtk_tree_path_free (obj->priv->hover_path);
+
+ if (obj->priv->expand_state)
+ {
+ g_hash_table_destroy (obj->priv->expand_state);
+ obj->priv->expand_state = NULL;
+ }
+
+ gdk_cursor_unref (obj->priv->busy_cursor);
+
+ G_OBJECT_CLASS (gedit_file_browser_view_parent_class)->
+ finalize (object);
+}
+
+static void
+add_expand_state (GeditFileBrowserView * view,
+ gchar const * uri)
+{
+ GFile * file;
+
+ if (!uri)
+ return;
+
+ file = g_file_new_for_uri (uri);
+
+ if (view->priv->expand_state)
+ g_hash_table_insert (view->priv->expand_state, file, file);
+ else
+ g_object_unref (file);
+}
+
+static void
+remove_expand_state (GeditFileBrowserView * view,
+ gchar const * uri)
+{
+ GFile * file;
+
+ if (!uri)
+ return;
+
+ file = g_file_new_for_uri (uri);
+
+ if (view->priv->expand_state)
+ g_hash_table_remove (view->priv->expand_state, file);
+
+ g_object_unref (file);
+}
+
+static void
+row_expanded (GtkTreeView * tree_view,
+ GtkTreeIter * iter,
+ GtkTreePath * path)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view);
+ gchar * uri;
+
+ if (GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_expanded)
+ GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_expanded (tree_view, iter, path);
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ return;
+
+ if (view->priv->restore_expand_state)
+ {
+ gtk_tree_model_get (view->priv->model,
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI,
+ &uri,
+ -1);
+
+ add_expand_state (view, uri);
+ g_free (uri);
+ }
+
+ _gedit_file_browser_store_iter_expanded (GEDIT_FILE_BROWSER_STORE (view->priv->model),
+ iter);
+}
+
+static void
+row_collapsed (GtkTreeView * tree_view,
+ GtkTreeIter * iter,
+ GtkTreePath * path)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (tree_view);
+ gchar * uri;
+
+ if (GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_collapsed)
+ GTK_TREE_VIEW_CLASS (gedit_file_browser_view_parent_class)->row_collapsed (tree_view, iter, path);
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ return;
+
+ if (view->priv->restore_expand_state)
+ {
+ gtk_tree_model_get (view->priv->model,
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI,
+ &uri,
+ -1);
+
+ remove_expand_state (view, uri);
+ g_free (uri);
+ }
+
+ _gedit_file_browser_store_iter_collapsed (GEDIT_FILE_BROWSER_STORE (view->priv->model),
+ iter);
+}
+
+static gboolean
+leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE &&
+ view->priv->hover_path != NULL) {
+ gtk_tree_path_free (view->priv->hover_path);
+ view->priv->hover_path = NULL;
+ }
+
+ // Chainup
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->leave_notify_event (widget, event);
+}
+
+static gboolean
+enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) {
+ if (view->priv->hover_path != NULL)
+ gtk_tree_path_free (view->priv->hover_path);
+
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ event->x, event->y,
+ &view->priv->hover_path,
+ NULL, NULL, NULL);
+
+ if (view->priv->hover_path != NULL)
+ gdk_window_set_cursor (gtk_widget_get_window (widget),
+ view->priv->hand_cursor);
+ }
+
+ // Chainup
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->enter_notify_event (widget, event);
+}
+
+static gboolean
+motion_notify_event (GtkWidget * widget,
+ GdkEventMotion * event)
+{
+ GtkTreePath *old_hover_path;
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) {
+ old_hover_path = view->priv->hover_path;
+ gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ event->x, event->y,
+ &view->priv->hover_path,
+ NULL, NULL, NULL);
+
+ if ((old_hover_path != NULL) != (view->priv->hover_path != NULL)) {
+ if (view->priv->hover_path != NULL)
+ gdk_window_set_cursor (gtk_widget_get_window (widget),
+ view->priv->hand_cursor);
+ else
+ gdk_window_set_cursor (gtk_widget_get_window (widget),
+ NULL);
+ }
+
+ if (old_hover_path != NULL)
+ gtk_tree_path_free (old_hover_path);
+ }
+
+ // Chainup
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->motion_notify_event (widget, event);
+}
+
+static void
+set_click_policy_property (GeditFileBrowserView *obj,
+ GeditFileBrowserViewClickPolicy click_policy)
+{
+ GtkTreeIter iter;
+ GdkDisplay *display;
+ GdkWindow *win;
+
+ obj->priv->click_policy = click_policy;
+
+ if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) {
+ if (obj->priv->hand_cursor == NULL)
+ obj->priv->hand_cursor = gdk_cursor_new(GDK_HAND2);
+ } else if (click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE) {
+ if (obj->priv->hover_path != NULL) {
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (obj->priv->model),
+ &iter, obj->priv->hover_path))
+ gtk_tree_model_row_changed (GTK_TREE_MODEL (obj->priv->model),
+ obj->priv->hover_path, &iter);
+
+ gtk_tree_path_free (obj->priv->hover_path);
+ obj->priv->hover_path = NULL;
+ }
+
+ if (GTK_WIDGET_REALIZED (GTK_WIDGET (obj))) {
+ win = gtk_widget_get_window (GTK_WIDGET (obj));
+ gdk_window_set_cursor (win, NULL);
+
+ display = gtk_widget_get_display (GTK_WIDGET (obj));
+
+ if (display != NULL)
+ gdk_display_flush (display);
+ }
+
+ if (obj->priv->hand_cursor) {
+ gdk_cursor_unref (obj->priv->hand_cursor);
+ obj->priv->hand_cursor = NULL;
+ }
+ }
+}
+
+static void
+directory_activated (GeditFileBrowserView *view,
+ GtkTreeIter *iter)
+{
+ gedit_file_browser_store_set_virtual_root (GEDIT_FILE_BROWSER_STORE (view->priv->model), iter);
+}
+
+static void
+activate_selected_files (GeditFileBrowserView *view) {
+ GtkTreeView *tree_view = GTK_TREE_VIEW (view);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+ GList *rows, *row;
+ GtkTreePath *directory = NULL;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GeditFileBrowserStoreFlag flags;
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &view->priv->model);
+
+ for (row = rows; row; row = row->next) {
+ path = (GtkTreePath *)(row->data);
+
+ /* Get iter from path */
+ if (!gtk_tree_model_get_iter (view->priv->model, &iter, path))
+ continue;
+
+ gtk_tree_model_get (view->priv->model, &iter, GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1);
+
+ if (FILE_IS_DIR (flags)) {
+ if (directory == NULL)
+ directory = path;
+
+ } else if (!FILE_IS_DUMMY (flags)) {
+ g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter);
+ }
+ }
+
+ if (directory != NULL) {
+ if (gtk_tree_model_get_iter (view->priv->model, &iter, directory))
+ g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter);
+ }
+
+ g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free (rows);
+}
+
+static void
+activate_selected_bookmark (GeditFileBrowserView *view) {
+ GtkTreeView *tree_view = GTK_TREE_VIEW (view);
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
+ GtkTreeIter iter;
+
+ if (gtk_tree_selection_get_selected (selection, &view->priv->model, &iter))
+ g_signal_emit (view, signals[BOOKMARK_ACTIVATED], 0, &iter);
+}
+
+static void
+activate_selected_items (GeditFileBrowserView *view)
+{
+ if (GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ activate_selected_files (view);
+ else if (GEDIT_IS_FILE_BOOKMARKS_STORE (view->priv->model))
+ activate_selected_bookmark (view);
+}
+
+static void
+toggle_hidden_filter (GeditFileBrowserView *view)
+{
+ GeditFileBrowserStoreFilterMode mode;
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ {
+ mode = gedit_file_browser_store_get_filter_mode
+ (GEDIT_FILE_BROWSER_STORE (view->priv->model));
+ mode ^= GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
+ gedit_file_browser_store_set_filter_mode
+ (GEDIT_FILE_BROWSER_STORE (view->priv->model), mode);
+ }
+}
+
+static gboolean
+button_event_modifies_selection (GdkEventButton *event)
+{
+ return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
+}
+
+static void
+drag_begin (GtkWidget *widget,
+ GdkDragContext *context)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ view->priv->drag_button = 0;
+ view->priv->drag_started = TRUE;
+
+ /* Chain up */
+ GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->drag_begin (widget, context);
+}
+
+static void
+did_not_drag (GeditFileBrowserView *view,
+ GdkEventButton *event)
+{
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+
+ tree_view = GTK_TREE_VIEW (view);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
+ &path, NULL, NULL, NULL)) {
+ if ((view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE)
+ && !button_event_modifies_selection(event)
+ && (event->button == 1 || event->button == 2)) {
+ /* Activate all selected items, and leave them selected */
+ activate_selected_items (view);
+ } else if ((event->button == 1 || event->button == 2)
+ && ((event->state & GDK_CONTROL_MASK) != 0 ||
+ (event->state & GDK_SHIFT_MASK) == 0)
+ && view->priv->selected_on_button_down) {
+ if (!button_event_modifies_selection (event)) {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+ } else {
+ gtk_tree_selection_unselect_path (selection, path);
+ }
+ }
+
+ gtk_tree_path_free (path);
+ }
+}
+
+static gboolean
+button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ GeditFileBrowserView *view = GEDIT_FILE_BROWSER_VIEW (widget);
+
+ if (event->button == view->priv->drag_button) {
+ view->priv->drag_button = 0;
+
+ if (!view->priv->drag_started &&
+ !view->priv->ignore_release)
+ did_not_drag (view, event);
+ }
+
+ /* Chain up */
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->button_release_event (widget, event);
+}
+
+static gboolean
+button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ int double_click_time;
+ static int click_count = 0;
+ static guint32 last_click_time = 0;
+ GeditFileBrowserView *view;
+ GtkTreeView *tree_view;
+ GtkTreeSelection *selection;
+ GtkTreePath *path;
+ int expander_size;
+ int horizontal_separator;
+ gboolean on_expander;
+ gboolean call_parent;
+ gboolean selected;
+ GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS(gedit_file_browser_view_parent_class);
+
+ tree_view = GTK_TREE_VIEW (widget);
+ view = GEDIT_FILE_BROWSER_VIEW (widget);
+ selection = gtk_tree_view_get_selection (tree_view);
+
+ /* Get double click time */
+ g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
+ "gtk-double-click-time", &double_click_time,
+ NULL);
+
+ /* Determine click count */
+ if (event->time - last_click_time < double_click_time)
+ click_count++;
+ else
+ click_count = 0;
+
+ last_click_time = event->time;
+
+ /* Ignore double click if we are in single click mode */
+ if (view->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE &&
+ click_count >= 2) {
+ return TRUE;
+ }
+
+ view->priv->ignore_release = FALSE;
+ call_parent = TRUE;
+
+ if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y,
+ &path, NULL, NULL, NULL)) {
+ /* Keep track of path of last click so double clicks only happen
+ * on the same item */
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS) {
+ if (view->priv->double_click_path[1])
+ gtk_tree_path_free (view->priv->double_click_path[1]);
+
+ view->priv->double_click_path[1] = view->priv->double_click_path[0];
+ view->priv->double_click_path[0] = gtk_tree_path_copy (path);
+ }
+
+ if (event->type == GDK_2BUTTON_PRESS) {
+ if (view->priv->double_click_path[1] &&
+ gtk_tree_path_compare (view->priv->double_click_path[0], view->priv->double_click_path[1]) == 0)
+ activate_selected_items (view);
+
+ /* Chain up */
+ widget_parent->button_press_event (widget, event);
+ } else {
+ /* We're going to filter out some situations where
+ * we can't let the default code run because all
+ * but one row would be would be deselected. We don't
+ * want that; we want the right click menu or single
+ * click to apply to everything that's currently selected. */
+ selected = gtk_tree_selection_path_is_selected (selection, path);
+
+ if (event->button == 3 && selected)
+ call_parent = FALSE;
+
+ if ((event->button == 1 || event->button == 2) &&
+ ((event->state & GDK_CONTROL_MASK) != 0 ||
+ (event->state & GDK_SHIFT_MASK) == 0)) {
+ gtk_widget_style_get (widget,
+ "expander-size", &expander_size,
+ "horizontal-separator", &horizontal_separator,
+ NULL);
+ on_expander = (event->x <= horizontal_separator / 2 +
+ gtk_tree_path_get_depth (path) * expander_size);
+
+ view->priv->selected_on_button_down = selected;
+
+ if (selected) {
+ call_parent = on_expander || gtk_tree_selection_count_selected_rows (selection) == 1;
+ view->priv->ignore_release = call_parent && view->priv->click_policy != GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE;
+ } else if ((event->state & GDK_CONTROL_MASK) != 0) {
+ call_parent = FALSE;
+ gtk_tree_selection_select_path (selection, path);
+ } else {
+ view->priv->ignore_release = on_expander;
+ }
+ }
+
+ if (call_parent) {
+ /* Chain up */
+ widget_parent->button_press_event (widget, event);
+ } else if (selected) {
+ gtk_widget_grab_focus (widget);
+ }
+
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS) {
+ view->priv->drag_started = FALSE;
+ view->priv->drag_button = event->button;
+ }
+ }
+
+ gtk_tree_path_free (path);
+ } else {
+ if ((event->button == 1 || event->button == 2) &&
+ event->type == GDK_BUTTON_PRESS) {
+ if (view->priv->double_click_path[1])
+ gtk_tree_path_free (view->priv->double_click_path[1]);
+
+ view->priv->double_click_path[1] = view->priv->double_click_path[0];
+ view->priv->double_click_path[0] = NULL;
+ }
+
+ gtk_tree_selection_unselect_all (selection);
+ /* Chain up */
+ widget_parent->button_press_event (widget, event);
+ }
+
+ /* We already chained up if nescessary, so just return TRUE */
+ return TRUE;
+}
+
+static gboolean
+key_press_event (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ GeditFileBrowserView *view;
+ guint modifiers;
+ gboolean handled;
+
+ view = GEDIT_FILE_BROWSER_VIEW (widget);
+ handled = FALSE;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ switch (event->keyval) {
+ case GDK_space:
+ if (event->state & GDK_CONTROL_MASK) {
+ handled = FALSE;
+ break;
+ }
+ if (!GTK_WIDGET_HAS_FOCUS (widget)) {
+ handled = FALSE;
+ break;
+ }
+
+ activate_selected_items (view);
+ handled = TRUE;
+ break;
+
+ case GDK_Return:
+ case GDK_KP_Enter:
+ activate_selected_items (view);
+ handled = TRUE;
+ break;
+
+ case GDK_h:
+ if ((event->state & modifiers) == GDK_CONTROL_MASK) {
+ toggle_hidden_filter (view);
+ handled = TRUE;
+ break;
+ }
+
+ default:
+ handled = FALSE;
+ }
+
+ /* Chain up */
+ if (!handled)
+ return GTK_WIDGET_CLASS (gedit_file_browser_view_parent_class)->key_press_event (widget, event);
+
+ return TRUE;
+}
+
+static void
+fill_expand_state (GeditFileBrowserView * view, GtkTreeIter * iter)
+{
+ GtkTreePath * path;
+ GtkTreeIter child;
+ gchar * uri;
+
+ if (!gtk_tree_model_iter_has_child (view->priv->model, iter))
+ return;
+
+ path = gtk_tree_model_get_path (view->priv->model, iter);
+
+ if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (view), path))
+ {
+ gtk_tree_model_get (view->priv->model,
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI,
+ &uri,
+ -1);
+
+ add_expand_state (view, uri);
+ g_free (uri);
+ }
+
+ if (gtk_tree_model_iter_children (view->priv->model, &child, iter))
+ {
+ do {
+ fill_expand_state (view, &child);
+ } while (gtk_tree_model_iter_next (view->priv->model, &child));
+ }
+
+ gtk_tree_path_free (path);
+}
+
+static void
+uninstall_restore_signals (GeditFileBrowserView * tree_view,
+ GtkTreeModel * model)
+{
+ g_signal_handlers_disconnect_by_func (model,
+ on_begin_refresh,
+ tree_view);
+
+ g_signal_handlers_disconnect_by_func (model,
+ on_end_refresh,
+ tree_view);
+
+ g_signal_handlers_disconnect_by_func (model,
+ on_unload,
+ tree_view);
+
+ g_signal_handlers_disconnect_by_func (model,
+ on_row_inserted,
+ tree_view);
+}
+
+static void
+install_restore_signals (GeditFileBrowserView * tree_view,
+ GtkTreeModel * model)
+{
+ g_signal_connect (model,
+ "begin-refresh",
+ G_CALLBACK (on_begin_refresh),
+ tree_view);
+
+ g_signal_connect (model,
+ "end-refresh",
+ G_CALLBACK (on_end_refresh),
+ tree_view);
+
+ g_signal_connect (model,
+ "unload",
+ G_CALLBACK (on_unload),
+ tree_view);
+
+ g_signal_connect_after (model,
+ "row-inserted",
+ G_CALLBACK (on_row_inserted),
+ tree_view);
+}
+
+static void
+set_restore_expand_state (GeditFileBrowserView * view,
+ gboolean state)
+{
+ if (state == view->priv->restore_expand_state)
+ return;
+
+ if (view->priv->expand_state)
+ {
+ g_hash_table_destroy (view->priv->expand_state);
+ view->priv->expand_state = NULL;
+ }
+
+ if (state)
+ {
+ view->priv->expand_state = g_hash_table_new_full (g_file_hash,
+ (GEqualFunc)g_file_equal,
+ g_object_unref,
+ NULL);
+
+ if (view->priv->model && GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ {
+ fill_expand_state (view, NULL);
+
+ install_restore_signals (view, view->priv->model);
+ }
+ }
+ else if (view->priv->model && GEDIT_IS_FILE_BROWSER_STORE (view->priv->model))
+ {
+ uninstall_restore_signals (view, view->priv->model);
+ }
+
+ view->priv->restore_expand_state = state;
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLICK_POLICY:
+ g_value_set_enum (value, obj->priv->click_policy);
+ break;
+ case PROP_RESTORE_EXPAND_STATE:
+ g_value_set_boolean (value, obj->priv->restore_expand_state);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserView *obj = GEDIT_FILE_BROWSER_VIEW (object);
+
+ switch (prop_id)
+ {
+ case PROP_CLICK_POLICY:
+ set_click_policy_property (obj, g_value_get_enum (value));
+ break;
+ case PROP_RESTORE_EXPAND_STATE:
+ set_restore_expand_state (obj, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_view_class_init (GeditFileBrowserViewClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = gedit_file_browser_view_finalize;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ /* Event handlers */
+ widget_class->motion_notify_event = motion_notify_event;
+ widget_class->enter_notify_event = enter_notify_event;
+ widget_class->leave_notify_event = leave_notify_event;
+ widget_class->button_press_event = button_press_event;
+ widget_class->button_release_event = button_release_event;
+ widget_class->drag_begin = drag_begin;
+ widget_class->key_press_event = key_press_event;
+
+ /* Tree view handlers */
+ tree_view_class->row_expanded = row_expanded;
+ tree_view_class->row_collapsed = row_collapsed;
+
+ /* Default handlers */
+ klass->directory_activated = directory_activated;
+
+ g_object_class_install_property (object_class, PROP_CLICK_POLICY,
+ g_param_spec_enum ("click-policy",
+ "Click Policy",
+ "The click policy",
+ GEDIT_TYPE_FILE_BROWSER_VIEW_CLICK_POLICY,
+ GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_RESTORE_EXPAND_STATE,
+ g_param_spec_boolean ("restore-expand-state",
+ "Restore Expand State",
+ "Restore expanded state of loaded directories",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ signals[ERROR] =
+ g_signal_new ("error",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserViewClass,
+ error), NULL, NULL,
+ gedit_file_browser_marshal_VOID__UINT_STRING,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+ signals[FILE_ACTIVATED] =
+ g_signal_new ("file-activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserViewClass,
+ file_activated), NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+ signals[DIRECTORY_ACTIVATED] =
+ g_signal_new ("directory-activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserViewClass,
+ directory_activated), NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+ signals[BOOKMARK_ACTIVATED] =
+ g_signal_new ("bookmark-activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserViewClass,
+ bookmark_activated), NULL, NULL,
+ g_cclosure_marshal_VOID__BOXED,
+ G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
+
+ g_type_class_add_private (object_class,
+ sizeof (GeditFileBrowserViewPrivate));
+}
+
+static void
+cell_data_cb (GtkTreeViewColumn * tree_column, GtkCellRenderer * cell,
+ GtkTreeModel * tree_model, GtkTreeIter * iter,
+ GeditFileBrowserView * obj)
+{
+ GtkTreePath *path;
+ PangoUnderline underline = PANGO_UNDERLINE_NONE;
+ gboolean editable = FALSE;
+
+ path = gtk_tree_model_get_path (tree_model, iter);
+
+ if (obj->priv->click_policy == GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE) {
+ if (obj->priv->hover_path != NULL &&
+ gtk_tree_path_compare (path, obj->priv->hover_path) == 0)
+ underline = PANGO_UNDERLINE_SINGLE;
+ }
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (tree_model))
+ {
+ if (obj->priv->editable != NULL &&
+ gtk_tree_row_reference_valid (obj->priv->editable))
+ {
+ GtkTreePath *edpath = gtk_tree_row_reference_get_path (obj->priv->editable);
+
+ editable = edpath && gtk_tree_path_compare (path, edpath) == 0;
+ }
+ }
+
+ gtk_tree_path_free (path);
+ g_object_set (cell, "editable", editable, "underline", underline, NULL);
+}
+
+static void
+gedit_file_browser_view_init (GeditFileBrowserView * obj)
+{
+ obj->priv = GEDIT_FILE_BROWSER_VIEW_GET_PRIVATE (obj);
+
+ obj->priv->column = gtk_tree_view_column_new ();
+
+ obj->priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_tree_view_column_pack_start (obj->priv->column,
+ obj->priv->pixbuf_renderer,
+ FALSE);
+ gtk_tree_view_column_add_attribute (obj->priv->column,
+ obj->priv->pixbuf_renderer,
+ "pixbuf",
+ GEDIT_FILE_BROWSER_STORE_COLUMN_ICON);
+
+ obj->priv->text_renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (obj->priv->column,
+ obj->priv->text_renderer, TRUE);
+ gtk_tree_view_column_add_attribute (obj->priv->column,
+ obj->priv->text_renderer,
+ "text",
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NAME);
+
+ g_signal_connect (obj->priv->text_renderer, "edited",
+ G_CALLBACK (on_cell_edited), obj);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (obj),
+ obj->priv->column);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (obj), FALSE);
+
+ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (obj),
+ GDK_BUTTON1_MASK,
+ drag_source_targets,
+ G_N_ELEMENTS (drag_source_targets),
+ GDK_ACTION_COPY);
+
+ obj->priv->busy_cursor = gdk_cursor_new (GDK_WATCH);
+}
+
+static gboolean
+bookmarks_separator_func (GtkTreeModel * model, GtkTreeIter * iter,
+ gpointer user_data)
+{
+ guint flags;
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS,
+ &flags, -1);
+
+ return (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_SEPARATOR);
+}
+
+/* Public */
+GtkWidget *
+gedit_file_browser_view_new (void)
+{
+ GeditFileBrowserView *obj =
+ GEDIT_FILE_BROWSER_VIEW (g_object_new
+ (GEDIT_TYPE_FILE_BROWSER_VIEW, NULL));
+
+ return GTK_WIDGET (obj);
+}
+
+void
+gedit_file_browser_view_set_model (GeditFileBrowserView * tree_view,
+ GtkTreeModel * model)
+{
+ GtkTreeSelection *selection;
+
+ if (tree_view->priv->model == model)
+ return;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
+
+ if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) {
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+ gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW
+ (tree_view),
+ bookmarks_separator_func,
+ NULL, NULL);
+ gtk_tree_view_column_set_cell_data_func (tree_view->priv->
+ column,
+ tree_view->priv->
+ text_renderer,
+ (GtkTreeCellDataFunc)
+ cell_data_cb,
+ tree_view, NULL);
+ } else {
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
+ gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW
+ (tree_view), NULL,
+ NULL, NULL);
+ gtk_tree_view_column_set_cell_data_func (tree_view->priv->
+ column,
+ tree_view->priv->
+ text_renderer,
+ (GtkTreeCellDataFunc)
+ cell_data_cb,
+ tree_view, NULL);
+
+ if (tree_view->priv->restore_expand_state)
+ install_restore_signals (tree_view, model);
+
+ }
+
+ if (tree_view->priv->hover_path != NULL) {
+ gtk_tree_path_free (tree_view->priv->hover_path);
+ tree_view->priv->hover_path = NULL;
+ }
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (tree_view->priv->model)) {
+ if (tree_view->priv->restore_expand_state)
+ uninstall_restore_signals (tree_view,
+ tree_view->priv->model);
+ }
+
+ tree_view->priv->model = model;
+ gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model);
+}
+
+void
+gedit_file_browser_view_start_rename (GeditFileBrowserView * tree_view,
+ GtkTreeIter * iter)
+{
+ guint flags;
+ GtkTreeRowReference *rowref;
+ GtkTreePath *path;
+
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view));
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_STORE
+ (tree_view->priv->model));
+ g_return_if_fail (iter != NULL);
+
+ gtk_tree_model_get (tree_view->priv->model, iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags)))
+ return;
+
+ path = gtk_tree_model_get_path (tree_view->priv->model, iter);
+ rowref = gtk_tree_row_reference_new (tree_view->priv->model, path);
+
+ /* Start editing */
+ gtk_widget_grab_focus (GTK_WIDGET (tree_view));
+
+ if (gtk_tree_path_up (path))
+ gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree_view),
+ path);
+
+ gtk_tree_path_free (path);
+ tree_view->priv->editable = rowref;
+
+ gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view),
+ gtk_tree_row_reference_get_path (tree_view->priv->editable),
+ tree_view->priv->column, TRUE);
+
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view),
+ gtk_tree_row_reference_get_path (tree_view->priv->editable),
+ tree_view->priv->column,
+ FALSE, 0.0, 0.0);
+}
+
+void
+gedit_file_browser_view_set_click_policy (GeditFileBrowserView *tree_view,
+ GeditFileBrowserViewClickPolicy policy)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view));
+
+ set_click_policy_property (tree_view, policy);
+
+ g_object_notify (G_OBJECT (tree_view), "click-policy");
+}
+
+void
+gedit_file_browser_view_set_restore_expand_state (GeditFileBrowserView * tree_view,
+ gboolean restore_expand_state)
+{
+ g_return_if_fail (GEDIT_IS_FILE_BROWSER_VIEW (tree_view));
+
+ set_restore_expand_state (tree_view, restore_expand_state);
+ g_object_notify (G_OBJECT (tree_view), "restore-expand-state");
+}
+
+/* Signal handlers */
+static void
+on_cell_edited (GtkCellRendererText * cell, gchar * path, gchar * new_text,
+ GeditFileBrowserView * tree_view)
+{
+ GtkTreePath * treepath;
+ GtkTreeIter iter;
+ gboolean ret;
+ GError * error = NULL;
+
+ gtk_tree_row_reference_free (tree_view->priv->editable);
+ tree_view->priv->editable = NULL;
+
+ if (new_text == NULL || *new_text == '\0')
+ return;
+
+ treepath = gtk_tree_path_new_from_string (path);
+ ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath);
+ gtk_tree_path_free (treepath);
+
+ if (ret) {
+ if (gedit_file_browser_store_rename (GEDIT_FILE_BROWSER_STORE (tree_view->priv->model),
+ &iter, new_text, &error)) {
+ treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (tree_view->priv->model), &iter);
+ gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view),
+ treepath, NULL,
+ FALSE, 0.0, 0.0);
+ gtk_tree_path_free (treepath);
+ }
+ else {
+ if (error) {
+ g_signal_emit (tree_view, signals[ERROR], 0,
+ error->code, error->message);
+ g_error_free (error);
+ }
+ }
+ }
+}
+
+static void
+on_begin_refresh (GeditFileBrowserStore * model,
+ GeditFileBrowserView * view)
+{
+ /* Store the refresh state, so we can handle unloading of nodes while
+ refreshing properly */
+ view->priv->is_refresh = TRUE;
+}
+
+static void
+on_end_refresh (GeditFileBrowserStore * model,
+ GeditFileBrowserView * view)
+{
+ /* Store the refresh state, so we can handle unloading of nodes while
+ refreshing properly */
+ view->priv->is_refresh = FALSE;
+}
+
+static void
+on_unload (GeditFileBrowserStore * model,
+ gchar const * uri,
+ GeditFileBrowserView * view)
+{
+ /* Don't remove the expand state if we are refreshing */
+ if (!view->priv->restore_expand_state || view->priv->is_refresh)
+ return;
+
+ remove_expand_state (view, uri);
+}
+
+static void
+restore_expand_state (GeditFileBrowserView * view,
+ GeditFileBrowserStore * model,
+ GtkTreeIter * iter)
+{
+ gchar * uri;
+ GFile * file;
+ GtkTreePath * path;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI,
+ &uri,
+ -1);
+
+ if (!uri)
+ return;
+
+ file = g_file_new_for_uri (uri);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
+
+ if (g_hash_table_lookup (view->priv->expand_state, file))
+ {
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (view),
+ path,
+ FALSE);
+ }
+
+ gtk_tree_path_free (path);
+
+ g_object_unref (file);
+ g_free (uri);
+}
+
+static void
+on_row_inserted (GeditFileBrowserStore * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter,
+ GeditFileBrowserView * view)
+{
+ GtkTreeIter parent;
+ GtkTreePath * copy;
+
+ if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (model), iter))
+ restore_expand_state (view, model, iter);
+
+ copy = gtk_tree_path_copy (path);
+
+ if (gtk_tree_path_up (copy) &&
+ (gtk_tree_path_get_depth (copy) != 0) &&
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &parent, copy))
+ {
+ restore_expand_state (view, model, &parent);
+ }
+
+ gtk_tree_path_free (copy);
+}
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-view.h b/plugins/filebrowser/gedit-file-browser-view.h
new file mode 100755
index 00000000..a5ada255
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-view.h
@@ -0,0 +1,84 @@
+/*
+ * gedit-file-browser-view.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_FILE_BROWSER_VIEW_H__
+#define __GEDIT_FILE_BROWSER_VIEW_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+#define GEDIT_TYPE_FILE_BROWSER_VIEW (gedit_file_browser_view_get_type ())
+#define GEDIT_FILE_BROWSER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserView))
+#define GEDIT_FILE_BROWSER_VIEW_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserView const))
+#define GEDIT_FILE_BROWSER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewClass))
+#define GEDIT_IS_FILE_BROWSER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW))
+#define GEDIT_IS_FILE_BROWSER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_VIEW))
+#define GEDIT_FILE_BROWSER_VIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_VIEW, GeditFileBrowserViewClass))
+
+typedef struct _GeditFileBrowserView GeditFileBrowserView;
+typedef struct _GeditFileBrowserViewClass GeditFileBrowserViewClass;
+typedef struct _GeditFileBrowserViewPrivate GeditFileBrowserViewPrivate;
+
+typedef enum {
+ GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_DOUBLE,
+ GEDIT_FILE_BROWSER_VIEW_CLICK_POLICY_SINGLE
+} GeditFileBrowserViewClickPolicy;
+
+struct _GeditFileBrowserView
+{
+ GtkTreeView parent;
+
+ GeditFileBrowserViewPrivate *priv;
+};
+
+struct _GeditFileBrowserViewClass
+{
+ GtkTreeViewClass parent_class;
+
+ /* Signals */
+ void (*error) (GeditFileBrowserView * filetree,
+ guint code,
+ gchar const *message);
+ void (*file_activated) (GeditFileBrowserView * filetree,
+ GtkTreeIter *iter);
+ void (*directory_activated) (GeditFileBrowserView * filetree,
+ GtkTreeIter *iter);
+ void (*bookmark_activated) (GeditFileBrowserView * filetree,
+ GtkTreeIter *iter);
+};
+
+GType gedit_file_browser_view_get_type (void) G_GNUC_CONST;
+GType gedit_file_browser_view_register_type (GTypeModule * module);
+
+GtkWidget *gedit_file_browser_view_new (void);
+void gedit_file_browser_view_set_model (GeditFileBrowserView * tree_view,
+ GtkTreeModel * model);
+void gedit_file_browser_view_start_rename (GeditFileBrowserView * tree_view,
+ GtkTreeIter * iter);
+void gedit_file_browser_view_set_click_policy (GeditFileBrowserView * tree_view,
+ GeditFileBrowserViewClickPolicy policy);
+void gedit_file_browser_view_set_restore_expand_state (GeditFileBrowserView * tree_view,
+ gboolean restore_expand_state);
+
+G_END_DECLS
+#endif /* __GEDIT_FILE_BROWSER_VIEW_H__ */
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-widget-ui.xml b/plugins/filebrowser/gedit-file-browser-widget-ui.xml
new file mode 100755
index 00000000..472fd185
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-widget-ui.xml
@@ -0,0 +1,54 @@
+<ui>
+ <toolbar name="ToolBar">
+ <placeholder name="Tool_Opt1"/>
+ <toolitem action="DirectoryUp"/>
+ <separator/>
+ <toolitem action="DirectoryRefresh"/>
+ <separator/>
+ <placeholder name="Tool_Opt2"/>
+ <separator/>
+ <toolitem action="FilterHidden"/>
+ <separator/>
+ <placeholder name="Tool_Opt3"/>
+ </toolbar>
+
+ <popup name="FilePopup" action="FilePopupAction">
+ <menuitem action="FileOpen" />
+ <placeholder name="FilePopup_Opt1" />
+ <separator/>
+ <menuitem action="DirectoryNew" />
+ <menuitem action="FileNew" />
+ <separator/>
+ <placeholder name="FilePopup_Opt2" />
+ <separator/>
+ <menuitem action="FileRename"/>
+ <menuitem action="FileMoveToTrash"/>
+ <menuitem action="FileDelete"/>
+ <separator/>
+ <placeholder name="FilePopup_Opt3" />
+ <separator/>
+ <menuitem action="DirectoryRefresh"/>
+ <menuitem action="DirectoryOpen"/>
+ <placeholder name="FilePopup_Opt4" />
+ <separator/>
+ <placeholder name="FilePopup_Opt5" />
+ <separator/>
+ <menu name="FilterMenu" action="FilterMenuAction">
+ <menuitem action="FilterHidden"/>
+ <menuitem action="FilterBinary"/>
+ </menu>
+ <placeholder name="FilePopup_Opt6" />
+ <separator/>
+ </popup>
+
+ <popup name="BookmarkPopup" action="BookmarkPopupAction">
+ <placeholder name="BookmarkPopup_Opt1" />
+ <separator/>
+ <placeholder name="BookmarkPopup_Opt2" />
+ <separator/>
+ <menuitem action="BookmarkOpen"/>
+ <placeholder name="BookmarkPopup_Opt3" />
+ <separator/>
+ <placeholder name="BookmarkPopup_Opt4" />
+ </popup>
+</ui>
diff --git a/plugins/filebrowser/gedit-file-browser-widget.c b/plugins/filebrowser/gedit-file-browser-widget.c
new file mode 100755
index 00000000..e8a73cce
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-widget.c
@@ -0,0 +1,3143 @@
+/*
+ * gedit-file-browser-widget.c - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-plugin.h>
+
+#include "gedit-file-browser-utils.h"
+#include "gedit-file-browser-error.h"
+#include "gedit-file-browser-widget.h"
+#include "gedit-file-browser-view.h"
+#include "gedit-file-browser-store.h"
+#include "gedit-file-bookmarks-store.h"
+#include "gedit-file-browser-marshal.h"
+#include "gedit-file-browser-enum-types.h"
+
+#define GEDIT_FILE_BROWSER_WIDGET_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GEDIT_TYPE_FILE_BROWSER_WIDGET, \
+ GeditFileBrowserWidgetPrivate))
+
+#define XML_UI_FILE "gedit-file-browser-widget-ui.xml"
+#define LOCATION_DATA_KEY "gedit-file-browser-widget-location"
+
+enum
+{
+ BOOKMARKS_ID,
+ SEPARATOR_CUSTOM_ID,
+ SEPARATOR_ID,
+ PATH_ID,
+ NUM_DEFAULT_IDS
+};
+
+enum
+{
+ COLUMN_INDENT,
+ COLUMN_ICON,
+ COLUMN_NAME,
+ COLUMN_FILE,
+ COLUMN_ID,
+ N_COLUMNS
+};
+
+/* Properties */
+enum
+{
+ PROP_0,
+
+ PROP_FILTER_PATTERN,
+ PROP_ENABLE_DELETE
+};
+
+/* Signals */
+enum
+{
+ URI_ACTIVATED,
+ ERROR,
+ CONFIRM_DELETE,
+ CONFIRM_NO_TRASH,
+ NUM_SIGNALS
+};
+
+static guint signals[NUM_SIGNALS] = { 0 };
+
+typedef struct _SignalNode
+{
+ GObject *object;
+ gulong id;
+} SignalNode;
+
+typedef struct
+{
+ gulong id;
+ GeditFileBrowserWidgetFilterFunc func;
+ gpointer user_data;
+ GDestroyNotify destroy_notify;
+} FilterFunc;
+
+typedef struct
+{
+ GFile *root;
+ GFile *virtual_root;
+} Location;
+
+typedef struct
+{
+ gchar *name;
+ GdkPixbuf *icon;
+} NameIcon;
+
+struct _GeditFileBrowserWidgetPrivate
+{
+ GeditFileBrowserView *treeview;
+ GeditFileBrowserStore *file_store;
+ GeditFileBookmarksStore *bookmarks_store;
+
+ GHashTable *bookmarks_hash;
+
+ GtkWidget *combo;
+ GtkTreeStore *combo_model;
+
+ GtkWidget *filter_expander;
+ GtkWidget *filter_entry;
+
+ GtkUIManager *manager;
+ GtkActionGroup *action_group;
+ GtkActionGroup *action_group_selection;
+ GtkActionGroup *action_group_file_selection;
+ GtkActionGroup *action_group_single_selection;
+ GtkActionGroup *action_group_single_most_selection;
+ GtkActionGroup *action_group_sensitive;
+ GtkActionGroup *bookmark_action_group;
+
+ GSList *signal_pool;
+
+ GSList *filter_funcs;
+ gulong filter_id;
+ gulong glob_filter_id;
+ GPatternSpec *filter_pattern;
+ gchar *filter_pattern_str;
+
+ GList *locations;
+ GList *current_location;
+ gboolean changing_location;
+ GtkWidget *location_previous_menu;
+ GtkWidget *location_next_menu;
+ GtkWidget *current_location_menu_item;
+
+ gboolean enable_delete;
+
+ GCancellable *cancellable;
+
+ GdkCursor *busy_cursor;
+};
+
+static void set_enable_delete (GeditFileBrowserWidget *obj,
+ gboolean enable);
+static void on_model_set (GObject * gobject,
+ GParamSpec * arg1,
+ GeditFileBrowserWidget * obj);
+static void on_treeview_error (GeditFileBrowserView * tree_view,
+ guint code,
+ gchar * message,
+ GeditFileBrowserWidget * obj);
+static void on_file_store_error (GeditFileBrowserStore * store,
+ guint code,
+ gchar * message,
+ GeditFileBrowserWidget * obj);
+static gboolean on_file_store_no_trash (GeditFileBrowserStore * store,
+ GList * files,
+ GeditFileBrowserWidget * obj);
+static void on_combo_changed (GtkComboBox * combo,
+ GeditFileBrowserWidget * obj);
+static gboolean on_treeview_popup_menu (GeditFileBrowserView * treeview,
+ GeditFileBrowserWidget * obj);
+static gboolean on_treeview_button_press_event (GeditFileBrowserView * treeview,
+ GdkEventButton * event,
+ GeditFileBrowserWidget * obj);
+static gboolean on_treeview_key_press_event (GeditFileBrowserView * treeview,
+ GdkEventKey * event,
+ GeditFileBrowserWidget * obj);
+static void on_selection_changed (GtkTreeSelection * selection,
+ GeditFileBrowserWidget * obj);
+
+static void on_virtual_root_changed (GeditFileBrowserStore * model,
+ GParamSpec *param,
+ GeditFileBrowserWidget * obj);
+
+static gboolean on_entry_filter_activate (GeditFileBrowserWidget * obj);
+static void on_location_jump_activate (GtkMenuItem * item,
+ GeditFileBrowserWidget * obj);
+static void on_bookmarks_row_changed (GtkTreeModel * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter,
+ GeditFileBrowserWidget * obj);
+static void on_bookmarks_row_deleted (GtkTreeModel * model,
+ GtkTreePath * path,
+ GeditFileBrowserWidget * obj);
+static void on_filter_mode_changed (GeditFileBrowserStore * model,
+ GParamSpec * param,
+ GeditFileBrowserWidget * obj);
+static void on_action_directory_previous (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_directory_next (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_directory_up (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_directory_new (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_file_open (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_file_new (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_file_rename (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_file_delete (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_file_move_to_trash (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_directory_refresh (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_directory_open (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_filter_hidden (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_filter_binary (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+static void on_action_bookmark_open (GtkAction * action,
+ GeditFileBrowserWidget * obj);
+
+GEDIT_PLUGIN_DEFINE_TYPE (GeditFileBrowserWidget, gedit_file_browser_widget,
+ GTK_TYPE_VBOX)
+
+static void
+free_name_icon (gpointer data)
+{
+ NameIcon * item;
+
+ if (data == NULL)
+ return;
+
+ item = (NameIcon *)(data);
+
+ g_free (item->name);
+
+ if (item->icon)
+ g_object_unref (item->icon);
+
+ g_free (item);
+}
+
+static FilterFunc *
+filter_func_new (GeditFileBrowserWidget * obj,
+ GeditFileBrowserWidgetFilterFunc func,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ FilterFunc *result;
+
+ result = g_new (FilterFunc, 1);
+
+ result->id = ++obj->priv->filter_id;
+ result->func = func;
+ result->user_data = user_data;
+ result->destroy_notify = notify;
+ return result;
+}
+
+static void
+location_free (Location * loc)
+{
+ if (loc->root)
+ g_object_unref (loc->root);
+
+ if (loc->virtual_root)
+ g_object_unref (loc->virtual_root);
+
+ g_free (loc);
+}
+
+static gboolean
+combo_find_by_id (GeditFileBrowserWidget * obj, guint id,
+ GtkTreeIter * iter)
+{
+ guint checkid;
+ GtkTreeModel *model = GTK_TREE_MODEL (obj->priv->combo_model);
+
+ if (iter == NULL)
+ return FALSE;
+
+ if (gtk_tree_model_get_iter_first (model, iter)) {
+ do {
+ gtk_tree_model_get (model, iter, COLUMN_ID,
+ &checkid, -1);
+
+ if (checkid == id)
+ return TRUE;
+ } while (gtk_tree_model_iter_next (model, iter));
+ }
+
+ return FALSE;
+}
+
+static void
+remove_path_items (GeditFileBrowserWidget * obj)
+{
+ GtkTreeIter iter;
+
+ while (combo_find_by_id (obj, PATH_ID, &iter))
+ gtk_tree_store_remove (obj->priv->combo_model, &iter);
+}
+
+static void
+cancel_async_operation (GeditFileBrowserWidget *widget)
+{
+ if (!widget->priv->cancellable)
+ return;
+
+ g_cancellable_cancel (widget->priv->cancellable);
+ g_object_unref (widget->priv->cancellable);
+
+ widget->priv->cancellable = NULL;
+}
+
+static void
+gedit_file_browser_widget_finalize (GObject * object)
+{
+ GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object);
+ GList *loc;
+
+ remove_path_items (obj);
+ gedit_file_browser_store_set_filter_func (obj->priv->file_store,
+ NULL, NULL);
+
+ g_object_unref (obj->priv->manager);
+ g_object_unref (obj->priv->file_store);
+ g_object_unref (obj->priv->bookmarks_store);
+ g_object_unref (obj->priv->combo_model);
+
+ g_slist_foreach (obj->priv->filter_funcs, (GFunc) g_free, NULL);
+ g_slist_free (obj->priv->filter_funcs);
+
+ for (loc = obj->priv->locations; loc; loc = loc->next)
+ location_free ((Location *) (loc->data));
+
+ if (obj->priv->current_location_menu_item)
+ g_object_unref (obj->priv->current_location_menu_item);
+
+ g_list_free (obj->priv->locations);
+
+ g_hash_table_destroy (obj->priv->bookmarks_hash);
+
+ cancel_async_operation (obj);
+
+ gdk_cursor_unref (obj->priv->busy_cursor);
+
+ G_OBJECT_CLASS (gedit_file_browser_widget_parent_class)->finalize (object);
+}
+
+static void
+gedit_file_browser_widget_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILTER_PATTERN:
+ g_value_set_string (value, obj->priv->filter_pattern_str);
+ break;
+ case PROP_ENABLE_DELETE:
+ g_value_set_boolean (value, obj->priv->enable_delete);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_widget_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditFileBrowserWidget *obj = GEDIT_FILE_BROWSER_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_FILTER_PATTERN:
+ gedit_file_browser_widget_set_filter_pattern (obj,
+ g_value_get_string (value));
+ break;
+ case PROP_ENABLE_DELETE:
+ set_enable_delete (obj, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_file_browser_widget_class_init (GeditFileBrowserWidgetClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gedit_file_browser_widget_finalize;
+
+ object_class->get_property = gedit_file_browser_widget_get_property;
+ object_class->set_property = gedit_file_browser_widget_set_property;
+
+ g_object_class_install_property (object_class, PROP_FILTER_PATTERN,
+ g_param_spec_string ("filter-pattern",
+ "Filter Pattern",
+ "The filter pattern",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class, PROP_ENABLE_DELETE,
+ g_param_spec_boolean ("enable-delete",
+ "Enable delete",
+ "Enable permanently deleting items",
+ TRUE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ signals[URI_ACTIVATED] =
+ g_signal_new ("uri-activated",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass,
+ uri_activated), NULL, NULL,
+ g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+ signals[ERROR] =
+ g_signal_new ("error", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass,
+ error), NULL, NULL,
+ gedit_file_browser_marshal_VOID__UINT_STRING,
+ G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
+
+ signals[CONFIRM_DELETE] =
+ g_signal_new ("confirm-delete", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass,
+ confirm_delete),
+ g_signal_accumulator_true_handled,
+ NULL,
+ gedit_file_browser_marshal_BOOL__OBJECT_POINTER,
+ G_TYPE_BOOLEAN,
+ 2,
+ G_TYPE_OBJECT,
+ G_TYPE_POINTER);
+
+ signals[CONFIRM_NO_TRASH] =
+ g_signal_new ("confirm-no-trash", G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditFileBrowserWidgetClass,
+ confirm_no_trash),
+ g_signal_accumulator_true_handled,
+ NULL,
+ gedit_file_browser_marshal_BOOL__POINTER,
+ G_TYPE_BOOLEAN,
+ 1,
+ G_TYPE_POINTER);
+
+ g_type_class_add_private (object_class,
+ sizeof (GeditFileBrowserWidgetPrivate));
+}
+
+static void
+add_signal (GeditFileBrowserWidget * obj, gpointer object, gulong id)
+{
+ SignalNode *node = g_new (SignalNode, 1);
+
+ node->object = G_OBJECT (object);
+ node->id = id;
+
+ obj->priv->signal_pool =
+ g_slist_prepend (obj->priv->signal_pool, node);
+}
+
+static void
+clear_signals (GeditFileBrowserWidget * obj)
+{
+ GSList *item;
+ SignalNode *node;
+
+ for (item = obj->priv->signal_pool; item; item = item->next) {
+ node = (SignalNode *) (item->data);
+
+ g_signal_handler_disconnect (node->object, node->id);
+ g_free (item->data);
+ }
+
+ g_slist_free (obj->priv->signal_pool);
+ obj->priv->signal_pool = NULL;
+}
+
+static gboolean
+separator_func (GtkTreeModel * model, GtkTreeIter * iter, gpointer data)
+{
+ guint id;
+
+ gtk_tree_model_get (model, iter, COLUMN_ID, &id, -1);
+
+ return (id == SEPARATOR_ID);
+}
+
+static gboolean
+get_from_bookmark_file (GeditFileBrowserWidget * obj, GFile * file,
+ gchar ** name, GdkPixbuf ** icon)
+{
+ gpointer data;
+ NameIcon * item;
+
+ data = g_hash_table_lookup (obj->priv->bookmarks_hash, file);
+
+ if (data == NULL)
+ return FALSE;
+
+ item = (NameIcon *)data;
+
+ *name = g_strdup (item->name);
+ *icon = item->icon;
+
+ if (item->icon != NULL)
+ {
+ g_object_ref (item->icon);
+ }
+
+ return TRUE;
+}
+
+static void
+insert_path_item (GeditFileBrowserWidget * obj,
+ GFile * file,
+ GtkTreeIter * after,
+ GtkTreeIter * iter,
+ guint indent)
+{
+ gchar * unescape;
+ GdkPixbuf * icon = NULL;
+
+ /* Try to get the icon and name from the bookmarks hash */
+ if (!get_from_bookmark_file (obj, file, &unescape, &icon)) {
+ /* It's not a bookmark, fetch the name and the icon ourselves */
+ unescape = gedit_file_browser_utils_file_basename (file);
+
+ /* Get the icon */
+ icon = gedit_file_browser_utils_pixbuf_from_file (file, GTK_ICON_SIZE_MENU);
+ }
+
+ gtk_tree_store_insert_after (obj->priv->combo_model, iter, NULL,
+ after);
+
+ gtk_tree_store_set (obj->priv->combo_model,
+ iter,
+ COLUMN_INDENT, indent,
+ COLUMN_ICON, icon,
+ COLUMN_NAME, unescape,
+ COLUMN_FILE, file,
+ COLUMN_ID, PATH_ID,
+ -1);
+
+ if (icon)
+ g_object_unref (icon);
+
+ g_free (unescape);
+}
+
+static void
+insert_separator_item (GeditFileBrowserWidget * obj)
+{
+ GtkTreeIter iter;
+
+ gtk_tree_store_insert (obj->priv->combo_model, &iter, NULL, 1);
+ gtk_tree_store_set (obj->priv->combo_model, &iter,
+ COLUMN_ICON, NULL,
+ COLUMN_NAME, NULL,
+ COLUMN_ID, SEPARATOR_ID, -1);
+}
+
+static void
+combo_set_active_by_id (GeditFileBrowserWidget * obj, guint id)
+{
+ GtkTreeIter iter;
+
+ if (combo_find_by_id (obj, id, &iter))
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX
+ (obj->priv->combo), &iter);
+}
+
+static guint
+uri_num_parents (GFile * from, GFile * to)
+{
+ /* Determine the number of 'levels' to get from #from to #to. */
+ guint parents = 0;
+ GFile * parent;
+
+ if (from == NULL)
+ return 0;
+
+ g_object_ref (from);
+
+ while ((parent = g_file_get_parent (from)) && !(to && g_file_equal (from, to))) {
+ g_object_unref (from);
+ from = parent;
+
+ ++parents;
+ }
+
+ g_object_unref (from);
+ return parents;
+}
+
+static void
+insert_location_path (GeditFileBrowserWidget * obj)
+{
+ Location *loc;
+ GFile *current = NULL;
+ GFile * tmp;
+ GtkTreeIter separator;
+ GtkTreeIter iter;
+ guint indent;
+
+ if (!obj->priv->current_location) {
+ g_message ("insert_location_path: no current location");
+ return;
+ }
+
+ loc = (Location *) (obj->priv->current_location->data);
+
+ current = loc->virtual_root;
+ combo_find_by_id (obj, SEPARATOR_ID, &separator);
+
+ indent = uri_num_parents (loc->virtual_root, loc->root);
+
+ while (current != NULL) {
+ insert_path_item (obj, current, &separator, &iter, indent--);
+
+ if (current == loc->virtual_root) {
+ g_signal_handlers_block_by_func (obj->priv->combo,
+ on_combo_changed,
+ obj);
+ gtk_combo_box_set_active_iter (GTK_COMBO_BOX
+ (obj->priv->combo),
+ &iter);
+ g_signal_handlers_unblock_by_func (obj->priv->
+ combo,
+ on_combo_changed,
+ obj);
+ }
+
+ if (g_file_equal (current, loc->root) || !gedit_utils_file_has_parent (current)) {
+ if (current != loc->virtual_root)
+ g_object_unref (current);
+ break;
+ }
+
+ tmp = g_file_get_parent (current);
+
+ if (current != loc->virtual_root)
+ g_object_unref (current);
+
+ current = tmp;
+ }
+}
+
+static void
+check_current_item (GeditFileBrowserWidget * obj, gboolean show_path)
+{
+ GtkTreeIter separator;
+ gboolean has_sep;
+
+ remove_path_items (obj);
+ has_sep = combo_find_by_id (obj, SEPARATOR_ID, &separator);
+
+ if (show_path) {
+ if (!has_sep)
+ insert_separator_item (obj);
+
+ insert_location_path (obj);
+ } else if (has_sep)
+ gtk_tree_store_remove (obj->priv->combo_model, &separator);
+}
+
+static void
+fill_combo_model (GeditFileBrowserWidget * obj)
+{
+ GtkTreeStore *store = obj->priv->combo_model;
+ GtkTreeIter iter;
+ GdkPixbuf *icon;
+
+ icon = gedit_file_browser_utils_pixbuf_from_theme (GTK_STOCK_HOME, GTK_ICON_SIZE_MENU);
+
+ gtk_tree_store_append (store, &iter, NULL);
+ gtk_tree_store_set (store, &iter,
+ COLUMN_ICON, icon,
+ COLUMN_NAME, _("Bookmarks"),
+ COLUMN_ID, BOOKMARKS_ID, -1);
+ g_object_unref (icon);
+
+ gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (obj->priv->combo),
+ separator_func, obj, NULL);
+ gtk_combo_box_set_active (GTK_COMBO_BOX (obj->priv->combo), 0);
+}
+
+static void
+indent_cell_data_func (GtkCellLayout * cell_layout,
+ GtkCellRenderer * cell,
+ GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gpointer data)
+{
+ gchar * indent;
+ guint num;
+
+ gtk_tree_model_get (model, iter, COLUMN_INDENT, &num, -1);
+
+ if (num == 0)
+ g_object_set (cell, "text", "", NULL);
+ else {
+ indent = g_strnfill (num * 2, ' ');
+
+ g_object_set (cell, "text", indent, NULL);
+ g_free (indent);
+ }
+}
+
+static void
+create_combo (GeditFileBrowserWidget * obj)
+{
+ GtkCellRenderer *renderer;
+
+ obj->priv->combo_model = gtk_tree_store_new (N_COLUMNS,
+ G_TYPE_UINT,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_FILE,
+ G_TYPE_UINT);
+ obj->priv->combo =
+ gtk_combo_box_new_with_model (GTK_TREE_MODEL
+ (obj->priv->combo_model));
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo),
+ renderer, FALSE);
+ gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT
+ (obj->priv->combo), renderer,
+ indent_cell_data_func, obj, NULL);
+
+
+ renderer = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo),
+ renderer, FALSE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (obj->priv->combo),
+ renderer, "pixbuf", COLUMN_ICON);
+
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (obj->priv->combo),
+ renderer, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (obj->priv->combo),
+ renderer, "text", COLUMN_NAME);
+
+ g_object_set (renderer, "ellipsize-set", TRUE,
+ "ellipsize", PANGO_ELLIPSIZE_END, NULL);
+
+ gtk_box_pack_start (GTK_BOX (obj), GTK_WIDGET (obj->priv->combo),
+ FALSE, FALSE, 0);
+
+ fill_combo_model (obj);
+ g_signal_connect (obj->priv->combo, "changed",
+ G_CALLBACK (on_combo_changed), obj);
+
+ gtk_widget_show (obj->priv->combo);
+}
+
+static GtkActionEntry toplevel_actions[] =
+{
+ {"FilterMenuAction", NULL, N_("_Filter")}
+};
+
+static const GtkActionEntry tree_actions_selection[] =
+{
+ {"FileMoveToTrash", "mate-stock-trash", N_("_Move to Trash"), NULL,
+ N_("Move selected file or folder to trash"),
+ G_CALLBACK (on_action_file_move_to_trash)},
+ {"FileDelete", GTK_STOCK_DELETE, N_("_Delete"), NULL,
+ N_("Delete selected file or folder"),
+ G_CALLBACK (on_action_file_delete)}
+};
+
+static const GtkActionEntry tree_actions_file_selection[] =
+{
+ {"FileOpen", GTK_STOCK_OPEN, NULL, NULL,
+ N_("Open selected file"),
+ G_CALLBACK (on_action_file_open)}
+};
+
+static const GtkActionEntry tree_actions[] =
+{
+ {"DirectoryUp", GTK_STOCK_GO_UP, N_("Up"), NULL,
+ N_("Open the parent folder"), G_CALLBACK (on_action_directory_up)}
+};
+
+static const GtkActionEntry tree_actions_single_most_selection[] =
+{
+ {"DirectoryNew", GTK_STOCK_ADD, N_("_New Folder"), NULL,
+ N_("Add new empty folder"),
+ G_CALLBACK (on_action_directory_new)},
+ {"FileNew", GTK_STOCK_NEW, N_("New F_ile"), NULL,
+ N_("Add new empty file"), G_CALLBACK (on_action_file_new)}
+};
+
+static const GtkActionEntry tree_actions_single_selection[] =
+{
+ {"FileRename", NULL, N_("_Rename"), NULL,
+ N_("Rename selected file or folder"),
+ G_CALLBACK (on_action_file_rename)}
+};
+
+static const GtkActionEntry tree_actions_sensitive[] =
+{
+ {"DirectoryPrevious", GTK_STOCK_GO_BACK, N_("_Previous Location"),
+ NULL,
+ N_("Go to the previous visited location"),
+ G_CALLBACK (on_action_directory_previous)},
+ {"DirectoryNext", GTK_STOCK_GO_FORWARD, N_("_Next Location"), NULL,
+ N_("Go to the next visited location"), G_CALLBACK (on_action_directory_next)},
+ {"DirectoryRefresh", GTK_STOCK_REFRESH, N_("Re_fresh View"), NULL,
+ N_("Refresh the view"), G_CALLBACK (on_action_directory_refresh)},
+ {"DirectoryOpen", GTK_STOCK_OPEN, N_("_View Folder"), NULL,
+ N_("View folder in file manager"),
+ G_CALLBACK (on_action_directory_open)}
+};
+
+static const GtkToggleActionEntry tree_actions_toggle[] =
+{
+ {"FilterHidden", GTK_STOCK_DIALOG_AUTHENTICATION,
+ N_("Show _Hidden"), NULL,
+ N_("Show hidden files and folders"),
+ G_CALLBACK (on_action_filter_hidden), FALSE},
+ {"FilterBinary", NULL, N_("Show _Binary"), NULL,
+ N_("Show binary files"), G_CALLBACK (on_action_filter_binary),
+ FALSE}
+};
+
+static const GtkActionEntry bookmark_actions[] =
+{
+ {"BookmarkOpen", GTK_STOCK_OPEN, N_("_View Folder"), NULL,
+ N_("View folder in file manager"), G_CALLBACK (on_action_bookmark_open)}
+};
+
+static void
+create_toolbar (GeditFileBrowserWidget * obj,
+ const gchar *data_dir)
+{
+ GtkUIManager *manager;
+ GError *error = NULL;
+ GtkActionGroup *action_group;
+ GtkWidget *toolbar;
+ GtkWidget *widget;
+ GtkAction *action;
+ gchar *ui_file;
+
+ manager = gtk_ui_manager_new ();
+ obj->priv->manager = manager;
+
+ ui_file = g_build_filename (data_dir, XML_UI_FILE, NULL);
+ gtk_ui_manager_add_ui_from_file (manager, ui_file, &error);
+
+ g_free (ui_file);
+
+ if (error != NULL) {
+ g_warning ("Error in adding ui from file %s: %s",
+ XML_UI_FILE, error->message);
+ g_error_free (error);
+ return;
+ }
+
+ action_group = gtk_action_group_new ("FileBrowserWidgetActionGroupToplevel");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ toplevel_actions,
+ G_N_ELEMENTS (toplevel_actions),
+ obj);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+
+ action_group = gtk_action_group_new ("FileBrowserWidgetActionGroup");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ tree_actions,
+ G_N_ELEMENTS (tree_actions),
+ obj);
+ gtk_action_group_add_toggle_actions (action_group,
+ tree_actions_toggle,
+ G_N_ELEMENTS (tree_actions_toggle),
+ obj);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ obj->priv->action_group = action_group;
+
+ action_group = gtk_action_group_new ("FileBrowserWidgetSelectionActionGroup");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ tree_actions_selection,
+ G_N_ELEMENTS (tree_actions_selection),
+ obj);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ obj->priv->action_group_selection = action_group;
+
+ action_group = gtk_action_group_new ("FileBrowserWidgetFileSelectionActionGroup");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ tree_actions_file_selection,
+ G_N_ELEMENTS (tree_actions_file_selection),
+ obj);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ obj->priv->action_group_file_selection = action_group;
+
+ action_group = gtk_action_group_new ("FileBrowserWidgetSingleSelectionActionGroup");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ tree_actions_single_selection,
+ G_N_ELEMENTS (tree_actions_single_selection),
+ obj);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ obj->priv->action_group_single_selection = action_group;
+
+ action_group = gtk_action_group_new ("FileBrowserWidgetSingleMostSelectionActionGroup");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ tree_actions_single_most_selection,
+ G_N_ELEMENTS (tree_actions_single_most_selection),
+ obj);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ obj->priv->action_group_single_most_selection = action_group;
+
+ action_group = gtk_action_group_new ("FileBrowserWidgetSensitiveActionGroup");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ tree_actions_sensitive,
+ G_N_ELEMENTS (tree_actions_sensitive),
+ obj);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ obj->priv->action_group_sensitive = action_group;
+
+ action_group = gtk_action_group_new ("FileBrowserWidgetBookmarkActionGroup");
+ gtk_action_group_set_translation_domain (action_group, NULL);
+ gtk_action_group_add_actions (action_group,
+ bookmark_actions,
+ G_N_ELEMENTS (bookmark_actions),
+ obj);
+ gtk_ui_manager_insert_action_group (manager, action_group, 0);
+ obj->priv->bookmark_action_group = action_group;
+
+ action = gtk_action_group_get_action (obj->priv->action_group_sensitive,
+ "DirectoryPrevious");
+ gtk_action_set_sensitive (action, FALSE);
+
+ action = gtk_action_group_get_action (obj->priv->action_group_sensitive,
+ "DirectoryNext");
+ gtk_action_set_sensitive (action, FALSE);
+
+ toolbar = gtk_ui_manager_get_widget (manager, "/ToolBar");
+ gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_ICONS);
+ gtk_toolbar_set_icon_size (GTK_TOOLBAR (toolbar), GTK_ICON_SIZE_MENU);
+
+ /* Previous directory menu tool item */
+ obj->priv->location_previous_menu = gtk_menu_new ();
+ gtk_widget_show (obj->priv->location_previous_menu);
+
+ widget = GTK_WIDGET (gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_BACK));
+ gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (widget),
+ obj->priv->location_previous_menu);
+
+ g_object_set (widget, "label", _("Previous location"), NULL);
+ gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (widget),
+ _("Go to previous location"));
+ gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (widget),
+ _("Go to a previously opened location"));
+
+ action = gtk_action_group_get_action (obj->priv->action_group_sensitive,
+ "DirectoryPrevious");
+ g_object_set (action, "is_important", TRUE, "short_label",
+ _("Previous location"), NULL);
+ gtk_action_connect_proxy (action, widget);
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (widget), 0);
+
+ /* Next directory menu tool item */
+ obj->priv->location_next_menu = gtk_menu_new ();
+ gtk_widget_show (obj->priv->location_next_menu);
+
+ widget = GTK_WIDGET (gtk_menu_tool_button_new_from_stock (GTK_STOCK_GO_FORWARD));
+ gtk_menu_tool_button_set_menu (GTK_MENU_TOOL_BUTTON (widget),
+ obj->priv->location_next_menu);
+
+ g_object_set (widget, "label", _("Next location"), NULL);
+ gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (widget),
+ _("Go to next location"));
+ gtk_menu_tool_button_set_arrow_tooltip_text (GTK_MENU_TOOL_BUTTON (widget),
+ _("Go to a previously opened location"));
+
+ action = gtk_action_group_get_action (obj->priv->action_group_sensitive,
+ "DirectoryNext");
+ g_object_set (action, "is_important", TRUE, "short_label",
+ _("Previous location"), NULL);
+ gtk_action_connect_proxy (action, widget);
+ gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (widget), 1);
+
+ gtk_box_pack_start (GTK_BOX (obj), toolbar, FALSE, FALSE, 0);
+ gtk_widget_show (toolbar);
+
+ set_enable_delete (obj, obj->priv->enable_delete);
+}
+
+static void
+set_enable_delete (GeditFileBrowserWidget *obj,
+ gboolean enable)
+{
+ GtkAction *action;
+ obj->priv->enable_delete = enable;
+
+ if (obj->priv->action_group_selection == NULL)
+ return;
+
+ action =
+ gtk_action_group_get_action (obj->priv->action_group_selection,
+ "FileDelete");
+
+ g_object_set (action, "visible", enable, "sensitive", enable, NULL);
+}
+
+static gboolean
+filter_real (GeditFileBrowserStore * model, GtkTreeIter * iter,
+ GeditFileBrowserWidget * obj)
+{
+ GSList *item;
+ FilterFunc *func;
+
+ for (item = obj->priv->filter_funcs; item; item = item->next) {
+ func = (FilterFunc *) (item->data);
+
+ if (!func->func (obj, model, iter, func->user_data))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+add_bookmark_hash (GeditFileBrowserWidget * obj,
+ GtkTreeIter * iter)
+{
+ GtkTreeModel *model;
+ GdkPixbuf * pixbuf;
+ gchar * name;
+ gchar * uri;
+ GFile * file;
+ NameIcon * item;
+
+ model = GTK_TREE_MODEL (obj->priv->bookmarks_store);
+
+ uri = gedit_file_bookmarks_store_get_uri (obj->priv->
+ bookmarks_store,
+ iter);
+
+ if (uri == NULL)
+ return;
+
+ file = g_file_new_for_uri (uri);
+ g_free (uri);
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_ICON,
+ &pixbuf,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_NAME,
+ &name, -1);
+
+ item = g_new (NameIcon, 1);
+ item->name = name;
+ item->icon = pixbuf;
+
+ g_hash_table_insert (obj->priv->bookmarks_hash,
+ file,
+ item);
+}
+
+static void
+init_bookmarks_hash (GeditFileBrowserWidget * obj)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ model = GTK_TREE_MODEL (obj->priv->bookmarks_store);
+
+ if (!gtk_tree_model_get_iter_first (model, &iter))
+ return;
+
+ do {
+ add_bookmark_hash (obj, &iter);
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ g_signal_connect (obj->priv->bookmarks_store,
+ "row-changed",
+ G_CALLBACK (on_bookmarks_row_changed),
+ obj);
+
+ g_signal_connect (obj->priv->bookmarks_store,
+ "row-deleted",
+ G_CALLBACK (on_bookmarks_row_deleted),
+ obj);
+}
+
+static void
+on_begin_loading (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ if (!GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview))))
+ return;
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (obj)),
+ obj->priv->busy_cursor);
+}
+
+static void
+on_end_loading (GeditFileBrowserStore *model,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ if (!GDK_IS_WINDOW (gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview))))
+ return;
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (obj)), NULL);
+}
+
+static void
+create_tree (GeditFileBrowserWidget * obj)
+{
+ GtkWidget *sw;
+
+ obj->priv->file_store = gedit_file_browser_store_new (NULL);
+ obj->priv->bookmarks_store = gedit_file_bookmarks_store_new ();
+ obj->priv->treeview =
+ GEDIT_FILE_BROWSER_VIEW (gedit_file_browser_view_new ());
+
+ gedit_file_browser_view_set_restore_expand_state (obj->priv->treeview, TRUE);
+
+ gedit_file_browser_store_set_filter_mode (obj->priv->file_store,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN
+ |
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY);
+ gedit_file_browser_store_set_filter_func (obj->priv->file_store,
+ (GeditFileBrowserStoreFilterFunc)
+ filter_real, obj);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_ETCHED_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_container_add (GTK_CONTAINER (sw),
+ GTK_WIDGET (obj->priv->treeview));
+ gtk_box_pack_start (GTK_BOX (obj), sw, TRUE, TRUE, 0);
+
+ g_signal_connect (obj->priv->treeview, "notify::model",
+ G_CALLBACK (on_model_set), obj);
+ g_signal_connect (obj->priv->treeview, "error",
+ G_CALLBACK (on_treeview_error), obj);
+ g_signal_connect (obj->priv->treeview, "popup-menu",
+ G_CALLBACK (on_treeview_popup_menu), obj);
+ g_signal_connect (obj->priv->treeview, "button-press-event",
+ G_CALLBACK (on_treeview_button_press_event),
+ obj);
+ g_signal_connect (obj->priv->treeview, "key-press-event",
+ G_CALLBACK (on_treeview_key_press_event), obj);
+
+ g_signal_connect (gtk_tree_view_get_selection
+ (GTK_TREE_VIEW (obj->priv->treeview)), "changed",
+ G_CALLBACK (on_selection_changed), obj);
+ g_signal_connect (obj->priv->file_store, "notify::filter-mode",
+ G_CALLBACK (on_filter_mode_changed), obj);
+
+ g_signal_connect (obj->priv->file_store, "notify::virtual-root",
+ G_CALLBACK (on_virtual_root_changed), obj);
+
+ g_signal_connect (obj->priv->file_store, "begin-loading",
+ G_CALLBACK (on_begin_loading), obj);
+
+ g_signal_connect (obj->priv->file_store, "end-loading",
+ G_CALLBACK (on_end_loading), obj);
+
+ g_signal_connect (obj->priv->file_store, "error",
+ G_CALLBACK (on_file_store_error), obj);
+
+ init_bookmarks_hash (obj);
+
+ gtk_widget_show (sw);
+ gtk_widget_show (GTK_WIDGET (obj->priv->treeview));
+}
+
+static void
+create_filter (GeditFileBrowserWidget * obj)
+{
+ GtkWidget *expander;
+ GtkWidget *vbox;
+ GtkWidget *entry;
+
+ expander = gtk_expander_new_with_mnemonic (_("_Match Filename"));
+ gtk_widget_show (expander);
+ gtk_box_pack_start (GTK_BOX (obj), expander, FALSE, FALSE, 0);
+
+ vbox = gtk_vbox_new (FALSE, 3);
+ gtk_widget_show (vbox);
+
+ obj->priv->filter_expander = expander;
+
+ entry = gtk_entry_new ();
+ gtk_widget_show (entry);
+
+ obj->priv->filter_entry = entry;
+
+ g_signal_connect_swapped (entry, "activate",
+ G_CALLBACK (on_entry_filter_activate),
+ obj);
+ g_signal_connect_swapped (entry, "focus_out_event",
+ G_CALLBACK (on_entry_filter_activate),
+ obj);
+
+ gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, FALSE, 0);
+ gtk_container_add (GTK_CONTAINER (expander), vbox);
+}
+
+static void
+gedit_file_browser_widget_init (GeditFileBrowserWidget * obj)
+{
+ obj->priv = GEDIT_FILE_BROWSER_WIDGET_GET_PRIVATE (obj);
+
+ obj->priv->bookmarks_hash = g_hash_table_new_full (g_file_hash,
+ (GEqualFunc)g_file_equal,
+ g_object_unref,
+ free_name_icon);
+
+ gtk_box_set_spacing (GTK_BOX (obj), 3);
+
+ obj->priv->busy_cursor = gdk_cursor_new (GDK_WATCH);
+}
+
+/* Private */
+
+static void
+update_sensitivity (GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model =
+ gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ GtkAction *action;
+ gint mode;
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model)) {
+ gtk_action_group_set_sensitive (obj->priv->action_group,
+ TRUE);
+ gtk_action_group_set_sensitive (obj->priv->bookmark_action_group,
+ FALSE);
+
+ mode =
+ gedit_file_browser_store_get_filter_mode
+ (GEDIT_FILE_BROWSER_STORE (model));
+
+ action =
+ gtk_action_group_get_action (obj->priv->action_group,
+ "FilterHidden");
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ !(mode &
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN));
+ } else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) {
+ gtk_action_group_set_sensitive (obj->priv->action_group,
+ FALSE);
+ gtk_action_group_set_sensitive (obj->priv->bookmark_action_group,
+ TRUE);
+
+ /* Set the filter toggle to normal up state, just for visual pleasure */
+ action =
+ gtk_action_group_get_action (obj->priv->action_group,
+ "FilterHidden");
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ FALSE);
+ }
+
+ on_selection_changed (gtk_tree_view_get_selection
+ (GTK_TREE_VIEW (obj->priv->treeview)), obj);
+}
+
+static gboolean
+gedit_file_browser_widget_get_first_selected (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter)
+{
+ GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+ GtkTreeModel *model;
+ GList *rows = gtk_tree_selection_get_selected_rows (selection, &model);
+ gboolean result;
+
+ if (!rows)
+ return FALSE;
+
+ result = gtk_tree_model_get_iter(model, iter, (GtkTreePath *)(rows->data));
+
+ g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free (rows);
+
+ return result;
+}
+
+static gboolean
+popup_menu (GeditFileBrowserWidget * obj, GdkEventButton * event, GtkTreeModel * model)
+{
+ GtkWidget *menu;
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ menu = gtk_ui_manager_get_widget (obj->priv->manager, "/FilePopup");
+ else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ menu = gtk_ui_manager_get_widget (obj->priv->manager, "/BookmarkPopup");
+ else
+ return FALSE;
+
+ g_return_val_if_fail (menu != NULL, FALSE);
+
+ if (event != NULL) {
+ GtkTreeSelection *selection;
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (gtk_tree_selection_count_selected_rows (selection) <= 1) {
+ GtkTreePath *path;
+
+ if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (obj->priv->treeview),
+ (gint)event->x, (gint)event->y,
+ &path, NULL, NULL, NULL))
+ {
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, path);
+ gtk_tree_path_free (path);
+ }
+ }
+
+ gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+ event->button, event->time);
+ } else {
+ gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
+ gedit_utils_menu_position_under_tree_view,
+ obj->priv->treeview, 0,
+ gtk_get_current_event_time ());
+ gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+filter_glob (GeditFileBrowserWidget * obj, GeditFileBrowserStore * store,
+ GtkTreeIter * iter, gpointer user_data)
+{
+ gchar *name;
+ gboolean result;
+ guint flags;
+
+ if (obj->priv->filter_pattern == NULL)
+ return TRUE;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (store), iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_NAME, &name,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (FILE_IS_DIR (flags) || FILE_IS_DUMMY (flags))
+ result = TRUE;
+ else
+ result =
+ g_pattern_match_string (obj->priv->filter_pattern,
+ name);
+
+ g_free (name);
+
+ return result;
+}
+
+static void
+rename_selected_file (GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ if (gedit_file_browser_widget_get_first_selected (obj, &iter))
+ gedit_file_browser_view_start_rename (obj->priv->treeview,
+ &iter);
+}
+
+static GList *
+get_deletable_files (GeditFileBrowserWidget *obj) {
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GList *rows;
+ GList *row;
+ GList *paths = NULL;
+ guint flags;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ /* Get all selected files */
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ for (row = rows; row; row = row->next) {
+ path = (GtkTreePath *)(row->data);
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ continue;
+
+ gtk_tree_model_get (model, &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS,
+ &flags, -1);
+
+ if (FILE_IS_DUMMY (flags))
+ continue;
+
+ paths = g_list_append (paths, gtk_tree_path_copy (path));
+ }
+
+ g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free (rows);
+
+ return paths;
+}
+
+static gboolean
+delete_selected_files (GeditFileBrowserWidget * obj, gboolean trash)
+{
+ GtkTreeModel *model;
+ gboolean confirm;
+ GeditFileBrowserStoreResult result;
+ GList *rows;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return FALSE;
+
+ rows = get_deletable_files (obj);
+
+ if (!rows)
+ return FALSE;
+
+ if (!trash) {
+ g_signal_emit (obj, signals[CONFIRM_DELETE], 0, model, rows, &confirm);
+
+ if (!confirm)
+ return FALSE;
+ }
+
+ result = gedit_file_browser_store_delete_all (GEDIT_FILE_BROWSER_STORE (model),
+ rows, trash);
+
+ g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free (rows);
+
+ return result == GEDIT_FILE_BROWSER_STORE_RESULT_OK;
+}
+
+static gboolean
+on_file_store_no_trash (GeditFileBrowserStore * store,
+ GList * files,
+ GeditFileBrowserWidget * obj)
+{
+ gboolean confirm = FALSE;
+
+ g_signal_emit (obj, signals[CONFIRM_NO_TRASH], 0, files, &confirm);
+
+ return confirm;
+}
+
+static GFile *
+get_topmost_file (GFile * file)
+{
+ GFile * tmp;
+ GFile * current;
+
+ current = g_object_ref (file);
+
+ while ((tmp = g_file_get_parent (current)) != NULL) {
+ g_object_unref (current);
+ current = tmp;
+ }
+
+ return current;
+}
+
+static GtkWidget *
+create_goto_menu_item (GeditFileBrowserWidget * obj, GList * item,
+ GdkPixbuf * icon)
+{
+ GtkWidget *result;
+ GtkWidget *image;
+ gchar *unescape;
+ GdkPixbuf *pixbuf = NULL;
+ Location *loc;
+
+ loc = (Location *) (item->data);
+
+ if (!get_from_bookmark_file (obj, loc->virtual_root, &unescape, &pixbuf)) {
+ unescape = gedit_file_browser_utils_file_basename (loc->virtual_root);
+
+ if (icon)
+ pixbuf = g_object_ref (icon);
+ }
+
+ if (pixbuf) {
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ g_object_unref (pixbuf);
+
+ gtk_widget_show (image);
+
+ result = gtk_image_menu_item_new_with_label (unescape);
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (result),
+ image);
+ } else {
+ result = gtk_menu_item_new_with_label (unescape);
+ }
+
+ g_object_set_data (G_OBJECT (result), LOCATION_DATA_KEY, item);
+ g_signal_connect (result, "activate",
+ G_CALLBACK (on_location_jump_activate), obj);
+
+ gtk_widget_show (result);
+
+ g_free (unescape);
+
+ return result;
+}
+
+static GList *
+list_next_iterator (GList * list)
+{
+ if (!list)
+ return NULL;
+
+ return list->next;
+}
+
+static GList *
+list_prev_iterator (GList * list)
+{
+ if (!list)
+ return NULL;
+
+ return list->prev;
+}
+
+static void
+jump_to_location (GeditFileBrowserWidget * obj, GList * item,
+ gboolean previous)
+{
+ Location *loc;
+ GtkWidget *widget;
+ GList *children;
+ GList *child;
+ GList *(*iter_func) (GList *);
+ GtkWidget *menu_from;
+ GtkWidget *menu_to;
+ gchar *root;
+ gchar *virtual_root;
+
+ if (!obj->priv->locations)
+ return;
+
+ if (previous) {
+ iter_func = list_next_iterator;
+ menu_from = obj->priv->location_previous_menu;
+ menu_to = obj->priv->location_next_menu;
+ } else {
+ iter_func = list_prev_iterator;
+ menu_from = obj->priv->location_next_menu;
+ menu_to = obj->priv->location_previous_menu;
+ }
+
+ children = gtk_container_get_children (GTK_CONTAINER (menu_from));
+ child = children;
+
+ /* This is the menuitem for the current location, which is the first
+ to be added to the menu */
+ widget = obj->priv->current_location_menu_item;
+
+ while (obj->priv->current_location != item) {
+ if (widget) {
+ /* Prepend the menu item to the menu */
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu_to),
+ widget);
+
+ g_object_unref (widget);
+ }
+
+ widget = GTK_WIDGET (child->data);
+
+ /* Make sure the widget isn't destroyed when removed */
+ g_object_ref (widget);
+ gtk_container_remove (GTK_CONTAINER (menu_from), widget);
+
+ obj->priv->current_location_menu_item = widget;
+
+ if (obj->priv->current_location == NULL) {
+ obj->priv->current_location = obj->priv->locations;
+
+ if (obj->priv->current_location == item)
+ break;
+ } else {
+ obj->priv->current_location =
+ iter_func (obj->priv->current_location);
+ }
+
+ child = child->next;
+ }
+
+ g_list_free (children);
+
+ obj->priv->changing_location = TRUE;
+
+ loc = (Location *) (obj->priv->current_location->data);
+
+ /* Set the new root + virtual root */
+ root = g_file_get_uri (loc->root);
+ virtual_root = g_file_get_uri (loc->virtual_root);
+
+ gedit_file_browser_widget_set_root_and_virtual_root (obj,
+ root,
+ virtual_root);
+
+ g_free (root);
+ g_free (virtual_root);
+
+ obj->priv->changing_location = FALSE;
+}
+
+static void
+clear_next_locations (GeditFileBrowserWidget * obj)
+{
+ GList *children;
+ GList *item;
+
+ if (obj->priv->current_location == NULL)
+ return;
+
+ while (obj->priv->current_location->prev) {
+ location_free ((Location *) (obj->priv->current_location->
+ prev->data));
+ obj->priv->locations =
+ g_list_remove_link (obj->priv->locations,
+ obj->priv->current_location->prev);
+ }
+
+ children =
+ gtk_container_get_children (GTK_CONTAINER
+ (obj->priv->location_next_menu));
+
+ for (item = children; item; item = item->next) {
+ gtk_container_remove (GTK_CONTAINER
+ (obj->priv->location_next_menu),
+ GTK_WIDGET (item->data));
+ }
+
+ g_list_free (children);
+
+ gtk_action_set_sensitive (gtk_action_group_get_action
+ (obj->priv->action_group_sensitive,
+ "DirectoryNext"), FALSE);
+}
+
+static void
+update_filter_mode (GeditFileBrowserWidget * obj,
+ GtkAction * action,
+ GeditFileBrowserStoreFilterMode mode)
+{
+ gboolean active =
+ gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+ GtkTreeModel *model =
+ gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ gint now;
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model)) {
+ now =
+ gedit_file_browser_store_get_filter_mode
+ (GEDIT_FILE_BROWSER_STORE (model));
+
+ if (active)
+ now &= ~mode;
+ else
+ now |= mode;
+
+ gedit_file_browser_store_set_filter_mode
+ (GEDIT_FILE_BROWSER_STORE (model), now);
+ }
+}
+
+static void
+set_filter_pattern_real (GeditFileBrowserWidget * obj,
+ gchar const * pattern,
+ gboolean update_entry)
+{
+ GtkTreeModel *model;
+
+ model =
+ gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (pattern != NULL && *pattern == '\0')
+ pattern = NULL;
+
+ if (pattern == NULL && obj->priv->filter_pattern_str == NULL)
+ return;
+
+ if (pattern != NULL && obj->priv->filter_pattern_str != NULL &&
+ strcmp (pattern, obj->priv->filter_pattern_str) == 0)
+ return;
+
+ /* Free the old pattern */
+ g_free (obj->priv->filter_pattern_str);
+ obj->priv->filter_pattern_str = g_strdup (pattern);
+
+ if (obj->priv->filter_pattern) {
+ g_pattern_spec_free (obj->priv->filter_pattern);
+ obj->priv->filter_pattern = NULL;
+ }
+
+ if (pattern == NULL) {
+ if (obj->priv->glob_filter_id != 0) {
+ gedit_file_browser_widget_remove_filter (obj,
+ obj->
+ priv->
+ glob_filter_id);
+ obj->priv->glob_filter_id = 0;
+ }
+ } else {
+ obj->priv->filter_pattern = g_pattern_spec_new (pattern);
+
+ if (obj->priv->glob_filter_id == 0)
+ obj->priv->glob_filter_id =
+ gedit_file_browser_widget_add_filter (obj,
+ filter_glob,
+ NULL,
+ NULL);
+ }
+
+ if (update_entry) {
+ if (obj->priv->filter_pattern_str == NULL)
+ gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry),
+ "");
+ else {
+ gtk_entry_set_text (GTK_ENTRY (obj->priv->filter_entry),
+ obj->priv->filter_pattern_str);
+
+ gtk_expander_set_expanded (GTK_EXPANDER (obj->priv->filter_expander),
+ TRUE);
+ }
+ }
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ gedit_file_browser_store_refilter (GEDIT_FILE_BROWSER_STORE
+ (model));
+
+ g_object_notify (G_OBJECT (obj), "filter-pattern");
+}
+
+
+/* Public */
+
+GtkWidget *
+gedit_file_browser_widget_new (const gchar *data_dir)
+{
+ GeditFileBrowserWidget *obj =
+ g_object_new (GEDIT_TYPE_FILE_BROWSER_WIDGET, NULL);
+
+ create_toolbar (obj, data_dir);
+ create_combo (obj);
+ create_tree (obj);
+ create_filter (obj);
+
+ gedit_file_browser_widget_show_bookmarks (obj);
+
+ return GTK_WIDGET (obj);
+}
+
+void
+gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget * obj)
+{
+ /* Select bookmarks in the combo box */
+ g_signal_handlers_block_by_func (obj->priv->combo,
+ on_combo_changed, obj);
+ combo_set_active_by_id (obj, BOOKMARKS_ID);
+ g_signal_handlers_unblock_by_func (obj->priv->combo,
+ on_combo_changed, obj);
+
+ check_current_item (obj, FALSE);
+
+ gedit_file_browser_view_set_model (obj->priv->treeview,
+ GTK_TREE_MODEL (obj->priv->
+ bookmarks_store));
+}
+
+static void
+show_files_real (GeditFileBrowserWidget *obj,
+ gboolean do_root_changed)
+{
+ gedit_file_browser_view_set_model (obj->priv->treeview,
+ GTK_TREE_MODEL (obj->priv->
+ file_store));
+
+ if (do_root_changed)
+ on_virtual_root_changed (obj->priv->file_store, NULL, obj);
+}
+
+void
+gedit_file_browser_widget_show_files (GeditFileBrowserWidget * obj)
+{
+ show_files_real (obj, TRUE);
+}
+
+void
+gedit_file_browser_widget_set_root_and_virtual_root (GeditFileBrowserWidget *obj,
+ gchar const *root,
+ gchar const *virtual_root)
+{
+ GeditFileBrowserStoreResult result;
+
+ if (!virtual_root)
+ result =
+ gedit_file_browser_store_set_root_and_virtual_root
+ (obj->priv->file_store, root, root);
+ else
+ result =
+ gedit_file_browser_store_set_root_and_virtual_root
+ (obj->priv->file_store, root, virtual_root);
+
+ if (result == GEDIT_FILE_BROWSER_STORE_RESULT_NO_CHANGE)
+ show_files_real (obj, TRUE);
+}
+
+void
+gedit_file_browser_widget_set_root (GeditFileBrowserWidget * obj,
+ gchar const *root,
+ gboolean virtual_root)
+{
+ GFile *file;
+ GFile *parent;
+ gchar *str;
+
+ if (!virtual_root) {
+ gedit_file_browser_widget_set_root_and_virtual_root (obj,
+ root,
+ NULL);
+ return;
+ }
+
+ if (!root)
+ return;
+
+ file = g_file_new_for_uri (root);
+ parent = get_topmost_file (file);
+ str = g_file_get_uri (parent);
+
+ gedit_file_browser_widget_set_root_and_virtual_root
+ (obj, str, root);
+
+ g_free (str);
+
+ g_object_unref (file);
+ g_object_unref (parent);
+}
+
+GeditFileBrowserStore *
+gedit_file_browser_widget_get_browser_store (GeditFileBrowserWidget * obj)
+{
+ return obj->priv->file_store;
+}
+
+GeditFileBookmarksStore *
+gedit_file_browser_widget_get_bookmarks_store (GeditFileBrowserWidget * obj)
+{
+ return obj->priv->bookmarks_store;
+}
+
+GeditFileBrowserView *
+gedit_file_browser_widget_get_browser_view (GeditFileBrowserWidget * obj)
+{
+ return obj->priv->treeview;
+}
+
+GtkUIManager *
+gedit_file_browser_widget_get_ui_manager (GeditFileBrowserWidget * obj)
+{
+ return obj->priv->manager;
+}
+
+GtkWidget *
+gedit_file_browser_widget_get_filter_entry (GeditFileBrowserWidget * obj)
+{
+ return obj->priv->filter_entry;
+}
+
+gulong
+gedit_file_browser_widget_add_filter (GeditFileBrowserWidget * obj,
+ GeditFileBrowserWidgetFilterFunc func,
+ gpointer user_data,
+ GDestroyNotify notify)
+{
+ FilterFunc *f;
+ GtkTreeModel *model =
+ gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ f = filter_func_new (obj, func, user_data, notify);
+ obj->priv->filter_funcs =
+ g_slist_append (obj->priv->filter_funcs, f);
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ gedit_file_browser_store_refilter (GEDIT_FILE_BROWSER_STORE
+ (model));
+
+ return f->id;
+}
+
+void
+gedit_file_browser_widget_remove_filter (GeditFileBrowserWidget * obj,
+ gulong id)
+{
+ GSList *item;
+ FilterFunc *func;
+
+ for (item = obj->priv->filter_funcs; item; item = item->next)
+ {
+ func = (FilterFunc *) (item->data);
+
+ if (func->id == id)
+ {
+ if (func->destroy_notify)
+ func->destroy_notify (func->user_data);
+
+ obj->priv->filter_funcs =
+ g_slist_remove_link (obj->priv->filter_funcs,
+ item);
+ g_free (func);
+ break;
+ }
+ }
+}
+
+void
+gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget * obj,
+ gchar const *pattern)
+{
+ set_filter_pattern_real (obj, pattern, TRUE);
+}
+
+gboolean
+gedit_file_browser_widget_get_selected_directory (GeditFileBrowserWidget * obj,
+ GtkTreeIter * iter)
+{
+ GtkTreeModel *model =
+ gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ GtkTreeIter parent;
+ guint flags;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return FALSE;
+
+ if (!gedit_file_browser_widget_get_first_selected (obj, iter)) {
+ if (!gedit_file_browser_store_get_iter_virtual_root
+ (GEDIT_FILE_BROWSER_STORE (model), iter))
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!FILE_IS_DIR (flags)) {
+ /* Get the parent, because the selection is a file */
+ gtk_tree_model_iter_parent (model, &parent, iter);
+ *iter = parent;
+ }
+
+ return TRUE;
+}
+
+static guint
+gedit_file_browser_widget_get_num_selected_files_or_directories (GeditFileBrowserWidget *obj,
+ guint *files,
+ guint *dirs)
+{
+ GList *rows, *row;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ GeditFileBrowserStoreFlag flags;
+ guint result = 0;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ return 0;
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ for (row = rows; row; row = row->next) {
+ path = (GtkTreePath *)(row->data);
+
+ /* Get iter from path */
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ continue;
+
+ gtk_tree_model_get (model, &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ -1);
+
+ if (!FILE_IS_DUMMY (flags)) {
+ if (!FILE_IS_DIR (flags))
+ ++(*files);
+ else
+ ++(*dirs);
+
+ ++result;
+ }
+ }
+
+ g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
+ g_list_free (rows);
+
+ return result;
+}
+
+typedef struct
+{
+ GeditFileBrowserWidget *widget;
+ GCancellable *cancellable;
+} AsyncData;
+
+static AsyncData *
+async_data_new (GeditFileBrowserWidget *widget)
+{
+ AsyncData *ret;
+
+ ret = g_new (AsyncData, 1);
+ ret->widget = widget;
+
+ cancel_async_operation (widget);
+ widget->priv->cancellable = g_cancellable_new ();
+
+ ret->cancellable = g_object_ref (widget->priv->cancellable);
+
+ return ret;
+}
+
+static void
+async_free (AsyncData *async)
+{
+ g_object_unref (async->cancellable);
+ g_free (async);
+}
+
+static void
+set_busy (GeditFileBrowserWidget *obj, gboolean busy)
+{
+ GdkCursor *cursor;
+ GdkWindow *window;
+
+ window = gtk_widget_get_window (GTK_WIDGET (obj->priv->treeview));
+
+ if (!GDK_IS_WINDOW (window))
+ return;
+
+ if (busy)
+ {
+ cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (window, cursor);
+ gdk_cursor_unref (cursor);
+ }
+ else
+ {
+ gdk_window_set_cursor (window, NULL);
+ }
+}
+
+static void try_mount_volume (GeditFileBrowserWidget *widget, GVolume *volume);
+
+static void
+activate_mount (GeditFileBrowserWidget *widget,
+ GVolume *volume,
+ GMount *mount)
+{
+ GFile *root;
+ gchar *uri;
+
+ if (!mount)
+ {
+ gchar *message;
+ gchar *name;
+
+ name = g_volume_get_name (volume);
+ message = g_strdup_printf (_("No mount object for mounted volume: %s"), name);
+
+ g_signal_emit (widget,
+ signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ message);
+
+ g_free (name);
+ g_free (message);
+ return;
+ }
+
+ root = g_mount_get_root (mount);
+ uri = g_file_get_uri (root);
+
+ gedit_file_browser_widget_set_root (widget, uri, FALSE);
+
+ g_free (uri);
+ g_object_unref (root);
+}
+
+static void
+try_activate_drive (GeditFileBrowserWidget *widget,
+ GDrive *drive)
+{
+ GList *volumes;
+ GVolume *volume;
+ GMount *mount;
+
+ volumes = g_drive_get_volumes (drive);
+
+ volume = G_VOLUME (volumes->data);
+ mount = g_volume_get_mount (volume);
+
+ if (mount)
+ {
+ /* try set the root of the mount */
+ activate_mount (widget, volume, mount);
+ g_object_unref (mount);
+ }
+ else
+ {
+ /* try to mount it then? */
+ try_mount_volume (widget, volume);
+ }
+
+ g_list_foreach (volumes, (GFunc)g_object_unref, NULL);
+ g_list_free (volumes);
+}
+
+static void
+poll_for_media_cb (GDrive *drive,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+
+ /* check for cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_free (async);
+ return;
+ }
+
+ /* finish poll operation */
+ set_busy (async->widget, FALSE);
+
+ if (g_drive_poll_for_media_finish (drive, res, &error) &&
+ g_drive_has_media (drive) &&
+ g_drive_has_volumes (drive))
+ {
+ try_activate_drive (async->widget, drive);
+ }
+ else
+ {
+ gchar *message;
+ gchar *name;
+
+ name = g_drive_get_name (drive);
+ message = g_strdup_printf (_("Could not open media: %s"), name);
+
+ g_signal_emit (async->widget,
+ signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ message);
+
+ g_free (name);
+ g_free (message);
+
+ g_error_free (error);
+ }
+
+ async_free (async);
+}
+
+static void
+mount_volume_cb (GVolume *volume,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+
+ /* check for cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_free (async);
+ return;
+ }
+
+ if (g_volume_mount_finish (volume, res, &error))
+ {
+ GMount *mount;
+
+ mount = g_volume_get_mount (volume);
+ activate_mount (async->widget, volume, mount);
+
+ if (mount)
+ g_object_unref (mount);
+ }
+ else
+ {
+ gchar *message;
+ gchar *name;
+
+ name = g_volume_get_name (volume);
+ message = g_strdup_printf (_("Could not mount volume: %s"), name);
+
+ g_signal_emit (async->widget,
+ signals[ERROR],
+ 0,
+ GEDIT_FILE_BROWSER_ERROR_SET_ROOT,
+ message);
+
+ g_free (name);
+ g_free (message);
+
+ g_error_free (error);
+ }
+
+ set_busy (async->widget, FALSE);
+ async_free (async);
+}
+
+static void
+activate_drive (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter)
+{
+ GDrive *drive;
+ AsyncData *async;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->bookmarks_store), iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT,
+ &drive, -1);
+
+ /* most common use case is a floppy drive, we'll poll for media and
+ go from there */
+ async = async_data_new (obj);
+ g_drive_poll_for_media (drive,
+ async->cancellable,
+ (GAsyncReadyCallback)poll_for_media_cb,
+ async);
+
+ g_object_unref (drive);
+ set_busy (obj, TRUE);
+}
+
+static void
+try_mount_volume (GeditFileBrowserWidget *widget,
+ GVolume *volume)
+{
+ GMountOperation *operation;
+ AsyncData *async;
+
+ operation = gtk_mount_operation_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (widget))));
+ async = async_data_new (widget);
+
+ g_volume_mount (volume,
+ G_MOUNT_MOUNT_NONE,
+ operation,
+ async->cancellable,
+ (GAsyncReadyCallback)mount_volume_cb,
+ async);
+
+ g_object_unref (operation);
+ set_busy (widget, TRUE);
+}
+
+static void
+activate_volume (GeditFileBrowserWidget *obj,
+ GtkTreeIter *iter)
+{
+ GVolume *volume;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->bookmarks_store), iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_OBJECT,
+ &volume, -1);
+
+ /* see if we can mount the volume */
+ try_mount_volume (obj, volume);
+ g_object_unref (volume);
+}
+
+void
+gedit_file_browser_widget_refresh (GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model =
+ gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ gedit_file_browser_store_refresh (GEDIT_FILE_BROWSER_STORE
+ (model));
+ else if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) {
+ g_hash_table_ref (obj->priv->bookmarks_hash);
+ g_hash_table_destroy (obj->priv->bookmarks_hash);
+
+ gedit_file_bookmarks_store_refresh
+ (GEDIT_FILE_BOOKMARKS_STORE (model));
+ }
+}
+
+void
+gedit_file_browser_widget_history_back (GeditFileBrowserWidget *obj)
+{
+ if (obj->priv->locations) {
+ if (obj->priv->current_location)
+ jump_to_location (obj,
+ obj->priv->current_location->
+ next, TRUE);
+ else {
+ jump_to_location (obj, obj->priv->locations, TRUE);
+ }
+ }
+}
+
+void
+gedit_file_browser_widget_history_forward (GeditFileBrowserWidget *obj)
+{
+ if (obj->priv->locations)
+ jump_to_location (obj, obj->priv->current_location->prev,
+ FALSE);
+}
+
+static void
+bookmark_open (GeditFileBrowserWidget *obj,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ gchar *uri;
+ gint flags;
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BOOKMARKS_STORE_COLUMN_FLAGS,
+ &flags, -1);
+
+ if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_DRIVE)
+ {
+ /* handle a drive node */
+ gedit_file_browser_store_cancel_mount_operation (obj->priv->file_store);
+ activate_drive (obj, iter);
+ return;
+ }
+ else if (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_VOLUME)
+ {
+ /* handle a volume node */
+ gedit_file_browser_store_cancel_mount_operation (obj->priv->file_store);
+ activate_volume (obj, iter);
+ return;
+ }
+
+ uri =
+ gedit_file_bookmarks_store_get_uri
+ (GEDIT_FILE_BOOKMARKS_STORE (model), iter);
+
+ if (uri) {
+ /* here we check if the bookmark is a mount point, or if it
+ is a remote bookmark. If that's the case, we will set the
+ root to the uri of the bookmark and not try to set the
+ topmost parent as root (since that may as well not be the
+ mount point anymore) */
+ if ((flags & GEDIT_FILE_BOOKMARKS_STORE_IS_MOUNT) ||
+ (flags & GEDIT_FILE_BOOKMARKS_STORE_IS_REMOTE_BOOKMARK)) {
+ gedit_file_browser_widget_set_root (obj,
+ uri,
+ FALSE);
+ } else {
+ gedit_file_browser_widget_set_root (obj,
+ uri,
+ TRUE);
+ }
+ } else {
+ g_warning ("No uri!");
+ }
+
+ g_free (uri);
+}
+
+static void
+file_open (GeditFileBrowserWidget *obj,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ gchar *uri;
+ gint flags;
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri,
+ -1);
+
+ if (!FILE_IS_DIR (flags) && !FILE_IS_DUMMY (flags)) {
+ g_signal_emit (obj, signals[URI_ACTIVATED], 0, uri);
+ }
+
+ g_free (uri);
+}
+
+static gboolean
+directory_open (GeditFileBrowserWidget *obj,
+ GtkTreeModel *model,
+ GtkTreeIter *iter)
+{
+ gboolean result = FALSE;
+ GError *error = NULL;
+ gchar *uri = NULL;
+ GeditFileBrowserStoreFlag flags;
+
+ gtk_tree_model_get (model, iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI, &uri,
+ -1);
+
+ if (FILE_IS_DIR (flags)) {
+ result = TRUE;
+
+ if (!gtk_show_uri (gtk_widget_get_screen (GTK_WIDGET (obj)), uri, GDK_CURRENT_TIME, &error)) {
+ g_signal_emit (obj, signals[ERROR], 0,
+ GEDIT_FILE_BROWSER_ERROR_OPEN_DIRECTORY,
+ error->message);
+
+ g_error_free (error);
+ error = NULL;
+ }
+ }
+
+ g_free (uri);
+
+ return result;
+}
+
+static void
+on_bookmark_activated (GeditFileBrowserView *tree_view,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+
+ bookmark_open (obj, model, iter);
+}
+
+static void
+on_file_activated (GeditFileBrowserView *tree_view,
+ GtkTreeIter *iter,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view));
+
+ file_open (obj, model, iter);
+}
+
+static gboolean
+virtual_root_is_root (GeditFileBrowserWidget * obj,
+ GeditFileBrowserStore * model)
+{
+ GtkTreeIter root;
+ GtkTreeIter virtual_root;
+
+ if (!gedit_file_browser_store_get_iter_root (model, &root))
+ return TRUE;
+
+ if (!gedit_file_browser_store_get_iter_virtual_root (model, &virtual_root))
+ return TRUE;
+
+ return gedit_file_browser_store_iter_equal (model, &root, &virtual_root);
+}
+
+static void
+on_virtual_root_changed (GeditFileBrowserStore * model,
+ GParamSpec * param,
+ GeditFileBrowserWidget * obj)
+{
+ GtkTreeIter iter;
+ gchar *uri;
+ gchar *root_uri;
+ GtkTreeIter root;
+ GtkAction *action;
+ Location *loc;
+ GdkPixbuf *pixbuf;
+
+ if (gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview)) !=
+ GTK_TREE_MODEL (obj->priv->file_store))
+ {
+ show_files_real (obj, FALSE);
+ }
+
+ if (gedit_file_browser_store_get_iter_virtual_root (model, &iter)) {
+ gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_URI,
+ &uri, -1);
+
+ if (gedit_file_browser_store_get_iter_root (model, &root)) {
+ if (!obj->priv->changing_location) {
+ /* Remove all items from obj->priv->current_location on */
+ if (obj->priv->current_location)
+ clear_next_locations (obj);
+
+ root_uri =
+ gedit_file_browser_store_get_root
+ (model);
+
+ loc = g_new (Location, 1);
+ loc->root = g_file_new_for_uri (root_uri);
+ loc->virtual_root = g_file_new_for_uri (uri);
+ g_free (root_uri);
+
+ if (obj->priv->current_location) {
+ /* Add current location to the menu so we can go back
+ to it later */
+ gtk_menu_shell_prepend
+ (GTK_MENU_SHELL
+ (obj->priv->
+ location_previous_menu),
+ obj->priv->
+ current_location_menu_item);
+ }
+
+ obj->priv->locations =
+ g_list_prepend (obj->priv->locations,
+ loc);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model),
+ &iter,
+ GEDIT_FILE_BROWSER_STORE_COLUMN_ICON,
+ &pixbuf, -1);
+
+ obj->priv->current_location =
+ obj->priv->locations;
+ obj->priv->current_location_menu_item =
+ create_goto_menu_item (obj,
+ obj->priv->
+ current_location,
+ pixbuf);
+
+ g_object_ref_sink (obj->priv->
+ current_location_menu_item);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+ }
+
+ action =
+ gtk_action_group_get_action (obj->priv->
+ action_group,
+ "DirectoryUp");
+ gtk_action_set_sensitive (action,
+ !virtual_root_is_root (obj, model));
+
+ action =
+ gtk_action_group_get_action (obj->priv->
+ action_group_sensitive,
+ "DirectoryPrevious");
+ gtk_action_set_sensitive (action,
+ obj->priv->
+ current_location != NULL
+ && obj->priv->
+ current_location->next !=
+ NULL);
+
+ action =
+ gtk_action_group_get_action (obj->priv->
+ action_group_sensitive,
+ "DirectoryNext");
+ gtk_action_set_sensitive (action,
+ obj->priv->
+ current_location != NULL
+ && obj->priv->
+ current_location->prev !=
+ NULL);
+ }
+
+ check_current_item (obj, TRUE);
+ g_free (uri);
+ } else {
+ g_message ("NO!");
+ }
+}
+
+static void
+on_model_set (GObject * gobject, GParamSpec * arg1,
+ GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (gobject));
+
+ clear_signals (obj);
+
+ if (GEDIT_IS_FILE_BOOKMARKS_STORE (model)) {
+ clear_next_locations (obj);
+
+ /* Add the current location to the back menu */
+ if (obj->priv->current_location) {
+ GtkAction *action;
+
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (obj->priv->location_previous_menu),
+ obj->priv->current_location_menu_item);
+
+ g_object_unref (obj->priv->current_location_menu_item);
+ obj->priv->current_location = NULL;
+ obj->priv->current_location_menu_item = NULL;
+
+ action = gtk_action_group_get_action (obj->priv->action_group_sensitive,
+ "DirectoryPrevious");
+ gtk_action_set_sensitive (action, TRUE);
+ }
+
+ gtk_widget_set_sensitive (obj->priv->filter_expander, FALSE);
+
+ add_signal (obj, gobject,
+ g_signal_connect (gobject, "bookmark-activated",
+ G_CALLBACK
+ (on_bookmark_activated), obj));
+ } else if (GEDIT_IS_FILE_BROWSER_STORE (model)) {
+ /* make sure any async operation is cancelled */
+ cancel_async_operation (obj);
+
+ add_signal (obj, gobject,
+ g_signal_connect (gobject, "file-activated",
+ G_CALLBACK
+ (on_file_activated), obj));
+
+ add_signal (obj, model,
+ g_signal_connect (model, "no-trash",
+ G_CALLBACK
+ (on_file_store_no_trash), obj));
+
+ gtk_widget_set_sensitive (obj->priv->filter_expander, TRUE);
+ }
+
+ update_sensitivity (obj);
+}
+
+static void
+on_file_store_error (GeditFileBrowserStore * store, guint code,
+ gchar * message, GeditFileBrowserWidget * obj)
+{
+ g_signal_emit (obj, signals[ERROR], 0, code, message);
+}
+
+static void
+on_treeview_error (GeditFileBrowserView * tree_view, guint code,
+ gchar * message, GeditFileBrowserWidget * obj)
+{
+ g_signal_emit (obj, signals[ERROR], 0, code, message);
+}
+
+static void
+on_combo_changed (GtkComboBox * combo, GeditFileBrowserWidget * obj)
+{
+ GtkTreeIter iter;
+ guint id;
+ gchar * uri;
+ GFile * file;
+
+ if (!gtk_combo_box_get_active_iter (combo, &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (obj->priv->combo_model), &iter,
+ COLUMN_ID, &id, -1);
+
+ switch (id) {
+ case BOOKMARKS_ID:
+ gedit_file_browser_widget_show_bookmarks (obj);
+ break;
+
+ case PATH_ID:
+ gtk_tree_model_get (GTK_TREE_MODEL
+ (obj->priv->combo_model), &iter,
+ COLUMN_FILE, &file, -1);
+
+ uri = g_file_get_uri (file);
+ gedit_file_browser_store_set_virtual_root_from_string
+ (obj->priv->file_store, uri);
+
+ g_free (uri);
+ g_object_unref (file);
+ break;
+ }
+}
+
+static gboolean
+on_treeview_popup_menu (GeditFileBrowserView * treeview,
+ GeditFileBrowserWidget * obj)
+{
+ return popup_menu (obj, NULL, gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)));
+}
+
+static gboolean
+on_treeview_button_press_event (GeditFileBrowserView * treeview,
+ GdkEventButton * event,
+ GeditFileBrowserWidget * obj)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
+ return popup_menu (obj, event,
+ gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)));
+ }
+
+ return FALSE;
+}
+
+static gboolean
+do_change_directory (GeditFileBrowserWidget * obj,
+ GdkEventKey * event)
+{
+ GtkAction * action = NULL;
+
+ if ((event->state &
+ (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK & ~GDK_MOD1_MASK)) ==
+ event->state && event->keyval == GDK_BackSpace)
+ action = gtk_action_group_get_action (obj->priv->
+ action_group_sensitive,
+ "DirectoryPrevious");
+ else if (!((event->state & GDK_MOD1_MASK) &&
+ (event->state & (~GDK_CONTROL_MASK & ~GDK_SHIFT_MASK)) == event->state))
+ return FALSE;
+
+ switch (event->keyval) {
+ case GDK_Left:
+ action = gtk_action_group_get_action (obj->priv->
+ action_group_sensitive,
+ "DirectoryPrevious");
+ break;
+ case GDK_Right:
+ action = gtk_action_group_get_action (obj->priv->
+ action_group_sensitive,
+ "DirectoryNext");
+ break;
+ case GDK_Up:
+ action = gtk_action_group_get_action (obj->priv->
+ action_group,
+ "DirectoryUp");
+ break;
+ default:
+ break;
+ }
+
+ if (action != NULL) {
+ gtk_action_activate (action);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+on_treeview_key_press_event (GeditFileBrowserView * treeview,
+ GdkEventKey * event,
+ GeditFileBrowserWidget * obj)
+{
+ guint modifiers;
+
+ if (do_change_directory (obj, event))
+ return TRUE;
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE
+ (gtk_tree_view_get_model (GTK_TREE_VIEW (treeview))))
+ return FALSE;
+
+ modifiers = gtk_accelerator_get_default_mod_mask ();
+
+ if (event->keyval == GDK_Delete
+ || event->keyval == GDK_KP_Delete) {
+
+ if ((event->state & modifiers) == GDK_SHIFT_MASK) {
+ if (obj->priv->enable_delete) {
+ delete_selected_files (obj, FALSE);
+ return TRUE;
+ }
+ } else if ((event->state & modifiers) == 0) {
+ delete_selected_files (obj, TRUE);
+ return TRUE;
+ }
+ }
+
+ if ((event->keyval == GDK_F2)
+ && (event->state & modifiers) == 0) {
+ rename_selected_file (obj);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+on_selection_changed (GtkTreeSelection * selection,
+ GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+ guint selected = 0;
+ guint files = 0;
+ guint dirs = 0;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (GEDIT_IS_FILE_BROWSER_STORE (model))
+ {
+ selected = gedit_file_browser_widget_get_num_selected_files_or_directories (obj,
+ &files,
+ &dirs);
+ }
+
+ gtk_action_group_set_sensitive (obj->priv->action_group_selection,
+ selected > 0);
+ gtk_action_group_set_sensitive (obj->priv->action_group_file_selection,
+ (selected > 0) && (selected == files));
+ gtk_action_group_set_sensitive (obj->priv->action_group_single_selection,
+ selected == 1);
+ gtk_action_group_set_sensitive (obj->priv->action_group_single_most_selection,
+ selected <= 1);
+}
+
+static gboolean
+on_entry_filter_activate (GeditFileBrowserWidget * obj)
+{
+ gchar const *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (obj->priv->filter_entry));
+ set_filter_pattern_real (obj, text, FALSE);
+
+ return FALSE;
+}
+
+static void
+on_location_jump_activate (GtkMenuItem * item,
+ GeditFileBrowserWidget * obj)
+{
+ GList *location;
+
+ location = g_object_get_data (G_OBJECT (item), LOCATION_DATA_KEY);
+
+ if (obj->priv->current_location) {
+ jump_to_location (obj, location,
+ g_list_position (obj->priv->locations,
+ location) >
+ g_list_position (obj->priv->locations,
+ obj->priv->
+ current_location));
+ } else {
+ jump_to_location (obj, location, TRUE);
+ }
+
+}
+
+static void
+on_bookmarks_row_changed (GtkTreeModel * model,
+ GtkTreePath * path,
+ GtkTreeIter * iter,
+ GeditFileBrowserWidget *obj)
+{
+ add_bookmark_hash (obj, iter);
+}
+
+static void
+on_bookmarks_row_deleted (GtkTreeModel * model,
+ GtkTreePath * path,
+ GeditFileBrowserWidget *obj)
+{
+ GtkTreeIter iter;
+ gchar * uri;
+ GFile * file;
+
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ return;
+
+ uri = gedit_file_bookmarks_store_get_uri (obj->priv->bookmarks_store, &iter);
+
+ if (!uri)
+ return;
+
+ file = g_file_new_for_uri (uri);
+ g_hash_table_remove (obj->priv->bookmarks_hash, file);
+
+ g_object_unref (file);
+ g_free (uri);
+}
+
+static void
+on_filter_mode_changed (GeditFileBrowserStore * model,
+ GParamSpec * param,
+ GeditFileBrowserWidget * obj)
+{
+ gint mode;
+ GtkToggleAction * action;
+ gboolean active;
+
+ mode = gedit_file_browser_store_get_filter_mode (model);
+
+ action = GTK_TOGGLE_ACTION (gtk_action_group_get_action (obj->priv->action_group,
+ "FilterHidden"));
+ active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN);
+
+ if (active != gtk_toggle_action_get_active (action))
+ gtk_toggle_action_set_active (action, active);
+
+ action = GTK_TOGGLE_ACTION (gtk_action_group_get_action (obj->priv->action_group,
+ "FilterBinary"));
+ active = !(mode & GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY);
+
+ if (active != gtk_toggle_action_get_active (action))
+ gtk_toggle_action_set_active (action, active);
+}
+
+static void
+on_action_directory_next (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ gedit_file_browser_widget_history_forward (obj);
+}
+
+static void
+on_action_directory_previous (GtkAction * action,
+ GeditFileBrowserWidget * obj)
+{
+ gedit_file_browser_widget_history_back (obj);
+}
+
+static void
+on_action_directory_up (GtkAction * action,
+ GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ gedit_file_browser_store_set_virtual_root_up (GEDIT_FILE_BROWSER_STORE (model));
+}
+
+static void
+on_action_directory_new (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+ GtkTreeIter parent;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ if (!gedit_file_browser_widget_get_selected_directory (obj, &parent))
+ return;
+
+ if (gedit_file_browser_store_new_directory
+ (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) {
+ gedit_file_browser_view_start_rename (obj->priv->treeview,
+ &iter);
+ }
+}
+
+static void
+on_action_file_open (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GList *rows;
+ GList *row;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ for (row = rows; row; row = row->next) {
+ path = (GtkTreePath *)(row->data);
+
+ if (gtk_tree_model_get_iter (model, &iter, path))
+ file_open (obj, model, &iter);
+
+ gtk_tree_path_free (path);
+ }
+
+ g_list_free (rows);
+}
+
+static void
+on_action_file_new (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+ GtkTreeIter parent;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ if (!gedit_file_browser_widget_get_selected_directory (obj, &parent))
+ return;
+
+ if (gedit_file_browser_store_new_file
+ (GEDIT_FILE_BROWSER_STORE (model), &parent, &iter)) {
+ gedit_file_browser_view_start_rename (obj->priv->treeview,
+ &iter);
+ }
+}
+
+static void
+on_action_file_rename (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ rename_selected_file (obj);
+}
+
+static void
+on_action_file_delete (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ delete_selected_files (obj, FALSE);
+}
+
+static void
+on_action_file_move_to_trash (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ delete_selected_files (obj, TRUE);
+}
+
+static void
+on_action_directory_refresh (GtkAction * action,
+ GeditFileBrowserWidget * obj)
+{
+ gedit_file_browser_widget_refresh (obj);
+}
+
+static void
+on_action_directory_open (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GList *rows;
+ GList *row;
+ gboolean directory_opened = FALSE;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BROWSER_STORE (model))
+ return;
+
+ rows = gtk_tree_selection_get_selected_rows (selection, &model);
+
+ for (row = rows; row; row = row->next) {
+ path = (GtkTreePath *)(row->data);
+
+ if (gtk_tree_model_get_iter (model, &iter, path))
+ directory_opened |= directory_open (obj, model, &iter);
+
+ gtk_tree_path_free (path);
+ }
+
+ if (!directory_opened) {
+ if (gedit_file_browser_widget_get_selected_directory (obj, &iter))
+ directory_open (obj, model, &iter);
+ }
+
+ g_list_free (rows);
+}
+
+static void
+on_action_filter_hidden (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ update_filter_mode (obj,
+ action,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN);
+}
+
+static void
+on_action_filter_binary (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ update_filter_mode (obj,
+ action,
+ GEDIT_FILE_BROWSER_STORE_FILTER_MODE_HIDE_BINARY);
+}
+
+static void
+on_action_bookmark_open (GtkAction * action, GeditFileBrowserWidget * obj)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (obj->priv->treeview));
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (obj->priv->treeview));
+
+ if (!GEDIT_IS_FILE_BOOKMARKS_STORE (model))
+ return;
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ bookmark_open (obj, model, &iter);
+}
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser-widget.h b/plugins/filebrowser/gedit-file-browser-widget.h
new file mode 100755
index 00000000..e9cc2a0e
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser-widget.h
@@ -0,0 +1,121 @@
+/*
+ * gedit-file-browser-widget.h - Gedit plugin providing easy file access
+ * from the sidepanel
+ *
+ * Copyright (C) 2006 - Jesse van den Kieboom <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_FILE_BROWSER_WIDGET_H__
+#define __GEDIT_FILE_BROWSER_WIDGET_H__
+
+#include <gtk/gtk.h>
+#include "gedit-file-browser-store.h"
+#include "gedit-file-bookmarks-store.h"
+#include "gedit-file-browser-view.h"
+
+G_BEGIN_DECLS
+#define GEDIT_TYPE_FILE_BROWSER_WIDGET (gedit_file_browser_widget_get_type ())
+#define GEDIT_FILE_BROWSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidget))
+#define GEDIT_FILE_BROWSER_WIDGET_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidget const))
+#define GEDIT_FILE_BROWSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidgetClass))
+#define GEDIT_IS_FILE_BROWSER_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET))
+#define GEDIT_IS_FILE_BROWSER_WIDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_FILE_BROWSER_WIDGET))
+#define GEDIT_FILE_BROWSER_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_FILE_BROWSER_WIDGET, GeditFileBrowserWidgetClass))
+
+typedef struct _GeditFileBrowserWidget GeditFileBrowserWidget;
+typedef struct _GeditFileBrowserWidgetClass GeditFileBrowserWidgetClass;
+typedef struct _GeditFileBrowserWidgetPrivate GeditFileBrowserWidgetPrivate;
+
+typedef
+gboolean (*GeditFileBrowserWidgetFilterFunc) (GeditFileBrowserWidget * obj,
+ GeditFileBrowserStore *
+ model, GtkTreeIter * iter,
+ gpointer user_data);
+
+struct _GeditFileBrowserWidget
+{
+ GtkVBox parent;
+
+ GeditFileBrowserWidgetPrivate *priv;
+};
+
+struct _GeditFileBrowserWidgetClass
+{
+ GtkVBoxClass parent_class;
+
+ /* Signals */
+ void (*uri_activated) (GeditFileBrowserWidget * widget,
+ gchar const *uri);
+ void (*error) (GeditFileBrowserWidget * widget,
+ guint code,
+ gchar const *message);
+ gboolean (*confirm_delete) (GeditFileBrowserWidget * widget,
+ GeditFileBrowserStore * model,
+ GList *list);
+ gboolean (*confirm_no_trash) (GeditFileBrowserWidget * widget,
+ GList *list);
+};
+
+GType gedit_file_browser_widget_get_type (void) G_GNUC_CONST;
+GType gedit_file_browser_widget_register_type (GTypeModule * module);
+
+GtkWidget *gedit_file_browser_widget_new (const gchar *data_dir);
+
+void gedit_file_browser_widget_show_bookmarks (GeditFileBrowserWidget * obj);
+void gedit_file_browser_widget_show_files (GeditFileBrowserWidget * obj);
+
+void gedit_file_browser_widget_set_root (GeditFileBrowserWidget * obj,
+ gchar const *root,
+ gboolean virtual_root);
+void
+gedit_file_browser_widget_set_root_and_virtual_root (GeditFileBrowserWidget * obj,
+ gchar const *root,
+ gchar const *virtual_root);
+
+gboolean
+gedit_file_browser_widget_get_selected_directory (GeditFileBrowserWidget * obj,
+ GtkTreeIter * iter);
+
+GeditFileBrowserStore *
+gedit_file_browser_widget_get_browser_store (GeditFileBrowserWidget * obj);
+GeditFileBookmarksStore *
+gedit_file_browser_widget_get_bookmarks_store (GeditFileBrowserWidget * obj);
+GeditFileBrowserView *
+gedit_file_browser_widget_get_browser_view (GeditFileBrowserWidget * obj);
+GtkWidget *
+gedit_file_browser_widget_get_filter_entry (GeditFileBrowserWidget * obj);
+
+GtkUIManager *
+gedit_file_browser_widget_get_ui_manager (GeditFileBrowserWidget * obj);
+
+gulong gedit_file_browser_widget_add_filter (GeditFileBrowserWidget * obj,
+ GeditFileBrowserWidgetFilterFunc func,
+ gpointer user_data,
+ GDestroyNotify notify);
+void gedit_file_browser_widget_remove_filter (GeditFileBrowserWidget * obj,
+ gulong id);
+void gedit_file_browser_widget_set_filter_pattern (GeditFileBrowserWidget * obj,
+ gchar const *pattern);
+
+void gedit_file_browser_widget_refresh (GeditFileBrowserWidget * obj);
+void gedit_file_browser_widget_history_back (GeditFileBrowserWidget * obj);
+void gedit_file_browser_widget_history_forward (GeditFileBrowserWidget * obj);
+
+G_END_DECLS
+#endif /* __GEDIT_FILE_BROWSER_WIDGET_H__ */
+
+// ex:ts=8:noet:
diff --git a/plugins/filebrowser/gedit-file-browser.schemas.in b/plugins/filebrowser/gedit-file-browser.schemas.in
new file mode 100755
index 00000000..c80c8eec
--- /dev/null
+++ b/plugins/filebrowser/gedit-file-browser.schemas.in
@@ -0,0 +1,97 @@
+<mateconfschemafile>
+ <schemalist>
+ <schema>
+ <key>/schemas/apps/gedit-2/plugins/filebrowser/on_load/tree_view</key>
+ <applyto>/apps/gedit-2/plugins/filebrowser/on_load/tree_view</applyto>
+ <owner>gedit</owner>
+ <type>bool</type>
+ <default>TRUE</default>
+ <locale name="C">
+ <short>Open With Tree View</short>
+ <long>Open the tree view when the file browser plugin gets loaded instead of the bookmarks view</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gedit-2/plugins/filebrowser/on_load/root</key>
+ <applyto>/apps/gedit-2/plugins/filebrowser/on_load/root</applyto>
+ <owner>gedit</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>File Browser Root Directory</short>
+ <long>The file browser root directory to use when loading the file
+ browser plugin and onload/tree_view is TRUE.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gedit-2/plugins/filebrowser/on_load/virtual_root</key>
+ <applyto>/apps/gedit-2/plugins/filebrowser/on_load/virtual_root</applyto>
+ <owner>gedit</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>File Browser Virtual Root Directory</short>
+ <long>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.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gedit-2/plugins/filebrowser/on_load/enable_remote</key>
+ <applyto>/apps/gedit-2/plugins/filebrowser/on_load/enable_remote</applyto>
+ <owner>gedit</owner>
+ <type>bool</type>
+ <default>FALSE</default>
+ <locale name="C">
+ <short>Enable Restore of Remote Locations</short>
+ <long>Sets whether to enable restoring of remote locations.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gedit-2/plugins/filebrowser/open_at_first_doc</key>
+ <applyto>/apps/gedit-2/plugins/filebrowser/open_at_first_doc</applyto>
+ <owner>gedit</owner>
+ <type>bool</type>
+ <default>TRUE</default>
+ <locale name="C">
+ <short>Set Location to First Document</short>
+ <long>If TRUE the file browser plugin will view the directory of
+ the first opened document given that the file browser hasn't been
+ used yet. (Thus this generally applies to opening a document from
+ the command line or opening it with Caja, etc.)</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gedit-2/plugins/filebrowser/filter_mode</key>
+ <applyto>/apps/gedit-2/plugins/filebrowser/filter_mode</applyto>
+ <owner>gedit</owner>
+ <type>string</type>
+ <default>hidden_and_binary</default>
+ <locale name="C">
+ <short>File Browser Filter Mode</short>
+ <long>This value determines what files get filtered from the file
+ browser. Valid values are: none (filter nothing),
+ hidden (filter hidden files), binary (filter binary files) and
+ hidden_and_binary (filter both hidden and binary files).</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/gedit-2/plugins/filebrowser/filter_pattern</key>
+ <applyto>/apps/gedit-2/plugins/filebrowser/filter_pattern</applyto>
+ <owner>gedit</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>File Browser Filter Pattern</short>
+ <long>The filter pattern to filter the file browser with. This filter
+ works on top of the filter_mode.</long>
+ </locale>
+ </schema>
+ </schemalist>
+</mateconfschemafile>
diff --git a/plugins/modelines/Makefile.am b/plugins/modelines/Makefile.am
new file mode 100755
index 00000000..ddcfccc8
--- /dev/null
+++ b/plugins/modelines/Makefile.am
@@ -0,0 +1,38 @@
+# Modelines Plugin
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+modelinesdir = $(GEDIT_PLUGINS_DATA_DIR)/modelines
+modelines_DATA = \
+ language-mappings
+
+plugin_LTLIBRARIES = libmodelines.la
+
+libmodelines_la_SOURCES = \
+ gedit-modeline-plugin.h \
+ gedit-modeline-plugin.c \
+ modeline-parser.h \
+ modeline-parser.c
+
+libmodelines_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libmodelines_la_LIBADD = $(GEDIT_LIBS)
+
+plugin_in_files = modelines.gedit-plugin.desktop.in
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = \
+ $(plugin_in_files) \
+ $(modelines_DATA)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/modelines/gedit-modeline-plugin.c b/plugins/modelines/gedit-modeline-plugin.c
new file mode 100755
index 00000000..49fc2f69
--- /dev/null
+++ b/plugins/modelines/gedit-modeline-plugin.c
@@ -0,0 +1,248 @@
+/*
+ * gedit-modeline-plugin.c
+ * Emacs, Kate and Vim-style modelines support for gedit.
+ *
+ * Copyright (C) 2005-2007 - Steve Frécinaux <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include "gedit-modeline-plugin.h"
+#include "modeline-parser.h"
+
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-utils.h>
+
+#define WINDOW_DATA_KEY "GeditModelinePluginWindowData"
+#define DOCUMENT_DATA_KEY "GeditModelinePluginDocumentData"
+
+typedef struct
+{
+ gulong tab_added_handler_id;
+ gulong tab_removed_handler_id;
+} WindowData;
+
+typedef struct
+{
+ gulong document_loaded_handler_id;
+ gulong document_saved_handler_id;
+} DocumentData;
+
+static void gedit_modeline_plugin_activate (GeditPlugin *plugin, GeditWindow *window);
+static void gedit_modeline_plugin_deactivate (GeditPlugin *plugin, GeditWindow *window);
+static GObject *gedit_modeline_plugin_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_param);
+static void gedit_modeline_plugin_finalize (GObject *object);
+
+GEDIT_PLUGIN_REGISTER_TYPE(GeditModelinePlugin, gedit_modeline_plugin)
+
+static void
+window_data_free (WindowData *wdata)
+{
+ g_slice_free (WindowData, wdata);
+}
+
+static void
+document_data_free (DocumentData *ddata)
+{
+ g_slice_free (DocumentData, ddata);
+}
+
+static void
+gedit_modeline_plugin_class_init (GeditModelinePluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ object_class->constructor = gedit_modeline_plugin_constructor;
+ object_class->finalize = gedit_modeline_plugin_finalize;
+
+ plugin_class->activate = gedit_modeline_plugin_activate;
+ plugin_class->deactivate = gedit_modeline_plugin_deactivate;
+}
+
+static GObject *
+gedit_modeline_plugin_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_param)
+{
+ GObject *object;
+ gchar *data_dir;
+
+ object = G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->constructor (type,
+ n_construct_properties,
+ construct_param);
+
+ data_dir = gedit_plugin_get_data_dir (GEDIT_PLUGIN (object));
+
+ modeline_parser_init (data_dir);
+
+ g_free (data_dir);
+
+ return object;
+}
+
+static void
+gedit_modeline_plugin_init (GeditModelinePlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin initializing");
+}
+
+static void
+gedit_modeline_plugin_finalize (GObject *object)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin finalizing");
+
+ modeline_parser_shutdown ();
+
+ G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->finalize (object);
+}
+
+static void
+on_document_loaded_or_saved (GeditDocument *document,
+ const GError *error,
+ GtkSourceView *view)
+{
+ modeline_parser_apply_modeline (view);
+}
+
+static void
+connect_handlers (GeditView *view)
+{
+ DocumentData *data;
+ GtkTextBuffer *doc;
+
+ doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ data = g_slice_new (DocumentData);
+
+ data->document_loaded_handler_id =
+ g_signal_connect (doc, "loaded",
+ G_CALLBACK (on_document_loaded_or_saved),
+ view);
+ data->document_saved_handler_id =
+ g_signal_connect (doc, "saved",
+ G_CALLBACK (on_document_loaded_or_saved),
+ view);
+
+ g_object_set_data_full (G_OBJECT (doc), DOCUMENT_DATA_KEY,
+ data, (GDestroyNotify) document_data_free);
+}
+
+static void
+disconnect_handlers (GeditView *view)
+{
+ DocumentData *data;
+ GtkTextBuffer *doc;
+
+ doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ data = g_object_steal_data (G_OBJECT (doc), DOCUMENT_DATA_KEY);
+
+ if (data)
+ {
+ g_signal_handler_disconnect (doc, data->document_loaded_handler_id);
+ g_signal_handler_disconnect (doc, data->document_saved_handler_id);
+
+ document_data_free (data);
+ }
+ else
+ {
+ g_warning ("Modeline handlers not found");
+ }
+}
+
+static void
+on_window_tab_added (GeditWindow *window,
+ GeditTab *tab,
+ gpointer user_data)
+{
+ connect_handlers (gedit_tab_get_view (tab));
+}
+
+static void
+on_window_tab_removed (GeditWindow *window,
+ GeditTab *tab,
+ gpointer user_data)
+{
+ disconnect_handlers (gedit_tab_get_view (tab));
+}
+
+static void
+gedit_modeline_plugin_activate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ WindowData *wdata;
+ GList *views;
+ GList *l;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ views = gedit_window_get_views (window);
+ for (l = views; l != NULL; l = l->next)
+ {
+ connect_handlers (GEDIT_VIEW (l->data));
+ modeline_parser_apply_modeline (GTK_SOURCE_VIEW (l->data));
+ }
+ g_list_free (views);
+
+ wdata = g_slice_new (WindowData);
+
+ wdata->tab_added_handler_id =
+ g_signal_connect (window, "tab-added",
+ G_CALLBACK (on_window_tab_added), NULL);
+
+ wdata->tab_removed_handler_id =
+ g_signal_connect (window, "tab-removed",
+ G_CALLBACK (on_window_tab_removed), NULL);
+
+ g_object_set_data_full (G_OBJECT (window), WINDOW_DATA_KEY,
+ wdata, (GDestroyNotify) window_data_free);
+}
+
+static void
+gedit_modeline_plugin_deactivate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ WindowData *wdata;
+ GList *views;
+ GList *l;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ wdata = g_object_steal_data (G_OBJECT (window), WINDOW_DATA_KEY);
+
+ g_signal_handler_disconnect (window, wdata->tab_added_handler_id);
+ g_signal_handler_disconnect (window, wdata->tab_removed_handler_id);
+
+ window_data_free (wdata);
+
+ views = gedit_window_get_views (window);
+
+ for (l = views; l != NULL; l = l->next)
+ {
+ disconnect_handlers (GEDIT_VIEW (l->data));
+
+ modeline_parser_deactivate (GTK_SOURCE_VIEW (l->data));
+ }
+
+ g_list_free (views);
+}
+
diff --git a/plugins/modelines/gedit-modeline-plugin.h b/plugins/modelines/gedit-modeline-plugin.h
new file mode 100755
index 00000000..92b01e70
--- /dev/null
+++ b/plugins/modelines/gedit-modeline-plugin.h
@@ -0,0 +1,48 @@
+/*
+ * gedit-modeline-plugin.h
+ * Emacs, Kate and Vim-style modelines support for gedit.
+ *
+ * Copyright (C) 2005-2007 - Steve Frécinaux <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GEDIT_MODELINE_PLUGIN_H__
+#define __GEDIT_MODELINE_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_MODELINE_PLUGIN (gedit_modeline_plugin_get_type ())
+#define GEDIT_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePlugin))
+#define GEDIT_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass))
+#define GEDIT_IS_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_MODELINE_PLUGIN))
+#define GEDIT_IS_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_MODELINE_PLUGIN))
+#define GEDIT_MODELINE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass))
+
+/* Private structure type */
+typedef GeditPluginClass GeditModelinePluginClass;
+typedef GeditPlugin GeditModelinePlugin;
+
+GType gedit_modeline_plugin_get_type (void) G_GNUC_CONST;
+
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_MODELINE_PLUGIN_H__ */
diff --git a/plugins/modelines/language-mappings b/plugins/modelines/language-mappings
new file mode 100755
index 00000000..47d00296
--- /dev/null
+++ b/plugins/modelines/language-mappings
@@ -0,0 +1,14 @@
+[vim]
+cs=c-sharp
+docbk=docbook
+javascript=js
+lhaskell=haskell-literate
+spec=rpmspec
+tex=latex
+xhtml=html
+
+[emacs]
+c++=cpp
+
+[kate]
+
diff --git a/plugins/modelines/modeline-parser.c b/plugins/modelines/modeline-parser.c
new file mode 100755
index 00000000..6feafc55
--- /dev/null
+++ b/plugins/modelines/modeline-parser.c
@@ -0,0 +1,852 @@
+/*
+ * modeline-parser.c
+ * Emacs, Kate and Vim-style modelines support for gedit.
+ *
+ * Copyright (C) 2005-2007 - Steve Frécinaux <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <gedit/gedit-language-manager.h>
+#include <gedit/gedit-prefs-manager.h>
+#include <gedit/gedit-debug.h>
+#include "modeline-parser.h"
+
+#define MODELINES_LANGUAGE_MAPPINGS_FILE "language-mappings"
+
+/* base dir to lookup configuration files */
+static gchar *modelines_data_dir;
+
+/* Mappings: language name -> Gedit language ID */
+static GHashTable *vim_languages;
+static GHashTable *emacs_languages;
+static GHashTable *kate_languages;
+
+typedef enum
+{
+ MODELINE_SET_NONE = 0,
+ MODELINE_SET_TAB_WIDTH = 1 << 0,
+ MODELINE_SET_INDENT_WIDTH = 1 << 1,
+ MODELINE_SET_WRAP_MODE = 1 << 2,
+ MODELINE_SET_SHOW_RIGHT_MARGIN = 1 << 3,
+ MODELINE_SET_RIGHT_MARGIN_POSITION = 1 << 4,
+ MODELINE_SET_LANGUAGE = 1 << 5,
+ MODELINE_SET_INSERT_SPACES = 1 << 6
+} ModelineSet;
+
+typedef struct _ModelineOptions
+{
+ gchar *language_id;
+
+ /* these options are similar to the GtkSourceView properties of the
+ * same names.
+ */
+ gboolean insert_spaces;
+ guint tab_width;
+ guint indent_width;
+ GtkWrapMode wrap_mode;
+ gboolean display_right_margin;
+ guint right_margin_position;
+
+ ModelineSet set;
+} ModelineOptions;
+
+#define MODELINE_OPTIONS_DATA_KEY "ModelineOptionsDataKey"
+
+static gboolean
+has_option (ModelineOptions *options,
+ ModelineSet set)
+{
+ return options->set & set;
+}
+
+void
+modeline_parser_init (const gchar *data_dir)
+{
+ modelines_data_dir = g_strdup (data_dir);
+}
+
+void
+modeline_parser_shutdown ()
+{
+ if (vim_languages != NULL)
+ g_hash_table_destroy (vim_languages);
+
+ if (emacs_languages != NULL)
+ g_hash_table_destroy (emacs_languages);
+
+ if (kate_languages != NULL)
+ g_hash_table_destroy (kate_languages);
+
+ vim_languages = NULL;
+ emacs_languages = NULL;
+ kate_languages = NULL;
+
+ g_free (modelines_data_dir);
+}
+
+static GHashTable *
+load_language_mappings_group (GKeyFile *key_file, const gchar *group)
+{
+ GHashTable *table;
+ gchar **keys;
+ gsize length = 0;
+ int i;
+
+ table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ keys = g_key_file_get_keys (key_file, group, &length, NULL);
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "%" G_GSIZE_FORMAT " mappings in group %s",
+ length, group);
+
+ for (i = 0; i < length; i++)
+ {
+ gchar *name = keys[i];
+ gchar *id = g_key_file_get_string (key_file, group, name, NULL);
+ g_hash_table_insert (table, name, id);
+ }
+ g_free (keys);
+
+ return table;
+}
+
+/* lazy loading of language mappings */
+static void
+load_language_mappings (void)
+{
+ gchar *fname;
+ GKeyFile *mappings;
+ GError *error = NULL;
+
+ fname = g_build_filename (modelines_data_dir,
+ MODELINES_LANGUAGE_MAPPINGS_FILE,
+ NULL);
+
+ mappings = g_key_file_new ();
+
+ if (g_key_file_load_from_file (mappings, fname, 0, &error))
+ {
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Loaded language mappings from %s",
+ fname);
+
+ vim_languages = load_language_mappings_group (mappings, "vim");
+ emacs_languages = load_language_mappings_group (mappings, "emacs");
+ kate_languages = load_language_mappings_group (mappings, "kate");
+ }
+ else
+ {
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Failed to loaded language mappings from %s: %s",
+ fname, error->message);
+
+ g_error_free (error);
+ }
+
+ g_key_file_free (mappings);
+ g_free (fname);
+}
+
+static gchar *
+get_language_id (const gchar *language_name, GHashTable *mapping)
+{
+ gchar *name;
+ gchar *language_id;
+
+ name = g_ascii_strdown (language_name, -1);
+
+ language_id = g_hash_table_lookup (mapping, name);
+
+ if (language_id != NULL)
+ {
+ g_free (name);
+ return g_strdup (language_id);
+ }
+ else
+ {
+ /* by default assume that the gtksourcevuew id is the same */
+ return name;
+ }
+}
+
+static gchar *
+get_language_id_vim (const gchar *language_name)
+{
+ if (vim_languages == NULL)
+ load_language_mappings ();
+
+ return get_language_id (language_name, vim_languages);
+}
+
+static gchar *
+get_language_id_emacs (const gchar *language_name)
+{
+ if (emacs_languages == NULL)
+ load_language_mappings ();
+
+ return get_language_id (language_name, emacs_languages);
+}
+
+static gchar *
+get_language_id_kate (const gchar *language_name)
+{
+ if (kate_languages == NULL)
+ load_language_mappings ();
+
+ return get_language_id (language_name, kate_languages);
+}
+
+static gboolean
+skip_whitespaces (gchar **s)
+{
+ while (**s != '\0' && g_ascii_isspace (**s))
+ (*s)++;
+ return **s != '\0';
+}
+
+/* Parse vi(m) modelines.
+ * Vi(m) modelines looks like this:
+ * - first form: [text]{white}{vi:|vim:|ex:}[white]{options}
+ * - second form: [text]{white}{vi:|vim:|ex:}[white]se[t] {options}:[text]
+ * They can happen on the three first or last lines.
+ */
+static gchar *
+parse_vim_modeline (gchar *s,
+ ModelineOptions *options)
+{
+ gboolean in_set = FALSE;
+ gboolean neg;
+ guint intval;
+ GString *key, *value;
+
+ key = g_string_sized_new (8);
+ value = g_string_sized_new (8);
+
+ while (*s != '\0' && !(in_set && *s == ':'))
+ {
+ while (*s != '\0' && (*s == ':' || g_ascii_isspace (*s)))
+ s++;
+
+ if (*s == '\0')
+ break;
+
+ if (strncmp (s, "set ", 4) == 0 ||
+ strncmp (s, "se ", 3) == 0)
+ {
+ s = strchr(s, ' ') + 1;
+ in_set = TRUE;
+ }
+
+ neg = FALSE;
+ if (strncmp (s, "no", 2) == 0)
+ {
+ neg = TRUE;
+ s += 2;
+ }
+
+ g_string_assign (key, "");
+ g_string_assign (value, "");
+
+ while (*s != '\0' && *s != ':' && *s != '=' &&
+ !g_ascii_isspace (*s))
+ {
+ g_string_append_c (key, *s);
+ s++;
+ }
+
+ if (*s == '=')
+ {
+ s++;
+ while (*s != '\0' && *s != ':' &&
+ !g_ascii_isspace (*s))
+ {
+ g_string_append_c (value, *s);
+ s++;
+ }
+ }
+
+ if (strcmp (key->str, "ft") == 0 ||
+ strcmp (key->str, "filetype") == 0)
+ {
+ g_free (options->language_id);
+ options->language_id = get_language_id_vim (value->str);
+
+ options->set |= MODELINE_SET_LANGUAGE;
+ }
+ else if (strcmp (key->str, "et") == 0 ||
+ strcmp (key->str, "expandtab") == 0)
+ {
+ options->insert_spaces = !neg;
+ options->set |= MODELINE_SET_INSERT_SPACES;
+ }
+ else if (strcmp (key->str, "ts") == 0 ||
+ strcmp (key->str, "tabstop") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->tab_width = intval;
+ options->set |= MODELINE_SET_TAB_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "sw") == 0 ||
+ strcmp (key->str, "shiftwidth") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->indent_width = intval;
+ options->set |= MODELINE_SET_INDENT_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "wrap") == 0)
+ {
+ options->wrap_mode = neg ? GTK_WRAP_NONE : GTK_WRAP_WORD;
+
+ options->set |= MODELINE_SET_WRAP_MODE;
+ }
+ else if (strcmp (key->str, "textwidth") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->right_margin_position = intval;
+ options->display_right_margin = TRUE;
+
+ options->set |= MODELINE_SET_SHOW_RIGHT_MARGIN |
+ MODELINE_SET_RIGHT_MARGIN_POSITION;
+
+ }
+ }
+ }
+
+ g_string_free (key, TRUE);
+ g_string_free (value, TRUE);
+
+ return s;
+}
+
+/* Parse emacs modelines.
+ * Emacs modelines looks like this: "-*- key1: value1; key2: value2 -*-"
+ * They can happen on the first line, or on the second one if the first line is
+ * a shebang (#!)
+ * See http://www.delorie.com/gnu/docs/emacs/emacs_486.html
+ */
+static gchar *
+parse_emacs_modeline (gchar *s,
+ ModelineOptions *options)
+{
+ guint intval;
+ GString *key, *value;
+
+ key = g_string_sized_new (8);
+ value = g_string_sized_new (8);
+
+ while (*s != '\0')
+ {
+ while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s)))
+ s++;
+ if (*s == '\0' || strncmp (s, "-*-", 3) == 0)
+ break;
+
+ g_string_assign (key, "");
+ g_string_assign (value, "");
+
+ while (*s != '\0' && *s != ':' && *s != ';' &&
+ !g_ascii_isspace (*s))
+ {
+ g_string_append_c (key, *s);
+ s++;
+ }
+
+ if (!skip_whitespaces (&s))
+ break;
+
+ if (*s != ':')
+ continue;
+ s++;
+
+ if (!skip_whitespaces (&s))
+ break;
+
+ while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s))
+ {
+ g_string_append_c (value, *s);
+ s++;
+ }
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Emacs modeline bit: %s = %s",
+ key->str, value->str);
+
+ /* "Mode" key is case insenstive */
+ if (g_ascii_strcasecmp (key->str, "Mode") == 0)
+ {
+ g_free (options->language_id);
+ options->language_id = get_language_id_emacs (value->str);
+
+ options->set |= MODELINE_SET_LANGUAGE;
+ }
+ else if (strcmp (key->str, "tab-width") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->tab_width = intval;
+ options->set |= MODELINE_SET_TAB_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "indent-offset") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->indent_width = intval;
+ options->set |= MODELINE_SET_INDENT_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "indent-tabs-mode") == 0)
+ {
+ intval = strcmp (value->str, "nil") == 0;
+ options->insert_spaces = intval;
+
+ options->set |= MODELINE_SET_INSERT_SPACES;
+ }
+ else if (strcmp (key->str, "autowrap") == 0)
+ {
+ intval = strcmp (value->str, "nil") != 0;
+ options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE;
+
+ options->set |= MODELINE_SET_WRAP_MODE;
+ }
+ }
+
+ g_string_free (key, TRUE);
+ g_string_free (value, TRUE);
+
+ return *s == '\0' ? s : s + 2;
+}
+
+/*
+ * Parse kate modelines.
+ * Kate modelines are of the form "kate: key1 value1; key2 value2;"
+ * These can happen on the 10 first or 10 last lines of the buffer.
+ * See http://wiki.kate-editor.org/index.php/Modelines
+ */
+static gchar *
+parse_kate_modeline (gchar *s,
+ ModelineOptions *options)
+{
+ guint intval;
+ GString *key, *value;
+
+ key = g_string_sized_new (8);
+ value = g_string_sized_new (8);
+
+ while (*s != '\0')
+ {
+ while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s)))
+ s++;
+ if (*s == '\0')
+ break;
+
+ g_string_assign (key, "");
+ g_string_assign (value, "");
+
+ while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s))
+ {
+ g_string_append_c (key, *s);
+ s++;
+ }
+
+ if (!skip_whitespaces (&s))
+ break;
+ if (*s == ';')
+ continue;
+
+ while (*s != '\0' && *s != ';' &&
+ !g_ascii_isspace (*s))
+ {
+ g_string_append_c (value, *s);
+ s++;
+ }
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Kate modeline bit: %s = %s",
+ key->str, value->str);
+
+ if (strcmp (key->str, "hl") == 0 ||
+ strcmp (key->str, "syntax") == 0)
+ {
+ g_free (options->language_id);
+ options->language_id = get_language_id_kate (value->str);
+
+ options->set |= MODELINE_SET_LANGUAGE;
+ }
+ else if (strcmp (key->str, "tab-width") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->tab_width = intval;
+ options->set |= MODELINE_SET_TAB_WIDTH;
+ }
+ }
+ else if (strcmp (key->str, "indent-width") == 0)
+ {
+ intval = atoi (value->str);
+ if (intval) options->indent_width = intval;
+ }
+ else if (strcmp (key->str, "space-indent") == 0)
+ {
+ intval = strcmp (value->str, "on") == 0 ||
+ strcmp (value->str, "true") == 0 ||
+ strcmp (value->str, "1") == 0;
+
+ options->insert_spaces = intval;
+ options->set |= MODELINE_SET_INSERT_SPACES;
+ }
+ else if (strcmp (key->str, "word-wrap") == 0)
+ {
+ intval = strcmp (value->str, "on") == 0 ||
+ strcmp (value->str, "true") == 0 ||
+ strcmp (value->str, "1") == 0;
+
+ options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE;
+
+ options->set |= MODELINE_SET_WRAP_MODE;
+ }
+ else if (strcmp (key->str, "word-wrap-column") == 0)
+ {
+ intval = atoi (value->str);
+
+ if (intval)
+ {
+ options->right_margin_position = intval;
+ options->display_right_margin = TRUE;
+
+ options->set |= MODELINE_SET_RIGHT_MARGIN_POSITION |
+ MODELINE_SET_SHOW_RIGHT_MARGIN;
+ }
+ }
+ }
+
+ g_string_free (key, TRUE);
+ g_string_free (value, TRUE);
+
+ return s;
+}
+
+/* Scan a line for vi(m)/emacs/kate modelines.
+ * Line numbers are counted starting at one.
+ */
+static void
+parse_modeline (gchar *s,
+ gint line_number,
+ gint line_count,
+ ModelineOptions *options)
+{
+ gchar prev;
+
+ /* look for the beginning of a modeline */
+ for (prev = ' '; (s != NULL) && (*s != '\0'); prev = *(s++))
+ {
+ if (!g_ascii_isspace (prev))
+ continue;
+
+ if ((line_number <= 3 || line_number > line_count - 3) &&
+ (strncmp (s, "ex:", 3) == 0 ||
+ strncmp (s, "vi:", 3) == 0 ||
+ strncmp (s, "vim:", 4) == 0))
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "Vim modeline on line %d", line_number);
+
+ while (*s != ':') s++;
+ s = parse_vim_modeline (s + 1, options);
+ }
+ else if (line_number <= 2 && strncmp (s, "-*-", 3) == 0)
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "Emacs modeline on line %d", line_number);
+
+ s = parse_emacs_modeline (s + 3, options);
+ }
+ else if ((line_number <= 10 || line_number > line_count - 10) &&
+ strncmp (s, "kate:", 5) == 0)
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "Kate modeline on line %d", line_number);
+
+ s = parse_kate_modeline (s + 5, options);
+ }
+ }
+}
+
+static gboolean
+check_previous (GtkSourceView *view,
+ ModelineOptions *previous,
+ ModelineSet set)
+{
+ GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+
+ /* Do not restore default when this is the first time */
+ if (!previous)
+ return FALSE;
+
+ /* Do not restore default when previous was not set */
+ if (!(previous->set & set))
+ return FALSE;
+
+ /* Only restore default when setting has not changed */
+ switch (set)
+ {
+ case MODELINE_SET_INSERT_SPACES:
+ return gtk_source_view_get_insert_spaces_instead_of_tabs (view) ==
+ previous->insert_spaces;
+ break;
+ case MODELINE_SET_TAB_WIDTH:
+ return gtk_source_view_get_tab_width (view) == previous->tab_width;
+ break;
+ case MODELINE_SET_INDENT_WIDTH:
+ return gtk_source_view_get_indent_width (view) == previous->indent_width;
+ break;
+ case MODELINE_SET_WRAP_MODE:
+ return gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view)) ==
+ previous->wrap_mode;
+ break;
+ case MODELINE_SET_RIGHT_MARGIN_POSITION:
+ return gtk_source_view_get_right_margin_position (view) ==
+ previous->right_margin_position;
+ break;
+ case MODELINE_SET_SHOW_RIGHT_MARGIN:
+ return gtk_source_view_get_show_right_margin (view) ==
+ previous->display_right_margin;
+ break;
+ case MODELINE_SET_LANGUAGE:
+ {
+ GtkSourceLanguage *language = gtk_source_buffer_get_language (buffer);
+
+ return (language == NULL && previous->language_id == NULL) ||
+ (language != NULL && g_strcmp0 (gtk_source_language_get_id (language),
+ previous->language_id) == 0);
+ }
+ break;
+ default:
+ return FALSE;
+ break;
+ }
+}
+
+static void
+free_modeline_options (ModelineOptions *options)
+{
+ g_free (options->language_id);
+ g_slice_free (ModelineOptions, options);
+}
+
+void
+modeline_parser_apply_modeline (GtkSourceView *view)
+{
+ ModelineOptions options;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter, liter;
+ gint line_count;
+
+ options.language_id = NULL;
+ options.set = MODELINE_SET_NONE;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+ gtk_text_buffer_get_start_iter (buffer, &iter);
+
+ line_count = gtk_text_buffer_get_line_count (buffer);
+
+ /* Parse the modelines on the 10 first lines... */
+ while ((gtk_text_iter_get_line (&iter) < 10) &&
+ !gtk_text_iter_is_end (&iter))
+ {
+ gchar *line;
+
+ liter = iter;
+ gtk_text_iter_forward_to_line_end (&iter);
+ line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE);
+
+ parse_modeline (line,
+ 1 + gtk_text_iter_get_line (&iter),
+ line_count,
+ &options);
+
+ gtk_text_iter_forward_line (&iter);
+
+ g_free (line);
+ }
+
+ /* ...and on the 10 last ones (modelines are not allowed in between) */
+ if (!gtk_text_iter_is_end (&iter))
+ {
+ gint cur_line;
+ guint remaining_lines;
+
+ /* we are on the 11th line (count from 0) */
+ cur_line = gtk_text_iter_get_line (&iter);
+ /* g_assert (10 == cur_line); */
+
+ remaining_lines = line_count - cur_line - 1;
+
+ if (remaining_lines > 10)
+ {
+ gtk_text_buffer_get_end_iter (buffer, &iter);
+ gtk_text_iter_backward_lines (&iter, 9);
+ }
+ }
+
+ while (!gtk_text_iter_is_end (&iter))
+ {
+ gchar *line;
+
+ liter = iter;
+ gtk_text_iter_forward_to_line_end (&iter);
+ line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE);
+
+ parse_modeline (line,
+ 1 + gtk_text_iter_get_line (&iter),
+ line_count,
+ &options);
+
+ gtk_text_iter_forward_line (&iter);
+
+ g_free (line);
+ }
+
+ /* Try to set language */
+ if (has_option (&options, MODELINE_SET_LANGUAGE) && options.language_id)
+ {
+ GtkSourceLanguageManager *manager;
+ GtkSourceLanguage *language;
+
+ manager = gedit_get_language_manager ();
+ language = gtk_source_language_manager_get_language
+ (manager, options.language_id);
+
+ if (language != NULL)
+ {
+ gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer),
+ language);
+ }
+ }
+
+ ModelineOptions *previous = g_object_get_data (G_OBJECT (buffer),
+ MODELINE_OPTIONS_DATA_KEY);
+
+ /* Apply the options we got from modelines and restore defaults if
+ we set them before */
+ if (has_option (&options, MODELINE_SET_INSERT_SPACES))
+ {
+ gtk_source_view_set_insert_spaces_instead_of_tabs
+ (view, options.insert_spaces);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_INSERT_SPACES))
+ {
+ gtk_source_view_set_insert_spaces_instead_of_tabs
+ (view,
+ gedit_prefs_manager_get_insert_spaces ());
+ }
+
+ if (has_option (&options, MODELINE_SET_TAB_WIDTH))
+ {
+ gtk_source_view_set_tab_width (view, options.tab_width);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_TAB_WIDTH))
+ {
+ gtk_source_view_set_tab_width (view,
+ gedit_prefs_manager_get_tabs_size ());
+ }
+
+ if (has_option (&options, MODELINE_SET_INDENT_WIDTH))
+ {
+ gtk_source_view_set_indent_width (view, options.indent_width);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_INDENT_WIDTH))
+ {
+ gtk_source_view_set_indent_width (view, -1);
+ }
+
+ if (has_option (&options, MODELINE_SET_WRAP_MODE))
+ {
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), options.wrap_mode);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_WRAP_MODE))
+ {
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view),
+ gedit_prefs_manager_get_wrap_mode ());
+ }
+
+ if (has_option (&options, MODELINE_SET_RIGHT_MARGIN_POSITION))
+ {
+ gtk_source_view_set_right_margin_position (view, options.right_margin_position);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_RIGHT_MARGIN_POSITION))
+ {
+ gtk_source_view_set_right_margin_position (view,
+ gedit_prefs_manager_get_right_margin_position ());
+ }
+
+ if (has_option (&options, MODELINE_SET_SHOW_RIGHT_MARGIN))
+ {
+ gtk_source_view_set_show_right_margin (view, options.display_right_margin);
+ }
+ else if (check_previous (view, previous, MODELINE_SET_SHOW_RIGHT_MARGIN))
+ {
+ gtk_source_view_set_show_right_margin (view,
+ gedit_prefs_manager_get_display_right_margin ());
+ }
+
+ if (previous)
+ {
+ *previous = options;
+ previous->language_id = g_strdup (options.language_id);
+ }
+ else
+ {
+ previous = g_slice_new (ModelineOptions);
+ *previous = options;
+ previous->language_id = g_strdup (options.language_id);
+
+ g_object_set_data_full (G_OBJECT (buffer),
+ MODELINE_OPTIONS_DATA_KEY,
+ previous,
+ (GDestroyNotify)free_modeline_options);
+ }
+
+ g_free (options.language_id);
+}
+
+void
+modeline_parser_deactivate (GtkSourceView *view)
+{
+ g_object_set_data (G_OBJECT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))),
+ MODELINE_OPTIONS_DATA_KEY,
+ NULL);
+}
+
+/* vi:ts=8 */
diff --git a/plugins/modelines/modeline-parser.h b/plugins/modelines/modeline-parser.h
new file mode 100755
index 00000000..2e8559e4
--- /dev/null
+++ b/plugins/modelines/modeline-parser.h
@@ -0,0 +1,37 @@
+/*
+ * modelie-parser.h
+ * Emacs, Kate and Vim-style modelines support for gedit.
+ *
+ * Copyright (C) 2005-2007 - Steve Frécinaux <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __MODELINE_PARSER_H__
+#define __MODELINE_PARSER_H__
+
+#include <glib.h>
+#include <gtksourceview/gtksourceview.h>
+
+G_BEGIN_DECLS
+
+void modeline_parser_init (const gchar *data_dir);
+void modeline_parser_shutdown (void);
+void modeline_parser_apply_modeline (GtkSourceView *view);
+void modeline_parser_deactivate (GtkSourceView *view);
+
+G_END_DECLS
+
+#endif /* __MODELINE_PARSER_H__ */
diff --git a/plugins/modelines/modelines.gedit-plugin.desktop.in b/plugins/modelines/modelines.gedit-plugin.desktop.in
new file mode 100755
index 00000000..c72f0199
--- /dev/null
+++ b/plugins/modelines/modelines.gedit-plugin.desktop.in
@@ -0,0 +1,8 @@
+[Gedit Plugin]
+Module=modelines
+IAge=2
+_Name=Modelines
+_Description=Emacs, Kate and Vim-style modelines support for gedit.
+Authors=Steve Frécinaux <[email protected]>
+Copyright=Copyright © 2005 Steve Frécinaux
+Website=http://www.gedit.org
diff --git a/plugins/pythonconsole/Makefile.am b/plugins/pythonconsole/Makefile.am
new file mode 100755
index 00000000..c27227f3
--- /dev/null
+++ b/plugins/pythonconsole/Makefile.am
@@ -0,0 +1,15 @@
+# Python Console Plugin
+SUBDIRS = pythonconsole
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+plugin_in_files = pythonconsole.gedit-plugin.desktop.in
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in b/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in
new file mode 100755
index 00000000..8cc65648
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole.gedit-plugin.desktop.in
@@ -0,0 +1,10 @@
+[Gedit Plugin]
+Loader=python
+Module=pythonconsole
+IAge=2
+_Name=Python Console
+_Description=Interactive Python console standing in the bottom panel
+Icon=mate-mime-text-x-python
+Authors=Steve Frécinaux <[email protected]>
+Copyright=Copyright © 2006 Steve Frécinaux
+Website=http://www.gedit.org
diff --git a/plugins/pythonconsole/pythonconsole/Makefile.am b/plugins/pythonconsole/pythonconsole/Makefile.am
new file mode 100755
index 00000000..7aa91fe9
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/Makefile.am
@@ -0,0 +1,17 @@
+# Python console plugin
+
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/pythonconsole
+plugin_PYTHON = \
+ __init__.py \
+ console.py \
+ config.py
+
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/pythonconsole/ui
+ui_DATA = config.ui
+
+EXTRA_DIST = $(ui_DATA)
+
+CLEANFILES =
+DISTCLEANFILES =
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/pythonconsole/pythonconsole/__init__.py b/plugins/pythonconsole/pythonconsole/__init__.py
new file mode 100755
index 00000000..60f70e9f
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/__init__.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+
+# __init__.py -- plugin object
+#
+# Copyright (C) 2006 - Steve Frécinaux
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py)
+# Copyright (C), 1998 James Henstridge <[email protected]>
+# Copyright (C), 2005 Adam Hooper <[email protected]>
+# Bits from gedit Python Console Plugin
+# Copyrignt (C), 2005 Raphaël Slinckx
+
+import gtk
+import gedit
+
+from console import PythonConsole
+from config import PythonConsoleConfigDialog
+from config import PythonConsoleConfig
+
+PYTHON_ICON = 'mate-mime-text-x-python'
+
+class PythonConsolePlugin(gedit.Plugin):
+ def __init__(self):
+ gedit.Plugin.__init__(self)
+ self.dlg = None
+
+ def activate(self, window):
+ console = PythonConsole(namespace = {'__builtins__' : __builtins__,
+ 'gedit' : gedit,
+ 'window' : window})
+ console.eval('print "You can access the main window through ' \
+ '\'window\' :\\n%s" % window', False)
+ bottom = window.get_bottom_panel()
+ image = gtk.Image()
+ image.set_from_icon_name(PYTHON_ICON, gtk.ICON_SIZE_MENU)
+ bottom.add_item(console, _('Python Console'), image)
+ window.set_data('PythonConsolePluginInfo', console)
+
+ def deactivate(self, window):
+ console = window.get_data("PythonConsolePluginInfo")
+ console.stop()
+ window.set_data("PythonConsolePluginInfo", None)
+ bottom = window.get_bottom_panel()
+ bottom.remove_item(console)
+
+def create_configure_dialog(self):
+
+ if not self.dlg:
+ self.dlg = PythonConsoleConfigDialog(self.get_data_dir())
+
+ dialog = self.dlg.dialog()
+ window = gedit.app_get_default().get_active_window()
+ if window:
+ dialog.set_transient_for(window)
+
+ return dialog
+
+# Here we dynamically insert create_configure_dialog based on if configuration
+# is enabled. This has to be done like this because gedit checks if a plugin
+# is configurable solely on the fact that it has this member defined or not
+if PythonConsoleConfig.enabled():
+ PythonConsolePlugin.create_configure_dialog = create_configure_dialog
+
+# ex:et:ts=4:
diff --git a/plugins/pythonconsole/pythonconsole/config.py b/plugins/pythonconsole/pythonconsole/config.py
new file mode 100755
index 00000000..fed4699b
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/config.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+
+# config.py -- Config dialog
+#
+# Copyright (C) 2008 - B. Clausius
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py)
+# Copyright (C), 1998 James Henstridge <[email protected]>
+# Copyright (C), 2005 Adam Hooper <[email protected]>
+# Bits from gedit Python Console Plugin
+# Copyrignt (C), 2005 Raphaël Slinckx
+
+import os
+import gtk
+
+__all__ = ('PythonConsoleConfig', 'PythonConsoleConfigDialog')
+
+MATECONF_KEY_BASE = '/apps/gedit-2/plugins/pythonconsole'
+MATECONF_KEY_COMMAND_COLOR = MATECONF_KEY_BASE + '/command-color'
+MATECONF_KEY_ERROR_COLOR = MATECONF_KEY_BASE + '/error-color'
+
+DEFAULT_COMMAND_COLOR = '#314e6c' # Blue Shadow
+DEFAULT_ERROR_COLOR = '#990000' # Accent Red Dark
+
+class PythonConsoleConfig(object):
+ try:
+ import mateconf
+ except ImportError:
+ mateconf = None
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def enabled():
+ return PythonConsoleConfig.mateconf != None
+
+ @staticmethod
+ def add_handler(handler):
+ if PythonConsoleConfig.mateconf:
+ PythonConsoleConfig.mateconf.client_get_default().notify_add(MATECONF_KEY_BASE, handler)
+
+ color_command = property(
+ lambda self: self.mateconf_get_str(MATECONF_KEY_COMMAND_COLOR, DEFAULT_COMMAND_COLOR),
+ lambda self, value: self.mateconf_set_str(MATECONF_KEY_COMMAND_COLOR, value))
+
+ color_error = property(
+ lambda self: self.mateconf_get_str(MATECONF_KEY_ERROR_COLOR, DEFAULT_ERROR_COLOR),
+ lambda self, value: self.mateconf_set_str(MATECONF_KEY_ERROR_COLOR, value))
+
+ @staticmethod
+ def mateconf_get_str(key, default=''):
+ if not PythonConsoleConfig.mateconf:
+ return default
+
+ val = PythonConsoleConfig.mateconf.client_get_default().get(key)
+ if val is not None and val.type == mateconf.VALUE_STRING:
+ return val.get_string()
+ else:
+ return default
+
+ @staticmethod
+ def mateconf_set_str(key, value):
+ if not PythonConsoleConfig.mateconf:
+ return
+
+ v = PythonConsoleConfig.mateconf.Value(mateconf.VALUE_STRING)
+ v.set_string(value)
+ PythonConsoleConfig.mateconf.client_get_default().set(key, v)
+
+class PythonConsoleConfigDialog(object):
+
+ def __init__(self, datadir):
+ object.__init__(self)
+ self._dialog = None
+ self._ui_path = os.path.join(datadir, 'ui', 'config.ui')
+ self.config = PythonConsoleConfig()
+
+ def dialog(self):
+ if self._dialog is None:
+ self._ui = gtk.Builder()
+ self._ui.add_from_file(self._ui_path)
+
+ self.set_colorbutton_color(self._ui.get_object('colorbutton-command'),
+ self.config.color_command)
+ self.set_colorbutton_color(self._ui.get_object('colorbutton-error'),
+ self.config.color_error)
+
+ self._ui.connect_signals(self)
+
+ self._dialog = self._ui.get_object('dialog-config')
+ self._dialog.show_all()
+ else:
+ self._dialog.present()
+
+ return self._dialog
+
+ @staticmethod
+ def set_colorbutton_color(colorbutton, value):
+ try:
+ color = gtk.gdk.color_parse(value)
+ except ValueError:
+ pass # Default color in config.ui used
+ else:
+ colorbutton.set_color(color)
+
+ def on_dialog_config_response(self, dialog, response_id):
+ self._dialog.destroy()
+
+ def on_dialog_config_destroy(self, dialog):
+ self._dialog = None
+ self._ui = None
+
+ def on_colorbutton_command_color_set(self, colorbutton):
+ self.config.color_command = colorbutton.get_color().to_string()
+
+ def on_colorbutton_error_color_set(self, colorbutton):
+ self.config.color_error = colorbutton.get_color().to_string()
+
+# ex:et:ts=4:
diff --git a/plugins/pythonconsole/pythonconsole/config.ui b/plugins/pythonconsole/pythonconsole/config.ui
new file mode 100755
index 00000000..4ffe26f6
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/config.ui
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.14"/>
+ <!-- interface-naming-policy toplevel-contextual -->
+ <object class="GtkDialog" id="dialog-config">
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="has_separator">False</property>
+ <signal name="destroy" handler="on_dialog_config_destroy"/>
+ <signal name="response" handler="on_dialog_config_response"/>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="border_width">6</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="column_spacing">6</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label-command">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">C_ommand color:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">colorbutton-command</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label-error">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">_Error color:</property>
+ <property name="use_underline">True</property>
+ <property name="mnemonic_widget">colorbutton-error</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="colorbutton-command">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#31314e4e6c6c</property>
+ <signal name="color_set" handler="on_colorbutton_command_color_set"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="colorbutton-error">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="color">#999900000000</property>
+ <signal name="color_set" handler="on_colorbutton_error_color_set"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label">gtk-close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-7">button1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/pythonconsole/pythonconsole/console.py b/plugins/pythonconsole/pythonconsole/console.py
new file mode 100755
index 00000000..e9d7a331
--- /dev/null
+++ b/plugins/pythonconsole/pythonconsole/console.py
@@ -0,0 +1,370 @@
+# -*- coding: utf-8 -*-
+
+# pythonconsole.py -- Console widget
+#
+# Copyright (C) 2006 - Steve Frécinaux
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# Parts from "Interactive Python-GTK Console" (stolen from epiphany's console.py)
+# Copyright (C), 1998 James Henstridge <[email protected]>
+# Copyright (C), 2005 Adam Hooper <[email protected]>
+# Bits from gedit Python Console Plugin
+# Copyrignt (C), 2005 Raphaël Slinckx
+
+import string
+import sys
+import re
+import traceback
+import gobject
+import gtk
+import pango
+
+from config import PythonConsoleConfig
+
+__all__ = ('PythonConsole', 'OutFile')
+
+class PythonConsole(gtk.ScrolledWindow):
+
+ __gsignals__ = {
+ 'grab-focus' : 'override',
+ }
+
+ def __init__(self, namespace = {}):
+ gtk.ScrolledWindow.__init__(self)
+
+ self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.set_shadow_type(gtk.SHADOW_IN)
+ self.view = gtk.TextView()
+ self.view.modify_font(pango.FontDescription('Monospace'))
+ self.view.set_editable(True)
+ self.view.set_wrap_mode(gtk.WRAP_WORD_CHAR)
+ self.add(self.view)
+ self.view.show()
+
+ buffer = self.view.get_buffer()
+ self.normal = buffer.create_tag("normal")
+ self.error = buffer.create_tag("error")
+ self.command = buffer.create_tag("command")
+
+ PythonConsoleConfig.add_handler(self.apply_preferences)
+ self.apply_preferences()
+
+ self.__spaces_pattern = re.compile(r'^\s+')
+ self.namespace = namespace
+
+ self.block_command = False
+
+ # Init first line
+ buffer.create_mark("input-line", buffer.get_end_iter(), True)
+ buffer.insert(buffer.get_end_iter(), ">>> ")
+ buffer.create_mark("input", buffer.get_end_iter(), True)
+
+ # Init history
+ self.history = ['']
+ self.history_pos = 0
+ self.current_command = ''
+ self.namespace['__history__'] = self.history
+
+ # Set up hooks for standard output.
+ self.stdout = OutFile(self, sys.stdout.fileno(), self.normal)
+ self.stderr = OutFile(self, sys.stderr.fileno(), self.error)
+
+ # Signals
+ self.view.connect("key-press-event", self.__key_press_event_cb)
+ buffer.connect("mark-set", self.__mark_set_cb)
+
+ def do_grab_focus(self):
+ self.view.grab_focus()
+
+ def apply_preferences(self, *args):
+ config = PythonConsoleConfig()
+ self.error.set_property("foreground", config.color_error)
+ self.command.set_property("foreground", config.color_command)
+
+ def stop(self):
+ self.namespace = None
+
+ def __key_press_event_cb(self, view, event):
+ modifier_mask = gtk.accelerator_get_default_mod_mask()
+ event_state = event.state & modifier_mask
+
+ if event.keyval == gtk.keysyms.d and event_state == gtk.gdk.CONTROL_MASK:
+ self.destroy()
+
+ elif event.keyval == gtk.keysyms.Return and event_state == gtk.gdk.CONTROL_MASK:
+ # Get the command
+ buffer = view.get_buffer()
+ inp_mark = buffer.get_mark("input")
+ inp = buffer.get_iter_at_mark(inp_mark)
+ cur = buffer.get_end_iter()
+ line = buffer.get_text(inp, cur)
+ self.current_command = self.current_command + line + "\n"
+ self.history_add(line)
+
+ # Prepare the new line
+ cur = buffer.get_end_iter()
+ buffer.insert(cur, "\n... ")
+ cur = buffer.get_end_iter()
+ buffer.move_mark(inp_mark, cur)
+
+ # Keep indentation of precendent line
+ spaces = re.match(self.__spaces_pattern, line)
+ if spaces is not None:
+ buffer.insert(cur, line[spaces.start() : spaces.end()])
+ cur = buffer.get_end_iter()
+
+ buffer.place_cursor(cur)
+ gobject.idle_add(self.scroll_to_end)
+ return True
+
+ elif event.keyval == gtk.keysyms.Return:
+ # Get the marks
+ buffer = view.get_buffer()
+ lin_mark = buffer.get_mark("input-line")
+ inp_mark = buffer.get_mark("input")
+
+ # Get the command line
+ inp = buffer.get_iter_at_mark(inp_mark)
+ cur = buffer.get_end_iter()
+ line = buffer.get_text(inp, cur)
+ self.current_command = self.current_command + line + "\n"
+ self.history_add(line)
+
+ # Make the line blue
+ lin = buffer.get_iter_at_mark(lin_mark)
+ buffer.apply_tag(self.command, lin, cur)
+ buffer.insert(cur, "\n")
+
+ cur_strip = self.current_command.rstrip()
+
+ if cur_strip.endswith(":") \
+ or (self.current_command[-2:] != "\n\n" and self.block_command):
+ # Unfinished block command
+ self.block_command = True
+ com_mark = "... "
+ elif cur_strip.endswith("\\"):
+ com_mark = "... "
+ else:
+ # Eval the command
+ self.__run(self.current_command)
+ self.current_command = ''
+ self.block_command = False
+ com_mark = ">>> "
+
+ # Prepare the new line
+ cur = buffer.get_end_iter()
+ buffer.move_mark(lin_mark, cur)
+ buffer.insert(cur, com_mark)
+ cur = buffer.get_end_iter()
+ buffer.move_mark(inp_mark, cur)
+ buffer.place_cursor(cur)
+ gobject.idle_add(self.scroll_to_end)
+ return True
+
+ elif event.keyval == gtk.keysyms.KP_Down or event.keyval == gtk.keysyms.Down:
+ # Next entry from history
+ view.emit_stop_by_name("key_press_event")
+ self.history_down()
+ gobject.idle_add(self.scroll_to_end)
+ return True
+
+ elif event.keyval == gtk.keysyms.KP_Up or event.keyval == gtk.keysyms.Up:
+ # Previous entry from history
+ view.emit_stop_by_name("key_press_event")
+ self.history_up()
+ gobject.idle_add(self.scroll_to_end)
+ return True
+
+ elif event.keyval == gtk.keysyms.KP_Left or event.keyval == gtk.keysyms.Left or \
+ event.keyval == gtk.keysyms.BackSpace:
+ buffer = view.get_buffer()
+ inp = buffer.get_iter_at_mark(buffer.get_mark("input"))
+ cur = buffer.get_iter_at_mark(buffer.get_insert())
+ if inp.compare(cur) == 0:
+ if not event_state:
+ buffer.place_cursor(inp)
+ return True
+ return False
+
+ # For the console we enable smart/home end behavior incoditionally
+ # since it is useful when editing python
+
+ elif (event.keyval == gtk.keysyms.KP_Home or event.keyval == gtk.keysyms.Home) and \
+ event_state == event_state & (gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK):
+ # Go to the begin of the command instead of the begin of the line
+ buffer = view.get_buffer()
+ iter = buffer.get_iter_at_mark(buffer.get_mark("input"))
+ ins = buffer.get_iter_at_mark(buffer.get_insert())
+
+ while iter.get_char().isspace():
+ iter.forward_char()
+
+ if iter.equal(ins):
+ iter = buffer.get_iter_at_mark(buffer.get_mark("input"))
+
+ if event_state & gtk.gdk.SHIFT_MASK:
+ buffer.move_mark_by_name("insert", iter)
+ else:
+ buffer.place_cursor(iter)
+ return True
+
+ elif (event.keyval == gtk.keysyms.KP_End or event.keyval == gtk.keysyms.End) and \
+ event_state == event_state & (gtk.gdk.SHIFT_MASK|gtk.gdk.CONTROL_MASK):
+
+ buffer = view.get_buffer()
+ iter = buffer.get_end_iter()
+ ins = buffer.get_iter_at_mark(buffer.get_insert())
+
+ iter.backward_char()
+
+ while iter.get_char().isspace():
+ iter.backward_char()
+
+ iter.forward_char()
+
+ if iter.equal(ins):
+ iter = buffer.get_end_iter()
+
+ if event_state & gtk.gdk.SHIFT_MASK:
+ buffer.move_mark_by_name("insert", iter)
+ else:
+ buffer.place_cursor(iter)
+ return True
+
+ def __mark_set_cb(self, buffer, iter, name):
+ input = buffer.get_iter_at_mark(buffer.get_mark("input"))
+ pos = buffer.get_iter_at_mark(buffer.get_insert())
+ self.view.set_editable(pos.compare(input) != -1)
+
+ def get_command_line(self):
+ buffer = self.view.get_buffer()
+ inp = buffer.get_iter_at_mark(buffer.get_mark("input"))
+ cur = buffer.get_end_iter()
+ return buffer.get_text(inp, cur)
+
+ def set_command_line(self, command):
+ buffer = self.view.get_buffer()
+ mark = buffer.get_mark("input")
+ inp = buffer.get_iter_at_mark(mark)
+ cur = buffer.get_end_iter()
+ buffer.delete(inp, cur)
+ buffer.insert(inp, command)
+ self.view.grab_focus()
+
+ def history_add(self, line):
+ if line.strip() != '':
+ self.history_pos = len(self.history)
+ self.history[self.history_pos - 1] = line
+ self.history.append('')
+
+ def history_up(self):
+ if self.history_pos > 0:
+ self.history[self.history_pos] = self.get_command_line()
+ self.history_pos = self.history_pos - 1
+ self.set_command_line(self.history[self.history_pos])
+
+ def history_down(self):
+ if self.history_pos < len(self.history) - 1:
+ self.history[self.history_pos] = self.get_command_line()
+ self.history_pos = self.history_pos + 1
+ self.set_command_line(self.history[self.history_pos])
+
+ def scroll_to_end(self):
+ iter = self.view.get_buffer().get_end_iter()
+ self.view.scroll_to_iter(iter, 0.0)
+ return False
+
+ def write(self, text, tag = None):
+ buffer = self.view.get_buffer()
+ if tag is None:
+ buffer.insert(buffer.get_end_iter(), text)
+ else:
+ buffer.insert_with_tags(buffer.get_end_iter(), text, tag)
+ gobject.idle_add(self.scroll_to_end)
+
+ def eval(self, command, display_command = False):
+ buffer = self.view.get_buffer()
+ lin = buffer.get_mark("input-line")
+ buffer.delete(buffer.get_iter_at_mark(lin),
+ buffer.get_end_iter())
+
+ if isinstance(command, list) or isinstance(command, tuple):
+ for c in command:
+ if display_command:
+ self.write(">>> " + c + "\n", self.command)
+ self.__run(c)
+ else:
+ if display_command:
+ self.write(">>> " + c + "\n", self.command)
+ self.__run(command)
+
+ cur = buffer.get_end_iter()
+ buffer.move_mark_by_name("input-line", cur)
+ buffer.insert(cur, ">>> ")
+ cur = buffer.get_end_iter()
+ buffer.move_mark_by_name("input", cur)
+ self.view.scroll_to_iter(buffer.get_end_iter(), 0.0)
+
+ def __run(self, command):
+ sys.stdout, self.stdout = self.stdout, sys.stdout
+ sys.stderr, self.stderr = self.stderr, sys.stderr
+
+ # eval and exec are broken in how they deal with utf8-encoded
+ # strings so we have to explicitly decode the command before
+ # passing it along
+ command = command.decode('utf8')
+
+ try:
+ try:
+ r = eval(command, self.namespace, self.namespace)
+ if r is not None:
+ print `r`
+ except SyntaxError:
+ exec command in self.namespace
+ except:
+ if hasattr(sys, 'last_type') and sys.last_type == SystemExit:
+ self.destroy()
+ else:
+ traceback.print_exc()
+
+ sys.stdout, self.stdout = self.stdout, sys.stdout
+ sys.stderr, self.stderr = self.stderr, sys.stderr
+
+ def destroy(self):
+ pass
+ #gtk.ScrolledWindow.destroy(self)
+
+class OutFile:
+ """A fake output file object. It sends output to a TK test widget,
+ and if asked for a file number, returns one set on instance creation"""
+ def __init__(self, console, fn, tag):
+ self.fn = fn
+ self.console = console
+ self.tag = tag
+ def close(self): pass
+ def flush(self): pass
+ def fileno(self): return self.fn
+ def isatty(self): return 0
+ def read(self, a): return ''
+ def readline(self): return ''
+ def readlines(self): return []
+ def write(self, s): self.console.write(s, self.tag)
+ def writelines(self, l): self.console.write(l, self.tag)
+ def seek(self, a): raise IOError, (29, 'Illegal seek')
+ def tell(self): raise IOError, (29, 'Illegal seek')
+ truncate = tell
+
+# ex:et:ts=4:
diff --git a/plugins/quickopen/Makefile.am b/plugins/quickopen/Makefile.am
new file mode 100755
index 00000000..4b5faf00
--- /dev/null
+++ b/plugins/quickopen/Makefile.am
@@ -0,0 +1,15 @@
+# Quick Open Plugin
+SUBDIRS = quickopen
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+plugin_in_files = quickopen.gedit-plugin.desktop.in
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/quickopen/quickopen.gedit-plugin.desktop.in b/plugins/quickopen/quickopen.gedit-plugin.desktop.in
new file mode 100755
index 00000000..40f7f040
--- /dev/null
+++ b/plugins/quickopen/quickopen.gedit-plugin.desktop.in
@@ -0,0 +1,10 @@
+[Gedit Plugin]
+Loader=python
+Module=quickopen
+IAge=2
+_Name=Quick Open
+_Description=Quickly open files
+Icon=gtk-open
+Authors=Jesse van den Kieboom <[email protected]>
+Copyright=Copyright © 2009 Jesse van den Kieboom
+Website=http://www.gedit.org
diff --git a/plugins/quickopen/quickopen/Makefile.am b/plugins/quickopen/quickopen/Makefile.am
new file mode 100755
index 00000000..88882fdf
--- /dev/null
+++ b/plugins/quickopen/quickopen/Makefile.am
@@ -0,0 +1,13 @@
+# Quick Open Plugin
+
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/quickopen
+plugin_PYTHON = \
+ __init__.py \
+ popup.py \
+ virtualdirs.py \
+ windowhelper.py
+
+CLEANFILES =
+DISTCLEANFILES =
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/quickopen/quickopen/__init__.py b/plugins/quickopen/quickopen/__init__.py
new file mode 100755
index 00000000..a41c9400
--- /dev/null
+++ b/plugins/quickopen/quickopen/__init__.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 - Jesse van den Kieboom
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gedit
+from windowhelper import WindowHelper
+
+class QuickOpenPlugin(gedit.Plugin):
+ def __init__(self):
+ gedit.Plugin.__init__(self)
+
+ self._popup_size = (450, 300)
+ self._helpers = {}
+
+ def activate(self, window):
+ self._helpers[window] = WindowHelper(window, self)
+
+ def deactivate(self, window):
+ self._helpers[window].deactivate()
+ del self._helpers[window]
+
+ def update_ui(self, window):
+ self._helpers[window].update_ui()
+
+ def get_popup_size(self):
+ return self._popup_size
+
+ def set_popup_size(self, size):
+ self._popup_size = size
+
+# ex:ts=8:et:
diff --git a/plugins/quickopen/quickopen/popup.py b/plugins/quickopen/quickopen/popup.py
new file mode 100755
index 00000000..a80caf31
--- /dev/null
+++ b/plugins/quickopen/quickopen/popup.py
@@ -0,0 +1,534 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 - Jesse van den Kieboom
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+import gtk.gdk
+import gobject
+import os
+import gio
+import pango
+import glib
+import fnmatch
+import gedit
+import xml.sax.saxutils
+from virtualdirs import VirtualDirectory
+
+class Popup(gtk.Dialog):
+ def __init__(self, window, paths, handler):
+ gtk.Dialog.__init__(self,
+ title=_('Quick Open'),
+ parent=window,
+ flags=gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR | gtk.DIALOG_MODAL)
+
+ self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ self._open_button = self.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT)
+
+ self._handler = handler
+ self._build_ui()
+
+ self._dirs = []
+ self._cache = {}
+ self._theme = None
+ self._cursor = None
+ self._shift_start = None
+
+ accel_group = gtk.AccelGroup()
+ accel_group.connect_group(gtk.keysyms.l, gtk.gdk.CONTROL_MASK, 0, self.on_focus_entry)
+
+ self.add_accel_group(accel_group)
+
+ unique = []
+
+ for path in paths:
+ if not path.get_uri() in unique:
+ self._dirs.append(path)
+ unique.append(path.get_uri())
+
+ def _build_ui(self):
+ vbox = self.get_content_area()
+ vbox.set_spacing(3)
+
+ self._entry = gtk.Entry()
+
+ self._entry.connect('changed', self.on_changed)
+ self._entry.connect('key-press-event', self.on_key_press_event)
+
+ sw = gtk.ScrolledWindow(None, None)
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ sw.set_shadow_type(gtk.SHADOW_OUT)
+
+ tv = gtk.TreeView()
+ tv.set_headers_visible(False)
+
+ self._store = gtk.ListStore(gio.Icon, str, object, int)
+ tv.set_model(self._store)
+
+ self._treeview = tv
+ tv.connect('row-activated', self.on_row_activated)
+
+ renderer = gtk.CellRendererPixbuf()
+ column = gtk.TreeViewColumn()
+ column.pack_start(renderer, False)
+ column.set_attributes(renderer, gicon=0)
+
+ renderer = gtk.CellRendererText()
+ column.pack_start(renderer, True)
+ column.set_attributes(renderer, markup=1)
+
+ column.set_cell_data_func(renderer, self.on_cell_data_cb)
+
+ tv.append_column(column)
+ sw.add(tv)
+
+ selection = tv.get_selection()
+ selection.connect('changed', self.on_selection_changed)
+ selection.set_mode(gtk.SELECTION_MULTIPLE)
+
+ vbox.pack_start(self._entry, False, False, 0)
+ vbox.pack_start(sw, True, True, 0)
+
+ lbl = gtk.Label()
+ lbl.set_alignment(0, 0.5)
+ lbl.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
+ self._info_label = lbl
+
+ vbox.pack_start(lbl, False, False, 0)
+
+ # Initial selection
+ self.on_selection_changed(tv.get_selection())
+ vbox.show_all()
+
+ def on_cell_data_cb(self, column, cell, model, piter):
+ path = model.get_path(piter)
+
+ if self._cursor and path == self._cursor.get_path():
+ style = self._treeview.get_style()
+ bg = style.bg[gtk.STATE_PRELIGHT]
+
+ cell.set_property('cell-background-gdk', bg)
+ cell.set_property('style', pango.STYLE_ITALIC)
+ else:
+ cell.set_property('cell-background-set', False)
+ cell.set_property('style-set', False)
+
+ def _icon_from_stock(self, stock):
+ theme = gtk.icon_theme_get_default()
+ size = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
+ pixbuf = theme.load_icon(stock, size[0], gtk.ICON_LOOKUP_USE_BUILTIN)
+
+ return pixbuf
+
+ def _list_dir(self, gfile):
+ entries = []
+
+ try:
+ entries = gfile.enumerate_children("standard::*")
+ except glib.GError:
+ pass
+
+ children = []
+
+ for entry in entries:
+ if isinstance(gfile, VirtualDirectory):
+ child, entry = entry
+ else:
+ child = gfile.get_child(entry.get_name())
+
+ children.append((child, entry.get_name(), entry.get_file_type(), entry.get_icon()))
+
+ return children
+
+ def _compare_entries(self, a, b, lpart):
+ if lpart in a:
+ if lpart in b:
+ return cmp(a.index(lpart), b.index(lpart))
+ else:
+ return -1
+ elif lpart in b:
+ return 1
+ else:
+ return 0
+
+ def _match_glob(self, s, glob):
+ if glob:
+ glob += '*'
+
+ return fnmatch.fnmatch(s, glob)
+
+ def do_search_dir(self, parts, d):
+ if not parts or not d:
+ return []
+
+ if not d in self._cache:
+ entries = self._list_dir(d)
+ entries.sort(lambda x, y: cmp(x[1].lower(), y[1].lower()))
+
+ self._cache[d] = entries
+ else:
+ entries = self._cache[d]
+
+ found = []
+ newdirs = []
+
+ lpart = parts[0].lower()
+
+ for entry in entries:
+ if not entry:
+ continue
+
+ lentry = entry[1].lower()
+
+ if not lpart or lpart in lentry or self._match_glob(lentry, lpart):
+ if entry[2] == gio.FILE_TYPE_DIRECTORY:
+ if len(parts) > 1:
+ newdirs.append(entry[0])
+ else:
+ found.append(entry)
+ elif entry[2] == gio.FILE_TYPE_REGULAR and \
+ (not lpart or len(parts) == 1):
+ found.append(entry)
+
+ found.sort(lambda a, b: self._compare_entries(a[1].lower(), b[1].lower(), lpart))
+
+ if lpart == '..':
+ newdirs.append(d.get_parent())
+
+ for dd in newdirs:
+ found.extend(self.do_search_dir(parts[1:], dd))
+
+ return found
+
+ def _replace_insensitive(self, s, find, rep):
+ out = ''
+ l = s.lower()
+ find = find.lower()
+ last = 0
+
+ if len(find) == 0:
+ return xml.sax.saxutils.escape(s)
+
+ while True:
+ m = l.find(find, last)
+
+ if m == -1:
+ break
+ else:
+ out += xml.sax.saxutils.escape(s[last:m]) + rep % (xml.sax.saxutils.escape(s[m:m + len(find)]),)
+ last = m + len(find)
+
+ return out + xml.sax.saxutils.escape(s[last:])
+
+
+ def make_markup(self, parts, path):
+ out = []
+
+ for i in range(0, len(parts)):
+ out.append(self._replace_insensitive(path[i], parts[i], "<b>%s</b>"))
+
+ return os.sep.join(out)
+
+ def _get_icon(self, f):
+ query = f.query_info(gio.FILE_ATTRIBUTE_STANDARD_ICON)
+
+ if not query:
+ return None
+ else:
+ return query.get_icon()
+
+ def _make_parts(self, parent, child, pp):
+ parts = []
+
+ # We went from parent, to child, using pp
+ idx = len(pp) - 1
+
+ while idx >= 0:
+ if pp[idx] == '..':
+ parts.insert(0, '..')
+ else:
+ parts.insert(0, child.get_basename())
+ child = child.get_parent()
+
+ idx -= 1
+
+ return parts
+
+ def normalize_relative(self, parts):
+ if not parts:
+ return []
+
+ out = self.normalize_relative(parts[:-1])
+
+ if parts[-1] == '..':
+ if not out or (out[-1] == '..') or len(out) == 1:
+ out.append('..')
+ else:
+ del out[-1]
+ else:
+ out.append(parts[-1])
+
+ return out
+
+ def _append_to_store(self, item):
+ if not item in self._stored_items:
+ self._store.append(item)
+ self._stored_items[item] = True
+
+ def _clear_store(self):
+ self._store.clear()
+ self._stored_items = {}
+
+ def _show_virtuals(self):
+ for d in self._dirs:
+ if isinstance(d, VirtualDirectory):
+ for entry in d.enumerate_children("standard::*"):
+ self._append_to_store((entry[1].get_icon(), xml.sax.saxutils.escape(entry[1].get_name()), entry[0], entry[1].get_file_type()))
+
+ def _remove_cursor(self):
+ if self._cursor:
+ path = self._cursor.get_path()
+ self._cursor = None
+
+ self._store.row_changed(path, self._store.get_iter(path))
+
+ def do_search(self):
+ self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+ self._remove_cursor()
+
+ text = self._entry.get_text().strip()
+ self._clear_store()
+
+ if text == '':
+ self._show_virtuals()
+ else:
+ parts = self.normalize_relative(text.split(os.sep))
+ files = []
+
+ for d in self._dirs:
+ for entry in self.do_search_dir(parts, d):
+ pathparts = self._make_parts(d, entry[0], parts)
+ self._append_to_store((entry[3], self.make_markup(parts, pathparts), entry[0], entry[2]))
+
+ piter = self._store.get_iter_first()
+
+ if piter:
+ self._treeview.get_selection().select_path(self._store.get_path(piter))
+
+ self.window.set_cursor(None)
+
+ def do_show(self):
+ gtk.Window.do_show(self)
+
+ self._entry.grab_focus()
+ self._entry.set_text("")
+
+ self.do_search()
+
+ def on_changed(self, editable):
+ self.do_search()
+ self.on_selection_changed(self._treeview.get_selection())
+
+ def _shift_extend(self, towhere):
+ selection = self._treeview.get_selection()
+
+ if not self._shift_start:
+ model, rows = selection.get_selected_rows()
+ start = rows[0]
+
+ self._shift_start = gtk.TreeRowReference(self._store, start)
+ else:
+ start = self._shift_start.get_path()
+
+ selection.unselect_all()
+ selection.select_range(start, towhere)
+
+ def _select_index(self, idx, hasctrl, hasshift):
+ path = (idx,)
+
+ if not (hasctrl or hasshift):
+ self._treeview.get_selection().unselect_all()
+
+ if hasshift:
+ self._shift_extend(path)
+ else:
+ self._shift_start = None
+
+ if not hasctrl:
+ self._treeview.get_selection().select_path(path)
+
+ self._treeview.scroll_to_cell(path, None, True, 0.5, 0)
+ self._remove_cursor()
+
+ if hasctrl or hasshift:
+ self._cursor = gtk.TreeRowReference(self._store, path)
+
+ piter = self._store.get_iter(path)
+ self._store.row_changed(path, piter)
+
+ def _move_selection(self, howmany, hasctrl, hasshift):
+ num = self._store.iter_n_children(None)
+
+ if num == 0:
+ return True
+
+ # Test for cursor
+ path = None
+
+ if self._cursor:
+ path = self._cursor.get_path()
+ else:
+ model, rows = self._treeview.get_selection().get_selected_rows()
+
+ if len(rows) == 1:
+ path = rows[0]
+
+ if not path:
+ if howmany > 0:
+ self._select_index(0, hasctrl, hasshift)
+ else:
+ self._select_index(num - 1, hasctrl, hasshift)
+ else:
+ idx = path[0]
+
+ if idx + howmany < 0:
+ self._select_index(0, hasctrl, hasshift)
+ elif idx + howmany >= num:
+ self._select_index(num - 1, hasctrl, hasshift)
+ else:
+ self._select_index(idx + howmany, hasctrl, hasshift)
+
+ return True
+
+ def _direct_file(self):
+ uri = self._entry.get_text()
+ gfile = None
+
+ if gedit.utils.uri_is_valid(uri):
+ gfile = gio.File(uri)
+ elif os.path.isabs(uri):
+ f = gio.File(uri)
+
+ if f.query_exists():
+ gfile = f
+
+ return gfile
+
+ def _activate(self):
+ model, rows = self._treeview.get_selection().get_selected_rows()
+ ret = True
+
+ for row in rows:
+ s = model.get_iter(row)
+ info = model.get(s, 2, 3)
+
+ if info[1] != gio.FILE_TYPE_DIRECTORY:
+ ret = ret and self._handler(info[0])
+ else:
+ text = self._entry.get_text()
+
+ for i in range(len(text) - 1, -1, -1):
+ if text[i] == os.sep:
+ break
+
+ self._entry.set_text(os.path.join(text[:i], os.path.basename(info[0].get_uri())) + os.sep)
+ self._entry.set_position(-1)
+ self._entry.grab_focus()
+ return True
+
+ if rows and ret:
+ self.destroy()
+
+ if not rows:
+ gfile = self._direct_file()
+
+ if gfile and self._handler(gfile):
+ self.destroy()
+ else:
+ ret = False
+ else:
+ ret = False
+
+ return ret
+
+ def toggle_cursor(self):
+ if not self._cursor:
+ return
+
+ path = self._cursor.get_path()
+ selection = self._treeview.get_selection()
+
+ if selection.path_is_selected(path):
+ selection.unselect_path(path)
+ else:
+ selection.select_path(path)
+
+ def on_key_press_event(self, widget, event):
+ move_mapping = {
+ gtk.keysyms.Down: 1,
+ gtk.keysyms.Up: -1,
+ gtk.keysyms.Page_Down: 5,
+ gtk.keysyms.Page_Up: -5
+ }
+
+ if event.keyval == gtk.keysyms.Escape:
+ self.destroy()
+ return True
+ elif event.keyval in move_mapping:
+ return self._move_selection(move_mapping[event.keyval], event.state & gtk.gdk.CONTROL_MASK, event.state & gtk.gdk.SHIFT_MASK)
+ elif event.keyval in [gtk.keysyms.Return, gtk.keysyms.KP_Enter, gtk.keysyms.Tab, gtk.keysyms.ISO_Left_Tab]:
+ return self._activate()
+ elif event.keyval == gtk.keysyms.space and event.state & gtk.gdk.CONTROL_MASK:
+ self.toggle_cursor()
+
+ return False
+
+ def on_row_activated(self, view, path, column):
+ self._activate()
+
+ def do_response(self, response):
+ if response != gtk.RESPONSE_ACCEPT or not self._activate():
+ self.destroy()
+
+ def on_selection_changed(self, selection):
+ model, rows = selection.get_selected_rows()
+
+ gfile = None
+ fname = None
+
+ if not rows:
+ gfile = self._direct_file()
+ elif len(rows) == 1:
+ gfile = model.get(model.get_iter(rows[0]), 2)[0]
+ else:
+ fname = ''
+
+ if gfile:
+ if gfile.is_native():
+ fname = xml.sax.saxutils.escape(gfile.get_path())
+ else:
+ fname = xml.sax.saxutils.escape(gfile.get_uri())
+
+ self._open_button.set_sensitive(fname != None)
+ self._info_label.set_markup(fname or '')
+
+ def on_focus_entry(self, group, accel, keyval, modifier):
+ self._entry.grab_focus()
+
+gobject.type_register(Popup)
+
+# ex:ts=8:et:
diff --git a/plugins/quickopen/quickopen/virtualdirs.py b/plugins/quickopen/quickopen/virtualdirs.py
new file mode 100755
index 00000000..ef0b8dc4
--- /dev/null
+++ b/plugins/quickopen/quickopen/virtualdirs.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 - Jesse van den Kieboom
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gtk
+import gio
+
+class VirtualDirectory:
+ def __init__(self, name):
+ self._name = name
+ self._children = []
+
+ def get_uri(self):
+ return 'virtual://' + self._name
+
+ def get_parent(self):
+ return None
+
+ def enumerate_children(self, attr):
+ return self._children
+
+ def append(self, child):
+ if not child.is_native():
+ return
+
+ try:
+ info = child.query_info("standard::*")
+
+ if info:
+ self._children.append((child, info))
+ except:
+ pass
+
+class RecentDocumentsDirectory(VirtualDirectory):
+ def __init__(self, maxitems=10, screen=None):
+ VirtualDirectory.__init__(self, 'recent')
+
+ self._maxitems = maxitems
+ self.fill(screen)
+
+ def fill(self, screen):
+ if screen:
+ manager = gtk.recent_manager_get_for_screen(screen)
+ else:
+ manager = gtk.recent_manager_get_default()
+
+ items = manager.get_items()
+ items.sort(lambda a, b: cmp(b.get_visited(), a.get_visited()))
+
+ added = 0
+
+ for item in items:
+ if item.has_group('gedit'):
+ self.append(gio.File(item.get_uri()))
+ added += 1
+
+ if added >= self._maxitems:
+ break
+
+class CurrentDocumentsDirectory(VirtualDirectory):
+ def __init__(self, window):
+ VirtualDirectory.__init__(self, 'documents')
+
+ self.fill(window)
+
+ def fill(self, window):
+ for doc in window.get_documents():
+ location = doc.get_location()
+ if location:
+ self.append(location)
+
+# ex:ts=8:et:
diff --git a/plugins/quickopen/quickopen/windowhelper.py b/plugins/quickopen/quickopen/windowhelper.py
new file mode 100755
index 00000000..70ea26f0
--- /dev/null
+++ b/plugins/quickopen/quickopen/windowhelper.py
@@ -0,0 +1,198 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2009 - Jesse van den Kieboom
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gedit
+import gtk
+from popup import Popup
+import os
+import gedit.commands
+import gio
+import glib
+from virtualdirs import RecentDocumentsDirectory
+from virtualdirs import CurrentDocumentsDirectory
+
+ui_str = """<ui>
+ <menubar name="MenuBar">
+ <menu name="FileMenu" action="File">
+ <placeholder name="FileOps_2">
+ <menuitem name="QuickOpen" action="QuickOpen"/>
+ </placeholder>
+ </menu>
+ </menubar>
+</ui>
+"""
+
+class WindowHelper:
+ def __init__(self, window, plugin):
+ self._window = window
+ self._plugin = plugin
+
+ self._popup = None
+ self._install_menu()
+
+ def deactivate(self):
+ self._uninstall_menu()
+ self._window = None
+ self._plugin = None
+
+ def update_ui(self):
+ pass
+
+ def _uninstall_menu(self):
+ manager = self._window.get_ui_manager()
+
+ manager.remove_ui(self._ui_id)
+ manager.remove_action_group(self._action_group)
+
+ manager.ensure_update()
+
+ def _install_menu(self):
+ manager = self._window.get_ui_manager()
+ self._action_group = gtk.ActionGroup("GeditQuickOpenPluginActions")
+ self._action_group.add_actions([
+ ("QuickOpen", gtk.STOCK_OPEN, _("Quick open"),
+ '<Ctrl><Alt>O', _("Quickly open documents"),
+ self.on_quick_open_activate)
+ ])
+
+ manager.insert_action_group(self._action_group, -1)
+ self._ui_id = manager.add_ui_from_string(ui_str)
+
+ def _create_popup(self):
+ paths = []
+
+ # Open documents
+ paths.append(CurrentDocumentsDirectory(self._window))
+
+ doc = self._window.get_active_document()
+
+ # Current document directory
+ if doc and doc.is_local():
+ gfile = doc.get_location()
+ paths.append(gfile.get_parent())
+
+ # File browser root directory
+ if gedit.version[0] > 2 or (gedit.version[0] == 2 and (gedit.version[1] > 26 or (gedit.version[1] == 26 and gedit.version[2] >= 2))):
+ bus = self._window.get_message_bus()
+
+ try:
+ msg = bus.send_sync('/plugins/filebrowser', 'get_root')
+
+ if msg:
+ uri = msg.get_value('uri')
+
+ if uri:
+ gfile = gio.File(uri)
+
+ if gfile.is_native():
+ paths.append(gfile)
+
+ except StandardError:
+ pass
+
+ # Recent documents
+ paths.append(RecentDocumentsDirectory(screen=self._window.get_screen()))
+
+ # Local bookmarks
+ for path in self._local_bookmarks():
+ paths.append(path)
+
+ # Desktop directory
+ desktopdir = self._desktop_dir()
+
+ if desktopdir:
+ paths.append(gio.File(desktopdir))
+
+ # Home directory
+ paths.append(gio.File(os.path.expanduser('~')))
+
+ self._popup = Popup(self._window, paths, self.on_activated)
+
+ self._popup.set_default_size(*self._plugin.get_popup_size())
+ self._popup.set_transient_for(self._window)
+ self._popup.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+
+ self._window.get_group().add_window(self._popup)
+
+ self._popup.connect('destroy', self.on_popup_destroy)
+
+ def _local_bookmarks(self):
+ filename = os.path.expanduser('~/.gtk-bookmarks')
+
+ if not os.path.isfile(filename):
+ return []
+
+ paths = []
+
+ for line in file(filename, 'r').xreadlines():
+ uri = line.strip().split(" ")[0]
+ f = gio.File(uri)
+
+ if f.is_native():
+ try:
+ info = f.query_info("standard::type")
+
+ if info and info.get_file_type() == gio.FILE_TYPE_DIRECTORY:
+ paths.append(f)
+ except glib.GError:
+ pass
+
+ return paths
+
+ def _desktop_dir(self):
+ config = os.getenv('XDG_CONFIG_HOME')
+
+ if not config:
+ config = os.path.expanduser('~/.config')
+
+ config = os.path.join(config, 'user-dirs.dirs')
+ desktopdir = None
+
+ if os.path.isfile(config):
+ for line in file(config, 'r').xreadlines():
+ line = line.strip()
+
+ if line.startswith('XDG_DESKTOP_DIR'):
+ parts = line.split('=', 1)
+ desktopdir = os.path.expandvars(parts[1].strip('"').strip("'"))
+ break
+
+ if not desktopdir:
+ desktopdir = os.path.expanduser('~/Desktop')
+
+ return desktopdir
+
+ # Callbacks
+ def on_quick_open_activate(self, action):
+ if not self._popup:
+ self._create_popup()
+
+ self._popup.show()
+
+ def on_popup_destroy(self, popup):
+ alloc = popup.get_allocation()
+ self._plugin.set_popup_size((alloc.width, alloc.height))
+
+ self._popup = None
+
+ def on_activated(self, gfile):
+ gedit.commands.load_uri(self._window, gfile.get_uri(), None, -1)
+ return True
+
+# ex:ts=8:et:
diff --git a/plugins/snippets/Makefile.am b/plugins/snippets/Makefile.am
new file mode 100755
index 00000000..06f0009b
--- /dev/null
+++ b/plugins/snippets/Makefile.am
@@ -0,0 +1,15 @@
+# Python snippets plugin
+SUBDIRS = snippets data
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+plugin_in_files = snippets.gedit-plugin.desktop.in
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/snippets/data/Makefile.am b/plugins/snippets/data/Makefile.am
new file mode 100755
index 00000000..8ec40419
--- /dev/null
+++ b/plugins/snippets/data/Makefile.am
@@ -0,0 +1,33 @@
+# Python snippets plugin
+SUBDIRS = lang
+
+snippets_DATA = \
+ css.xml \
+ c.xml \
+ cpp.xml \
+ chdr.xml \
+ docbook.xml \
+ fortran.xml \
+ global.xml \
+ haskell.xml \
+ html.xml \
+ idl.xml \
+ javascript.xml \
+ java.xml \
+ latex.xml \
+ mallard.xml \
+ perl.xml \
+ php.xml \
+ python.xml \
+ ruby.xml \
+ sh.xml \
+ snippets.xml \
+ tcl.xml \
+ xml.xml \
+ xslt.xml
+
+snippetsdir = $(GEDIT_PLUGINS_DATA_DIR)/snippets
+
+EXTRA_DIST = $(snippets_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/snippets/data/c.xml b/plugins/snippets/data/c.xml
new file mode 100755
index 00000000..61171cb8
--- /dev/null
+++ b/plugins/snippets/data/c.xml
@@ -0,0 +1,283 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="C">
+ <snippet id="gpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<program name>}
+ *
+ * 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 '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ${2}; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+$0]]></text>
+ <tag>gpl</tag>
+ <description>GPL License</description>
+ </snippet>
+ <snippet id="lgpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<library name>}
+ *
+ * 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 '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+$0]]></text>
+ <tag>lgpl</tag>
+ <description>LGPL License</description>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do
+{
+ $0
+} while ($1);]]></text>
+ <tag>do</tag>
+ <description>do .. while</description>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for (${1:i} = ${2:0}; ${1:i} < ${3:count}; ${1:i} += ${4:1})
+{
+ $0
+}]]></text>
+ <tag>for</tag>
+ <description>for loop</description>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while (${1:condition})
+{
+ $0
+}]]></text>
+ <tag>while</tag>
+ <description>while loop</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if (${1:condition})
+{
+ $0
+}]]></text>
+ <tag>if</tag>
+ <description>if</description>
+ </snippet>
+ <snippet id="elif">
+ <text><![CDATA[else if (${1:condition})
+{
+ $0
+}]]></text>
+ <tag>elif</tag>
+ <description>else if</description>
+ </snippet>
+ <snippet id="else">
+ <text><![CDATA[else
+{
+ $0
+}]]></text>
+ <tag>else</tag>
+ <description>else</description>
+ </snippet>
+ <snippet id="Inc">
+ <text><![CDATA[#include <${1:file}.h>
+$0]]></text>
+ <tag>Inc</tag>
+ <description>#include &lt;..&gt;</description>
+ </snippet>
+ <snippet id="inc">
+ <text><![CDATA[#include "${1:file}.h"
+$0]]></text>
+ <tag>inc</tag>
+ <description>#include ".."</description>
+ </snippet>
+ <snippet id="main">
+ <text><![CDATA[int
+main (int argc, char *argv[])
+{
+ $0
+ return 0;
+}]]></text>
+ <tag>main</tag>
+ <description>main</description>
+ </snippet>
+ <snippet id="struct">
+ <text><![CDATA[struct ${1:name}
+{
+ ${0:/* data */}
+};]]></text>
+ <tag>struct</tag>
+ <description>struct</description>
+ </snippet>
+ <snippet id="endif">
+ <text><![CDATA[#endif
+$0]]></text>
+ <description>#endif</description>
+ <accelerator><![CDATA[<Control><Alt>period]]></accelerator>
+ </snippet>
+ <snippet id="td">
+ <text><![CDATA[typedef ${1:newtype} ${2:type};
+$0]]></text>
+ <tag>td</tag>
+ <description>typedef</description>
+ </snippet>
+ <snippet id="gobject">
+ <text><![CDATA[#include "$1.h"
+$<
+global camel_str,low_str, type_str, is_str, up_str
+components = $1.split('-')
+low_str = '_'.join(components).lower()
+up_str = '_'.join(components).upper()
+type_str = '_'.join([components[0], 'TYPE'] + components[1:]).upper()
+is_str = '_'.join([components[0], 'IS'] + components[1:]).upper()
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+>
+
+#define $<[1]: return up_str >_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), $<[1]: return type_str >, $<[1]: return camel_str >Private))
+
+struct _$<[1]: return camel_str >Private
+{
+};
+
+G_DEFINE_TYPE ($<[1]: return camel_str >, $<[1]: return low_str >, ${2:G_TYPE_OBJECT})
+
+static void
+$<[1]: return low_str>_finalize (GObject *object)
+{
+ G_OBJECT_CLASS ($<[1]: return low_str >_parent_class)->finalize (object);
+}
+
+static void
+$<[1]: return low_str >_class_init ($<[1]: return camel_str >Class *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = $<[1]: return low_str >_finalize;
+
+ g_type_class_add_private (object_class, sizeof ($<[1]: return camel_str >Private));
+}
+
+static void
+$<[1]: return low_str >_init ($<[1]: return camel_str> *self)
+{
+ self->priv = $<[1]: return up_str >_GET_PRIVATE (self);
+}
+
+$<[1]: return camel_str > *
+$<[1]: return low_str >_new ()
+{
+ return g_object_new ($<[1]: return type_str >, NULL);
+}]]></text>
+ <tag>gobject</tag>
+ <description>GObject template</description>
+ </snippet>
+ <snippet id="ginterface">
+ <text><![CDATA[#include "$1.h"
+$<
+global camel_str,low_str,up_str
+components = $1.split('-')
+low_str = '_'.join(components).lower()
+up_str = '_'.join(components).upper()
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+>
+/* Default implementation */
+static const gchar *
+$<[1]: return low_str>_example_method_default ($<[1]: return camel_str > *self)
+{
+ g_return_val_if_reached (NULL);
+}
+
+static void
+$<[1]: return low_str>_init ($<[1]: return camel_str >Iface *iface)
+{
+ static gboolean initialized = FALSE;
+
+ iface->example_method = $<[1]: return low_str>_example_method_default;
+
+ if (!initialized)
+ {
+ initialized = TRUE;
+ }
+}
+
+/*
+ * This is an method example for an interface
+ */
+const gchar *
+$<[1]: return low_str>_example_method ($<[1]: return camel_str > *self)
+{
+ g_return_val_if_fail ($<[1]: return up_str> (self), NULL);
+ return $<[1]: return up_str>_GET_INTERFACE (self)->example_method (self);
+}
+
+GType
+$<[1]: return low_str>_get_type ()
+{
+ static GType $<[1]: return low_str>_type_id = 0;
+
+ if (!$<[1]: return low_str>_type_id)
+ {
+ static const GTypeInfo g_define_type_info =
+ {
+ sizeof ($<[1]: return camel_str >Iface),
+ (GBaseInitFunc) $<[1]: return low_str>_init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ NULL
+ };
+
+ $<[1]: return low_str>_type_id =
+ g_type_register_static (G_TYPE_INTERFACE,
+ "$<[1]: return camel_str>",
+ &g_define_type_info,
+ 0);
+ }
+
+ return $<[1]: return low_str>_type_id;
+}]]></text>
+ <tag>ginterface</tag>
+ <description>GObject interface</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/chdr.xml b/plugins/snippets/data/chdr.xml
new file mode 100755
index 00000000..f71ea901
--- /dev/null
+++ b/plugins/snippets/data/chdr.xml
@@ -0,0 +1,241 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="chdr">
+ <snippet id="once">
+ <text><![CDATA[#ifndef __${1:NAME}_H__
+#define __$1_H__
+
+$0
+
+#endif /* __$1_H__ */
+]]></text>
+ <description>Header Include-Guard</description>
+ <tag>once</tag>
+ </snippet>
+ <snippet id="inc">
+ <text><![CDATA[#include "${1:file}"
+$0]]></text>
+ <description>#include ".."</description>
+ <tag>inc</tag>
+ </snippet>
+ <snippet id="Inc">
+ <text><![CDATA[#include <${1:file}>
+$0]]></text>
+ <description>#include &lt;..&gt;</description>
+ <tag>Inc</tag>
+ </snippet>
+ <snippet id="namespace">
+ <text><![CDATA[namespace ${1:ns}
+{
+ $0
+};
+]]></text>
+ <description>namespace ..</description>
+ <tag>namespace</tag>
+ </snippet>
+ <snippet id="gpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<program name>}
+ *
+ * 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 '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ${2}; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+$0]]></text>
+ <tag>gpl</tag>
+ <description>GPL License</description>
+ </snippet>
+ <snippet id="lgpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<library name>}
+ *
+ * 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 '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+$0]]></text>
+ <tag>lgpl</tag>
+ <description>LGPL License</description>
+ </snippet>
+ <snippet id="td">
+ <text><![CDATA[typedef ${1:newtype} ${2:type};
+$0]]></text>
+ <tag>td</tag>
+ <description>typedef</description>
+ </snippet>
+ <snippet id="class">
+ <text><![CDATA[class ${1:name}
+{
+ public:
+ ${1:name} (${2:arguments});
+ virtual ~${1:name} ();
+
+ private:
+ ${0:/* data */}
+};]]></text>
+ <description>class ..</description>
+ <tag>class</tag>
+ </snippet>
+ <snippet id="struct">
+ <text><![CDATA[struct ${1:name}
+{
+ ${0:/* data */}
+};]]></text>
+ <tag>struct</tag>
+ <description>struct</description>
+ </snippet>
+ <snippet id="template">
+ <text><![CDATA[template <typename ${1:_InputIter}>]]></text>
+ <description>template &lt;typename ..&gt;</description>
+ <tag>template</tag>
+ </snippet>
+ <snippet id="gobject">
+ <text><![CDATA[#ifndef __${1:NAME}_H__
+#define __$1_H__
+
+#include <${2:glib-object.h}>
+
+G_BEGIN_DECLS
+
+$<
+global camel_str
+components = $1.split('_')
+type_str = '_'.join([components[0], 'TYPE'] + components[1:])
+is_str = '_'.join([components[0], 'IS'] + components[1:])
+camel_str = ''
+
+for t in components:
+ camel_str += t.capitalize()
+
+items = [ \
+['#define ' + type_str, '(' + $1.lower() + '_get_type ())'], \
+['#define ' + $1 + '(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + '))'], \
+['#define ' + $1 + '_CONST(obj)', '(G_TYPE_CHECK_INSTANCE_CAST ((obj), ' + type_str + ', ' + camel_str + ' const))'], \
+['#define ' + $1 + '_CLASS(klass)', '(G_TYPE_CHECK_CLASS_CAST ((klass), ' + type_str + ', ' + camel_str + 'Class))'], \
+['#define ' + is_str + '(obj)', '(G_TYPE_CHECK_INSTANCE_TYPE ((obj), ' + type_str + '))'], \
+['#define ' + is_str + '_CLASS(klass)', '(G_TYPE_CHECK_CLASS_TYPE ((klass), ' + type_str + '))'], \
+['#define ' + $1 + '_GET_CLASS(obj)', '(G_TYPE_INSTANCE_GET_CLASS ((obj), ' + type_str + ', ' + camel_str + 'Class))']
+]
+
+return align(items) >
+
+$<[1]:
+items = [ \
+['typedef struct _' + camel_str, camel_str + ';'], \
+['typedef struct _' + camel_str + 'Class', camel_str + 'Class;'], \
+['typedef struct _' + camel_str + 'Private', camel_str + 'Private;'] \
+]
+
+return align(items) >
+
+struct _$<[1]: return camel_str > {
+ ${7:GObject} parent;
+
+ $<[1]: return camel_str >Private *priv;
+};
+
+struct _$<[1]: return camel_str >Class {
+ $7Class parent_class;
+};
+
+GType $< return $1.lower() + '_get_type' > (void) G_GNUC_CONST;
+$<[1]: return camel_str > *$< return $1.lower()>_new (void);
+
+$0
+G_END_DECLS
+
+#endif /* __$1_H__ */]]></text>
+ <tag>gobject</tag>
+ <description>GObject template</description>
+ </snippet>
+ <snippet id="ginterface">
+ <text><![CDATA[#ifndef __${1:NAME}_H__
+#define __$1_H__
+
+#include <${2:glib-object.h}>
+
+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__ */]]></text>
+ <tag>ginterface</tag>
+ <description>GObject interface</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/cpp.xml b/plugins/snippets/data/cpp.xml
new file mode 100755
index 00000000..7c7ccabd
--- /dev/null
+++ b/plugins/snippets/data/cpp.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="cpp">
+ <snippet id="main">
+ <text><![CDATA[int main (int argc, char const* argv[])
+{
+ $0
+ return 0;
+}]]></text>
+ <description>main</description>
+ <tag>main</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for (${1:unsigned int} ${2:i} = ${3:0}; ${2:i} < ${4:count}; ${2:i} += ${5:1})
+{
+ $0
+}]]></text>
+ <description>for loop</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="beginend">
+ <text><![CDATA[${1:v}.begin(), ${1:v}.end()]]></text>
+ <description>$1.begin</description>
+ <tag>beginend</tag>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do
+{
+ $0
+} while ($1 );]]></text>
+ <description>do .. while</description>
+ <tag>do</tag>
+ </snippet>
+ <snippet id="endif">
+ <text><![CDATA[#endif
+$0]]></text>
+ <accelerator><![CDATA[<Control><Alt>period]]></accelerator>
+ <description>#endif</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if (${1:condition})
+{
+ $0
+}]]></text>
+ <description>if ..</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="inc">
+ <text><![CDATA[#include "${1:file}"
+$0]]></text>
+ <description>#include ".."</description>
+ <tag>inc</tag>
+ </snippet>
+ <snippet id="Inc">
+ <text><![CDATA[#include <${1:file}>
+$0]]></text>
+ <description>#include &lt;..&gt;</description>
+ <tag>Inc</tag>
+ </snippet>
+ <snippet id="namespace">
+ <text><![CDATA[namespace ${1:ns}
+{
+ $0
+};
+]]></text>
+ <description>namespace ..</description>
+ <tag>namespace</tag>
+ </snippet>
+ <snippet id="readfile">
+ <text><![CDATA[std::vector<uint8_t> 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]]></text>
+ <description>Read File Into Vector</description>
+ <tag>readfile</tag>
+ </snippet>
+ <snippet id="map">
+ <text><![CDATA[std::map<${1:key}, ${2:value}> ${3:map};
+$0]]></text>
+ <description>std::map</description>
+ <tag>map</tag>
+ </snippet>
+ <snippet id="vector">
+ <text><![CDATA[std::vector<${1:char}> ${2:v};
+$0]]></text>
+ <description>std::vector</description>
+ <tag>vector</tag>
+ </snippet>
+ <snippet id="struct">
+ <text><![CDATA[struct ${1:name}
+{
+ ${0:/* data */}
+};]]></text>
+ <description>struct ..</description>
+ <tag>struct</tag>
+ </snippet>
+ <snippet id="template">
+ <text><![CDATA[template <typename ${1:_InputIter}>]]></text>
+ <description>template &lt;typename ..&gt;</description>
+ <tag>template</tag>
+ </snippet>
+ <snippet id="gpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<program name>}
+ *
+ * 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 '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with ${2}; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+ $0]]></text>
+ <tag>gpl</tag>
+ <description>GPL License</description>
+ </snippet>
+ <snippet id="lgpl">
+ <text><![CDATA[/*
+ * ${1:[$GEDIT_CURRENT_DOCUMENT_NAME,<filename>]}
+ * This file is part of ${2:<library name>}
+ *
+ * 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 '<author\>' >
+ *
+ * ${2} is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * ${2} is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+ $0]]></text>
+ <tag>lgpl</tag>
+ <description>LGPL License</description>
+ </snippet>
+ <snippet id="td">
+ <text><![CDATA[typedef ${1:newtype} ${2:type};
+$0]]></text>
+ <tag>td</tag>
+ <description>typedef</description>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while ($1)
+{
+ $0
+}]]></text>
+ <tag>while</tag>
+ <description>while</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/css.xml b/plugins/snippets/data/css.xml
new file mode 100755
index 00000000..babca912
--- /dev/null
+++ b/plugins/snippets/data/css.xml
@@ -0,0 +1,557 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="CSS">
+ <snippet id="background">
+ <text><![CDATA[background-attachment: ${1:scroll/fixed};
+$0]]></text>
+ <description>background-attachment: scroll/fixed</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-1">
+ <text><![CDATA[background-color: #${1:DDD};
+$0]]></text>
+ <description>background-color: color-hex</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-2">
+ <text><![CDATA[background-color: ${1:red};
+$0]]></text>
+ <description>background-color: color-name</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-3">
+ <text><![CDATA[background-color: rgb(${1:255},${2:255},${3:255});
+$0]]></text>
+ <description>background-color: color-rgb</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-4">
+ <text><![CDATA[background: #${1:DDD} url($2) ${3:repeat/repeat-x/repeat-y/no-repeat} ${4:scroll/fixed} ${5:top letft/top center/top right/center left/center center/center right/bottom left/bottom center/bottom right/x-% y-%/x-pos y-pos};
+$0]]></text>
+ <description>background: color image repeat attachment position</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-5">
+ <text><![CDATA[background-color: transparent;
+$0]]></text>
+ <description>background-color: transparent</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-6">
+ <text><![CDATA[background-image: none;
+$0]]></text>
+ <description>background-image: none</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-7">
+ <text><![CDATA[background-image: url($1);
+$0]]></text>
+ <description>background-image: url</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-8">
+ <text><![CDATA[background-position: ${1:top letft/top center/top right/center left/center center/center right/bottom left/bottom center/bottom right/x-% y-%/x-pos y-pos};
+$0]]></text>
+ <description>background-position: position</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="background-9">
+ <text><![CDATA[background-repeat: ${1:repeat/repeat-x/repeat-y/no-repeat};
+$0]]></text>
+ <description>background-repeat: r/r-x/r-y/n-r</description>
+ <tag>background</tag>
+ </snippet>
+ <snippet id="border">
+ <text><![CDATA[border-bottom-color: #${1:999};
+$0]]></text>
+ <description>border-bottom-color: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-1">
+ <text><![CDATA[border-bottom: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-bottom: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-2">
+ <text><![CDATA[border-bottom-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-bottom-style: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-3">
+ <text><![CDATA[border-bottom-width: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-bottom-width: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-4">
+ <text><![CDATA[border-color: ${1:999};
+$0]]></text>
+ <description>border-color: color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-5">
+ <text><![CDATA[border-right-color: #${1:999};
+$0]]></text>
+ <description>border-left-color: color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-6">
+ <text><![CDATA[border-left: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-left: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-7">
+ <text><![CDATA[border-left-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-left-style: style</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-8">
+ <text><![CDATA[border-left-width: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-left-width: size</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-9">
+ <text><![CDATA[border-right-color: #${1:999};
+$0]]></text>
+ <description>border-right-color: color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-10">
+ <text><![CDATA[border-right: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-right: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-11">
+ <text><![CDATA[border-right-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-right-style: style</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-12">
+ <text><![CDATA[border-right-width: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-right-width: size</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-13">
+ <text><![CDATA[border: ${1:1px} ${2:solid} #${3:999};
+$0]]></text>
+ <description>border: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-14">
+ <text><![CDATA[border-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-style: style</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-15">
+ <text><![CDATA[border-top-color: #${1:999};
+$0]]></text>
+ <description>border-top-color: color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-16">
+ <text><![CDATA[border-top: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-top: size style color</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-17">
+ <text><![CDATA[border-top-style: ${1:none/hidden/dotted/dashed/solid/double/groove/ridge/inset/outset};
+$0]]></text>
+ <description>border-top-style: style</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-18">
+ <text><![CDATA[border-top-width: ${1:1}px ${2:solid} #${3:999};
+$0]]></text>
+ <description>border-top-width: size</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="border-19">
+ <text><![CDATA[border-color: ${1:1px};
+$0]]></text>
+ <description>border-width: width</description>
+ <tag>border</tag>
+ </snippet>
+ <snippet id="clear">
+ <text><![CDATA[clear: ${1:left/right/both/none};
+$0]]></text>
+ <description>clear: value</description>
+ <tag>clear</tag>
+ </snippet>
+ <snippet id="color">
+ <text><![CDATA[color: #${1:DDD};
+$0]]></text>
+ <description>color: color-hex</description>
+ <tag>color</tag>
+ </snippet>
+ <snippet id="color-1">
+ <text><![CDATA[color: ${1:red};
+$0]]></text>
+ <description>color: color-name</description>
+ <tag>color</tag>
+ </snippet>
+ <snippet id="color-2">
+ <text><![CDATA[color: rgb(${1:255},${2:255},${3:255});
+$0]]></text>
+ <description>color: color-rgb</description>
+ <tag>color</tag>
+ </snippet>
+ <snippet id="cursor">
+ <text><![CDATA[cursor: {$1:default/auto/crosshair/pointer/move/*-resize/text/wait/help};
+$0]]></text>
+ <description>cursor: type</description>
+ <tag>cursor</tag>
+ </snippet>
+ <snippet id="clear-1">
+ <text><![CDATA[cursor: url($1);
+$0]]></text>
+ <description>cursor: url</description>
+ <tag>clear</tag>
+ </snippet>
+ <snippet id="direction">
+ <text><![CDATA[direction: ${1:ltr|rtl};
+$0]]></text>
+ <description>direction: ltr|rtl</description>
+ <tag>direction</tag>
+ </snippet>
+ <snippet id="display">
+ <text><![CDATA[display: block;
+$0]]></text>
+ <description>display: block</description>
+ <tag>display</tag>
+ </snippet>
+ <snippet id="display-1">
+ <text><![CDATA[display: ${1:none/inline/block/list-item/run-in/compact/marker};
+$0]]></text>
+ <description>display: common-types</description>
+ <tag>display</tag>
+ </snippet>
+ <snippet id="display-2">
+ <text><![CDATA[display: inline;
+$0]]></text>
+ <description>display: inline</description>
+ <tag>display</tag>
+ </snippet>
+ <snippet id="display-3">
+ <text><![CDATA[display: ${1:table/inline-table/table-row-group/table-header-group/table-footer-group/table-row/table-column-group/table-column/table-cell/table-caption};
+$0]]></text>
+ <description>display: table-types</description>
+ <tag>display</tag>
+ </snippet>
+ <snippet id="float">
+ <text><![CDATA[float: ${1:left/right/none};
+$0]]></text>
+ <description>float: left/right/none</description>
+ <tag>float</tag>
+ </snippet>
+ <snippet id="font">
+ <text><![CDATA[font-family: ${1:Arial, "MS Trebuchet"}, ${2:sans-}serif;
+$0]]></text>
+ <description>font-family: family</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-1">
+ <text><![CDATA[font: ${1:75%} ${2:"Lucida Grande", "Trebuchet MS", Verdana,} ${3:sans-}serif;
+$0]]></text>
+ <description>font: size font</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-2">
+ <text><![CDATA[font-size: ${1:100%};
+$0]]></text>
+ <description>font-size: size</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-3">
+ <text><![CDATA[font-style: ${1:normal/italic/oblique};
+$0]]></text>
+ <description>font-style: normal/italic/oblique</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-4">
+ <text><![CDATA[font: ${1:normal/italic/oblique} ${2:normal/small-caps} ${3:normal/bold} ${4:1em/1.5em} ${5:Arial}, ${6:sans-}serif;
+$0]]></text>
+ <description>font: style variant weight size/line-height font-family</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-5">
+ <text><![CDATA[font-variant: ${1:normal/small-caps};
+$0]]></text>
+ <description>font-variant: normal/small-caps</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="font-6">
+ <text><![CDATA[font-weight: ${1:normal/bold};
+$0]]></text>
+ <description>font-weight: weight</description>
+ <tag>font</tag>
+ </snippet>
+ <snippet id="letter">
+ <text><![CDATA[letter-spacing: $1em;
+$0]]></text>
+ <description>letter-spacing: length-em</description>
+ <tag>letter</tag>
+ </snippet>
+ <snippet id="letter-1">
+ <text><![CDATA[letter-spacing: $1px;
+$0]]></text>
+ <description>letter-spacing: length-px</description>
+ <tag>letter</tag>
+ </snippet>
+ <snippet id="list">
+ <text><![CDATA[list-style-image: url($1);
+$0]]></text>
+ <description>list-style-image: url</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-1">
+ <text><![CDATA[list-style-position: ${1:inside/outside};
+$0]]></text>
+ <description>list-style-position: pos</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-2">
+ <text><![CDATA[list-style-type: ${1:cjk-ideographic/hiragana/katakana/hiragana-iroha/katakana-iroha};
+$0]]></text>
+ <description>list-style-type: asian</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-3">
+ <text><![CDATA[list-style-type: ${1:none/disc/circle/square};
+$0]]></text>
+ <description>list-style-type: marker</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-4">
+ <text><![CDATA[list-style-type: ${1:decimal/decimal-leading-zero/zero};
+$0]]></text>
+ <description>list-style-type: numeric</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-5">
+ <text><![CDATA[list-style-type: ${1:hebrew/armenian/georgian};
+$0]]></text>
+ <description>list-style-type: other</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-6">
+ <text><![CDATA[list-style: ${1:none/disc/circle/square/decimal/zero} ${2:inside/outside} url($3);
+$0]]></text>
+ <description>list-style: type position image</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="list-7">
+ <text><![CDATA[list-style-type: ${1:lower-roman/uppert-roman/lower-alpha/upper-alpha/lower-greek/lower-latin/upper-latin};
+$0]]></text>
+ <description>list-style-type: roman-alpha-greek</description>
+ <tag>list</tag>
+ </snippet>
+ <snippet id="margin">
+ <text><![CDATA[margin: ${1:20px};
+$0]]></text>
+ <description>margin: all</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-1">
+ <text><![CDATA[margin-bottom: ${1:20px};
+$0]]></text>
+ <description>margin-bottom: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-2">
+ <text><![CDATA[margin-left: ${1:20px};
+$0]]></text>
+ <description>margin-left: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-3">
+ <text><![CDATA[margin-right: ${1:20px};
+$0]]></text>
+ <description>margin-right: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-4">
+ <text><![CDATA[margin-top: ${1:20px};
+$0]]></text>
+ <description>margin-top: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-5">
+ <text><![CDATA[margin: ${1:20px} ${2:0px} ${3:40px} ${4:0px};
+$0]]></text>
+ <description>margin: T R B L</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-6">
+ <text><![CDATA[margin: ${1:20px} ${2:0px};
+$0]]></text>
+ <description>margin: V H</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="marker">
+ <text><![CDATA[marker-offset: auto;
+$0]]></text>
+ <description>marker-offset: auto</description>
+ <tag>marker</tag>
+ </snippet>
+ <snippet id="marker-1">
+ <text><![CDATA[marker-offset: ${1:10px};
+$0]]></text>
+ <description>marker-offset: length</description>
+ <tag>marker</tag>
+ </snippet>
+ <snippet id="overflow">
+ <text><![CDATA[overflow: ${1:visible/hidden/scroll/auto};
+$0]]></text>
+ <description>overflow: type</description>
+ <tag>overflow</tag>
+ </snippet>
+ <snippet id="padding">
+ <text><![CDATA[padding: ${1:20px};
+$0]]></text>
+ <description>padding: all</description>
+ <tag>padding</tag>
+ </snippet>
+ <snippet id="margin-7">
+ <text><![CDATA[padding-bottom: ${1:20px};
+$0]]></text>
+ <description>padding-bottom: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-8">
+ <text><![CDATA[padding-left: ${1:20px};
+$0]]></text>
+ <description>padding-left: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-9">
+ <text><![CDATA[padding-right: ${1:20px};
+$0]]></text>
+ <description>padding-right: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="margin-10">
+ <text><![CDATA[padding-top: ${1:20px};
+$0]]></text>
+ <description>padding-top: length</description>
+ <tag>margin</tag>
+ </snippet>
+ <snippet id="padding-1">
+ <text><![CDATA[padding: ${1:20px} ${2:0px} ${3:40px} ${4:0px};
+$0]]></text>
+ <description>padding: T R B L</description>
+ <tag>padding</tag>
+ </snippet>
+ <snippet id="padding-2">
+ <text><![CDATA[padding: ${1:20px} ${2:0px};
+$0]]></text>
+ <description>padding: V H</description>
+ <tag>padding</tag>
+ </snippet>
+ <snippet id="position">
+ <text><![CDATA[position: ${1:static/relative/absolute/fixed};
+$0]]></text>
+ <description>position: type</description>
+ <tag>position</tag>
+ </snippet>
+ <snippet id="{">
+ <text><![CDATA[{
+ /* $1 */
+ $0
+]]></text>
+ <description>properties { }</description>
+ <tag>{</tag>
+ </snippet>
+ <snippet id="text">
+ <text><![CDATA[text-align: ${1:left/right/center/justify};
+$0]]></text>
+ <description>text-align: left/center/right</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-1">
+ <text><![CDATA[text-decoration: ${1:none/underline/overline/line-through/blink};
+$0]]></text>
+ <description>text-decoration: none/underline/overline/line-through/blink</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-2">
+ <text><![CDATA[text-indent: ${1:10p}x;
+$0]]></text>
+ <description>text-indent: length</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-3">
+ <text><![CDATA[text-shadow: #${1:DDD} ${2:10px} ${3:10px} ${4:2px};
+$0]]></text>
+ <description>text-shadow: color-hex x y blur</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-4">
+ <text><![CDATA[text-shadow: rgb(${1:255},${2:255},${3:255}) ${4:10px} ${5:10px} ${6:2px};
+$0]]></text>
+ <description>text-shadow: color-rgb x y blur</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-5">
+ <text><![CDATA[text-shadow: none;
+$0]]></text>
+ <description>text-shadow: none</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-6">
+ <text><![CDATA[text-transform: ${1:capitalize/uppercase/lowercase};
+$0]]></text>
+ <description>text-transform: capitalize/upper/lower</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="text-7">
+ <text><![CDATA[text-transform: none;
+$0]]></text>
+ <description>text-transform: none</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="vertical">
+ <text><![CDATA[vertical-align: ${1:baseline/sub/super/top/text-top/middle/bottom/text-bottom/length/%};
+$0]]></text>
+ <description>vertical-align: type</description>
+ <tag>vertical</tag>
+ </snippet>
+ <snippet id="visibility">
+ <text><![CDATA[visibility: ${1:visible/hidden/collapse};
+$0]]></text>
+ <description>visibility: type</description>
+ <tag>visibility</tag>
+ </snippet>
+ <snippet id="white">
+ <text><![CDATA[white-space: ${1:normal/pre/nowrap};
+$0]]></text>
+ <description>white-space: normal/pre/nowrap</description>
+ <tag>white</tag>
+ </snippet>
+ <snippet id="word">
+ <text><![CDATA[word-spacing: ${1:10px};
+$0]]></text>
+ <description>word-spacing: length</description>
+ <tag>word</tag>
+ </snippet>
+ <snippet id="word-1">
+ <text><![CDATA[word-spacing: normal;
+$0]]></text>
+ <description>word-spacing: normal</description>
+ <tag>word</tag>
+ </snippet>
+ <snippet id="z">
+ <text><![CDATA[z-index: $1;
+$0]]></text>
+ <description>z-index: index</description>
+ <tag>z</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/docbook.xml b/plugins/snippets/data/docbook.xml
new file mode 100755
index 00000000..3159b603
--- /dev/null
+++ b/plugins/snippets/data/docbook.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="docbook">
+ <!-- useful snippets from xml set -->
+ <snippet id="&lt;">
+ <text><![CDATA[<${1}>$0</${1}>]]></text>
+ <description>XML tag</description>
+ <tag>&lt;</tag>
+ </snippet>
+ <snippet id="menuchoice">
+ <text><![CDATA[<menuchoice><guimenu>$1</guimenu><guimenuitem>$2</guimenuitem></menuchoice>
+]]></text>
+ <tag>menu</tag>
+ <description>menuchoice</description>
+ <accelerator />
+ </snippet>
+ <snippet id="keycombo">
+ <text><![CDATA[<keycombo><keycap>${1:Ctrl}</keycap><keycap>${2}</keycap></keycombo>]]></text>
+ <tag>key</tag>
+ <description>keycombo</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="sect">
+ <text><![CDATA[<sect${1} id="${2}">
+ <title>${3}</title>
+</sect${1}>]]></text>
+ <tag>sect</tag>
+ <description>sect*</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="app">
+ <text><![CDATA[<application>&app;</application>]]></text>
+ <tag>app</tag>
+ <description>app entity</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="appwrap">
+ <text><![CDATA[<application>$GEDIT_SELECTED_TEXT</application>]]></text>
+ <tag>application</tag>
+ <description>application tag</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="enclose">
+ <text><![CDATA[<${1}>$GEDIT_SELECTED_TEXT</${1}>]]></text>
+ <tag>enclose</tag>
+ <description>enclose selected text</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="itemizedlist">
+ <text><![CDATA[<itemizedlist>
+ <listitem>
+ <para>${1}</para>
+ </listitem>
+</itemizedlist>]]></text>
+ <tag>ul</tag>
+ <description>itemized list</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="orderedlist">
+ <text><![CDATA[<orderedlist>
+ <listitem>
+ <para>${1}</para>
+ </listitem>
+</orderedlist>]]></text>
+ <tag>ol</tag>
+ <description>ordered list</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="listitem">
+ <text><![CDATA[<listitem>
+ <para>${1}</para>
+</listitem>]]></text>
+ <tag>li</tag>
+ <description>list item</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="variablelist">
+ <text><![CDATA[<variablelist>
+ $1
+</variablelist>]]></text>
+ <tag>vl</tag>
+ <description>variablelist</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="varlistentry">
+ <text><![CDATA[<varlistentry><term>${1}</term>
+ <listitem>
+ <para>${2}</para>
+ </listitem>
+</varlistentry>]]></text>
+ <tag>vli</tag>
+ <description>variablelist entry</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="closepara">
+ <text><![CDATA[</para>]]></text>
+ <tag>/</tag>
+ <description>para close</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="openpara">
+ <text><![CDATA[<para>]]></text>
+ <tag>p</tag>
+ <description>para open</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="http">
+ <text><![CDATA[<ulink type="http" url="$1">$2</ulink>]]></text>
+ <tag>http</tag>
+ <description>ulink http</description>
+ <accelerator/>
+ </snippet>
+ <snippet id="yelp">
+ <text><![CDATA[<ulink type="help" url="$1">$2</ulink>]]></text>
+ <tag>help</tag>
+ <description>ulink mate help</description>
+ <accelerator/>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/fortran.xml b/plugins/snippets/data/fortran.xml
new file mode 100755
index 00000000..c64d6461
--- /dev/null
+++ b/plugins/snippets/data/fortran.xml
@@ -0,0 +1,164 @@
+<?xml version='1.0' encoding='utf-8'?>
+<snippets language="fortran">
+ <snippet id="c">
+ <text><![CDATA[character(len=${1:10}) :: $0]]></text>
+ <tag>c</tag>
+ <description>character</description>
+ </snippet>
+ <snippet id="cl">
+ <text><![CDATA[close(${1:unit}, status='${2:keep}')]]></text>
+ <tag>cl</tag>
+ <description>close</description>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do ${1:i}=$2, $3, ${4:1}
+ ${0:source}
+end do]]></text>
+ <tag>do</tag>
+ <description>do ... end do</description>
+ </snippet>
+ <snippet id="func">
+ <text><![CDATA[function ${1:name}( ${2:parameter} )
+ ${3:integer/real ::} $1
+ ${4:integer/real ::} $2
+
+ ${0:source}
+
+ $1 = !result
+end function]]></text>
+ <tag>func</tag>
+ <description>function</description>
+ </snippet>
+ <snippet id="ifel">
+ <text><![CDATA[if( $1 ) then
+ ${2:source}
+else
+ ${0:source}
+end if]]></text>
+ <tag>ifel</tag>
+ <description>if ... else ... end if</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if( $1 ) then
+ ${0:source}
+end if]]></text>
+ <tag>if</tag>
+ <description>if ... end if</description>
+ </snippet>
+ <snippet id="i">
+ <text><![CDATA[integer(kind=${1:4}) :: $0]]></text>
+ <tag>i</tag>
+ <description>integer</description>
+ </snippet>
+ <snippet id="ida">
+ <text><![CDATA[integer(kind=${1:4}), dimension(${2::}), allocatable :: $0]]></text>
+ <tag>ida</tag>
+ <description>integerdimalloc</description>
+ </snippet>
+ <snippet id="id">
+ <text><![CDATA[integer(kind=${1:4}), dimension(${2::}) :: $0]]></text>
+ <tag>id</tag>
+ <description>integerdim</description>
+ </snippet>
+ <snippet id="l">
+ <text><![CDATA[logical(kind=${1:1}) :: $0]]></text>
+ <tag>l</tag>
+ <description>logical</description>
+ </snippet>
+ <snippet id="mod">
+ <text><![CDATA[module ${1:name}
+ implicit none
+ ${2:integer/real ::} $3
+
+ ${4:contains}
+
+ ${0:source}
+end module]]></text>
+ <tag>mod</tag>
+ <description>module</description>
+ </snippet>
+ <snippet id="op">
+ <text><![CDATA[open(${1:unit}, file='${2:name}', status='${3:new}')]]></text>
+ <tag>op</tag>
+ <description>open</description>
+ </snippet>
+ <snippet id="prog">
+ <text><![CDATA[program ${1:name}
+ implicit none
+
+ ${0:source}
+end program]]></text>
+ <tag>prog</tag>
+ <description>program</description>
+ </snippet>
+ <snippet id="re">
+ <text><![CDATA[read(unit=${1:*},fmt=${2:*}) $0]]></text>
+ <tag>re</tag>
+ <description>read</description>
+ </snippet>
+ <snippet id="r">
+ <text><![CDATA[real(kind=${1:8}) :: $0]]></text>
+ <tag>r</tag>
+ <description>real</description>
+ </snippet>
+ <snippet id="rda">
+ <text><![CDATA[real(kind=${1:8}), dimension(${2::}), allocatable :: $0]]></text>
+ <tag>rda</tag>
+ <description>realdimalloc</description>
+ </snippet>
+ <snippet id="rd">
+ <text><![CDATA[real(kind=${1:8}), dimension(${2::}) :: $0]]></text>
+ <tag>rd</tag>
+ <description>realdim</description>
+ </snippet>
+ <snippet id="rec">
+ <text><![CDATA[recursive function ${1:name}( ${2:parameter} ) result( ${3:res} )
+ ${4:integer/real ::} $3
+ ${5:integer/real ::} $2
+
+ ${0:source}
+
+ $3 = !result
+end function]]></text>
+ <tag>rec</tag>
+ <description>recursivfunc</description>
+ </snippet>
+ <snippet id="sel">
+ <text><![CDATA[select case( $1 )
+ case( $2 )
+ ${3:source}
+ case default
+ ${0:source}
+end select]]></text>
+ <tag>sel</tag>
+ <description>select</description>
+ </snippet>
+ <snippet id="sub">
+ <text><![CDATA[subroutine ${1:name}( ${2:parameter} )
+ ${3:integer/real ::} $2
+
+ ${0:source}
+end subroutine]]></text>
+ <tag>sub</tag>
+ <description>subroutine</description>
+ </snippet>
+ <snippet id="t">
+ <text><![CDATA[type :: ${1:name}
+ ${2:integer/real ::} $0
+end type $1]]></text>
+ <tag>t</tag>
+ <description>type</description>
+ </snippet>
+ <snippet id="dow">
+ <text><![CDATA[do while( ${1} )
+ ${0:source}
+end do]]></text>
+ <tag>dow</tag>
+ <description>while</description>
+ </snippet>
+ <snippet id="wr">
+ <text><![CDATA[write(unit=${1:*},fmt=${2:*}) "$3", $0]]></text>
+ <tag>wr</tag>
+ <description>write</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/global.xml b/plugins/snippets/data/global.xml
new file mode 100755
index 00000000..afe3c0b7
--- /dev/null
+++ b/plugins/snippets/data/global.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets/>
diff --git a/plugins/snippets/data/haskell.xml b/plugins/snippets/data/haskell.xml
new file mode 100755
index 00000000..54a8e7d6
--- /dev/null
+++ b/plugins/snippets/data/haskell.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Haskell">
+ <snippet id="mod">
+ <text><![CDATA[module ${1:Main} where
+ $0]]></text>
+ <description>module</description>
+ <tag>mod</tag>
+ </snippet>
+ <snippet id="\">
+ <text><![CDATA[\\${1:t} -> ${1:t}]]></text>
+ <description>\t -&gt; t</description>
+ <tag>\</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/html.xml b/plugins/snippets/data/html.xml
new file mode 100755
index 00000000..d294f934
--- /dev/null
+++ b/plugins/snippets/data/html.xml
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="HTML">
+ <snippet id="doctype">
+ <text><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+]]></text>
+ <description>HTML — 4.01 Strict</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-1">
+ <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+]]></text>
+ <description>XHTML — 1.0 Frameset</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-2">
+ <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+]]></text>
+ <description>XHTML — 1.0 Strict</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-3">
+ <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+]]></text>
+ <description>XHTML — 1.0 Transitional</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-4">
+ <text><![CDATA[<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+]]></text>
+ <description>XHTML — 1.1</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="doctype-5">
+ <text><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+]]></text>
+ <description>HTML — 4.0 Transitional</description>
+ <tag>doctype</tag>
+ </snippet>
+ <snippet id="author">
+ <text><![CDATA[<meta name="author" content="${1:author}" />
+$0]]></text>
+ <tag>author</tag>
+ <description>Author</description>
+ </snippet>
+ <snippet id="date">
+ <text><![CDATA[<meta name="date" content="$<1: import time; return time.strftime("%Y-%m-%d") >" />
+$0]]></text>
+ <tag>date</tag>
+ <description>Date</description>
+ </snippet>
+ <snippet id="ref">
+ <text><![CDATA[<a href="${1:http://somesite.com/}">${2:$GEDIT_SELECTED_TEXT}</a>
+]]></text>
+ <accelerator><![CDATA[<Shift><Alt>l]]></accelerator>
+ <description>Wrap Selection as Link</description>
+ <tag>ref</tag>
+ </snippet>
+ <snippet id="open/close">
+ <text><![CDATA[<${1:p}>$GEDIT_SELECTED_TEXT</${1}>]]></text>
+ <accelerator><![CDATA[<Shift><Alt>w]]></accelerator>
+ <description>Wrap Selection in Open/Close Tag</description>
+ </snippet>
+ <snippet id="mailto">
+ <text><![CDATA[<a href="mailto:${1:[email protected]}?subject=${2:feedback}">${3:email me}</a> $0]]></text>
+ <description>Mail Anchor</description>
+ <tag>mailto</tag>
+ </snippet>
+ <snippet id="base">
+ <text><![CDATA[<base href="$1" ${2}/>$0]]></text>
+ <description>Base</description>
+ <tag>base</tag>
+ </snippet>
+ <snippet id="body">
+ <text><![CDATA[<body id="${1:ID} " onload="$2"}>
+ $0
+</body>]]></text>
+ <description>Body</description>
+ <tag>body</tag>
+ </snippet>
+ <snippet id="br">
+ <text><![CDATA[<br />
+$0]]></text>
+ <accelerator><![CDATA[<Shift><Control>space]]></accelerator>
+ <description>Br</description>
+ </snippet>
+ <snippet id="button">
+ <text><![CDATA[<button type="button" name="${1:name}" value="${2:caption}" onclick="$3" />$4
+]]></text>
+ <tag>button</tag>
+ <description>Button</description>
+ </snippet>
+ <snippet id="div">
+ <text><![CDATA[<div ${1}>
+ ${0:$GEDIT_SELECTED_TEXT}
+</div>]]></text>
+ <description>Div</description>
+ <tag>div</tag>
+ </snippet>
+ <snippet id="file">
+ <text><![CDATA[<input type="file" name="${1:name}" size="$2" accept="$3" />$0
+]]></text>
+ <tag>file</tag>
+ <description>File</description>
+ </snippet>
+ <snippet id="form">
+ <text><![CDATA[<form action="${1}" method="${2:get}">
+ $0
+
+ <p><input type="submit" value="${3:Continue &rarr;}" /></p>
+</form>]]></text>
+ <description>Form</description>
+ <tag>form</tag>
+ </snippet>
+ <snippet id="h">
+ <text><![CDATA[<h${1:1} id="${2}">${3:$GEDIT_SELECTED_TEXT}</h${1}>
+$0]]></text>
+ <description>Heading</description>
+ <tag>h</tag>
+ </snippet>
+ <snippet id="head">
+ <text><![CDATA[<head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+ <title>${1:Page Title}</title>
+ $0
+</head>]]></text>
+ <description>Head</description>
+ <tag>head</tag>
+ </snippet>
+ <snippet id="image">
+ <text><![CDATA[<img src="${1:path/to/file}" alt="${2:description}" title="${3:description}" width="$4" height="$5" />$0]]></text>
+ <tag>img</tag>
+ <description>Image</description>
+ </snippet>
+ <snippet id="input">
+ <text><![CDATA[<input type="${1:[button,checkbox,file,hidden,image,password,radio,reset,submit,text]}" name="${2:some_name}" value="$3" id="${5}" />]]></text>
+ <description>Input</description>
+ <tag>input</tag>
+ </snippet>
+ <snippet id="li">
+ <text><![CDATA[<li>$1</li>$0]]></text>
+ <tag>li</tag>
+ <description>List Element</description>
+ </snippet>
+ <snippet id="link">
+ <text><![CDATA[<link rel="${1:stylesheet}" href="${2:/css/master.css}" type="text/css" media="${3:screen}" title="${4:no title}" charset="${5:utf-8}" />]]></text>
+ <description>Link</description>
+ <tag>link</tag>
+ </snippet>
+ <snippet id="meta">
+ <text><![CDATA[<meta name="${1:name}" content="${2:content}" />]]></text>
+ <description>Meta</description>
+ <tag>meta</tag>
+ </snippet>
+ <snippet id="nbsp">
+ <text><![CDATA[&nbsp;]]></text>
+ <accelerator><![CDATA[<Control><Alt>space]]></accelerator>
+ <description>Non-Breaking Space</description>
+ </snippet>
+ <snippet id="noscript">
+ <text><![CDATA[<noscript>$1</noscript>$0]]></text>
+ <tag>noscript</tag>
+ <description>Noscript</description>
+ </snippet>
+ <snippet id="option">
+ <text><![CDATA[<option value="${1:value}">$2</option>$0]]></text>
+ <tag>option</tag>
+ <description>Option</description>
+ </snippet>
+ <snippet id="script">
+ <text><![CDATA[<script type="text/javascript" language="javascript" charset="utf-8">
+// <![CDATA[
+ $0
+// ]]]]><![CDATA[>
+</script>]]></text>
+ <description>Script</description>
+ <tag>script</tag>
+ </snippet>
+ <snippet id="scriptsrc">
+ <text><![CDATA[<script src="$1" type="text/javascript" language="${2:javascript}" charset="${3:utf-8}" />]]></text>
+ <description>Script With External Source</description>
+ <tag>scriptsrc</tag>
+ </snippet>
+ <snippet id="select">
+ <text><![CDATA[<select name="${1:name}">
+ <option value="${2:value}">$3</option>
+ $4
+</select>$0
+]]></text>
+ <tag>select</tag>
+ <description>Select</description>
+ </snippet>
+ <snippet id="span">
+ <text><![CDATA[<span ${1}>$2</span>$0]]></text>
+ <tag>span</tag>
+ <description>Span</description>
+ </snippet>
+ <snippet id="style">
+ <text><![CDATA[<style type="text/css" media="screen">
+/* <![CDATA[ */
+ $0
+/* ]]]]><![CDATA[> */
+</style>
+]]></text>
+ <description>Style</description>
+ <tag>style</tag>
+ </snippet>
+ <snippet id="table">
+ <text><![CDATA[<table border="${1:0}" cellspacing="${2:0}" cellpadding="${3:0}">
+ <tr><th>${4:Header}</th></tr>
+ <tr><td>${5:Data}</td></tr>
+ $0
+</table>]]></text>
+ <description>Table</description>
+ <tag>table</tag>
+ </snippet>
+ <snippet id="textarea">
+ <text><![CDATA[<textarea name="${1:Name}" rows="${2:8}" cols="${3:40}">$0</textarea>]]></text>
+ <description>Text Area</description>
+ <tag>textarea</tag>
+ </snippet>
+ <snippet id="title">
+ <text><![CDATA[<title>${1:Page Title}</title>]]></text>
+ <description>Title</description>
+ <tag>title</tag>
+ </snippet>
+ <snippet id="tr">
+ <text><![CDATA[<tr><td>$1</td></tr>
+$0]]></text>
+ <tag>tr</tag>
+ <description>Table Row</description>
+ </snippet>
+ <snippet id="ul">
+ <text><![CDATA[<ul>
+ <li>$1</li>
+ $2
+</ul>
+$0]]></text>
+ <tag>ul</tag>
+ <description>Unordered List</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/idl.xml b/plugins/snippets/data/idl.xml
new file mode 100755
index 00000000..2b6ef30d
--- /dev/null
+++ b/plugins/snippets/data/idl.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="IDL">
+ <snippet id="mod">
+ <text><![CDATA[module ${1:name}
+{
+ $0
+};
+]]></text>
+ <tag>mod</tag>
+ <description>Module</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[interface ${1:name}
+{
+ $0
+};
+]]></text>
+ <tag>if</tag>
+ <description>Interface</description>
+ </snippet>
+ <snippet id="str">
+ <text><![CDATA[struct ${1:name}
+{
+ $0
+};
+]]></text>
+ <tag>str</tag>
+ <description>Struct</description>
+ </snippet>
+ <snippet id="exc">
+ <text><![CDATA[exception ${1:name}
+{
+ $0
+};
+]]></text>
+ <tag>exc</tag>
+ <description>Exception</description>
+ </snippet>
+ <snippet id="seq">
+ <text><![CDATA[sequence<${1:type}> ]]></text>
+ <tag>seq</tag>
+ <description>Sequence</description>
+ </snippet>
+ <snippet id="tseq">
+ <text><![CDATA[typedef sequence<${1:type}> ${0:newtype};]]></text>
+ <tag>tseq</tag>
+ <description>Typedef Sequence</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/java.xml b/plugins/snippets/data/java.xml
new file mode 100755
index 00000000..043a5dd3
--- /dev/null
+++ b/plugins/snippets/data/java.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Java">
+ <snippet id="cd">
+ <text><![CDATA[private static final ${1:String} ${2:var} = "$0";]]></text>
+ <description>const def</description>
+ <tag>cd</tag>
+ </snippet>
+ <snippet id="ife">
+ <text><![CDATA[if ($1) { // $2
+
+ $0
+
+} else { // $3
+
+
+
+}
+
+]]></text>
+ <description>if .. else</description>
+ <tag>ife</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if ($1) { // $2
+ $0
+}]]></text>
+ <description>if</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="log">
+ <text><![CDATA[/** Logger for this class and subclasses. */
+protected final Log log = LogFactory.getLog(getClass());
+]]></text>
+ <description>logger</description>
+ <tag>log</tag>
+ </snippet>
+ <snippet id="tcf">
+ <text><![CDATA[try {
+ $2
+} catch (${1:Exception} e) {
+ $3
+} finally {
+ $4
+}
+$0]]></text>
+ <description>try .. catch .. finally</description>
+ <tag>tcf</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while ($1) { // $2
+ $0
+}]]></text>
+ <description>while statement</description>
+ <tag>while</tag>
+ </snippet>
+ <snippet id="main">
+ <text><![CDATA[public static void main(String[] args) {
+ ${1:System.exit(0)};
+}]]></text>
+ <description>main</description>
+ <tag>main</tag>
+ </snippet>
+ <snippet id="sout">
+ <text><![CDATA[System.out.println("${1}");
+$0
+]]></text>
+ <description>System.out.println</description>
+ <tag>sout</tag>
+ </snippet>
+ <snippet id="try/catch">
+ <text><![CDATA[try {
+ $GEDIT_SELECTED_TEXT
+}
+catch (Exception e) {
+ ${1:e.printStackTrace();}
+}
+$0]]></text>
+ <accelerator><![CDATA[<Shift><Alt>t]]></accelerator>
+ <description>Wrap Selection in Try/Catch</description>
+ </snippet>
+ <snippet id="tc">
+ <text><![CDATA[try {
+ $2
+} catch (${1:Exception} e) {
+ $3
+}
+$0]]></text>
+ <tag>tc</tag>
+ <description>try .. catch</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/javascript.xml b/plugins/snippets/data/javascript.xml
new file mode 100755
index 00000000..c1d498a6
--- /dev/null
+++ b/plugins/snippets/data/javascript.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="JavaScript">
+ <snippet id="fun">
+ <text><![CDATA[function ${1:function_name} (${2:first_argument})
+{
+ ${0:# body...}
+}]]></text>
+ <description>function</description>
+ <tag>fun</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/lang/Makefile.am b/plugins/snippets/data/lang/Makefile.am
new file mode 100755
index 00000000..8a497c12
--- /dev/null
+++ b/plugins/snippets/data/lang/Makefile.am
@@ -0,0 +1,9 @@
+# Python snippets plugin
+lang_DATA = \
+ snippets.lang
+
+langdir = $(GEDIT_PLUGINS_DATA_DIR)/snippets/lang
+
+EXTRA_DIST = $(lang_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/snippets/data/lang/snippets.lang b/plugins/snippets/data/lang/snippets.lang
new file mode 100755
index 00000000..81e57394
--- /dev/null
+++ b/plugins/snippets/data/lang/snippets.lang
@@ -0,0 +1,162 @@
+<?xml version="1.0"?>
+<!--
+
+ Author: Jesse van den Kieboom <[email protected]>
+ Copyright (C) 2007-2008 Jesse van den Kieboom <[email protected]>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ Boston, MA 02111-1307, USA.
+
+-->
+<language id="snippets" name="Snippets" hidden="true" version="2.0">
+ <styles>
+ <style id="placeholder-bounds" name="Placeholder begin and end" map-to="def:function"/>
+ <style id="default-value" name="Default Value" map-to="def:string"/>
+ <style id="single-placeholder" name="Single Placeholder" map-to="def:decimal"/>
+ <style id="shell-placeholder" name="Shell Placeholder" map-to="def:preprocessor"/>
+ <style id="python-placeholder" name="Python Placeholder" map-to="def:preprocessor"/>
+ <style id="regex-placeholder" name="Regular Expression Placeholder" map-to="def:preprocessor"/>
+ <style id="tabstop" name="Tabstop" map-to="def:decimal"/>
+ <style id="placeholder-ref" name="Placeholder Reference" map-to="def:decimal"/>
+ <style id="placeholder-def" name="Placeholder Default" map-to="def:string"/>
+ <style id="escape" name="Escape" map-to="def:special-char"/>
+ <style id="environmental-var" name="Environmental Variable" map-to="def:string"/>
+ <style id="seperator" name="Seperator" map-to="def:shebang"/>
+ <style id="regex-pattern" name="Regular Expression Pattern" map-to="def:string"/>
+ <style id="replace-pattern" name="Regular Expression Replace Pattern" map-to="def:string"/>
+ <style id="modifier" name="Modifier" map-to="def:keyword"/>
+ </styles>
+
+ <definitions>
+ <define-regex id="number">[0-9]+</define-regex>
+ <define-regex id="tabstop">\s*((\%{number})(:))</define-regex>
+ <define-regex id="number-list" extended="true">\s*(\[(\%{number}(,\%{number})*)\](:))</define-regex>
+ <define-regex id="environment">\$[A-Z_]+</define-regex>
+ <define-regex id="regex-pattern">((?:\\[/]|\\}|[^/}])+)</define-regex>
+
+ <context id="escape" style-ref="escape">
+ <match>\\\$</match>
+ </context>
+ <context id="single-placeholder" style-ref="single-placeholder">
+ <match>\$\%{number}|\${\%{number}}</match>
+ </context>
+ <context id="simple-placeholder-def" style-ref="default-value">
+ <start>\${\%{tabstop}</start>
+ <end>}</end>
+ <include>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/>
+ <context sub-pattern="2" where="start" style-ref="tabstop"/>
+ <context sub-pattern="3" where="start" style-ref="seperator"/>
+ <context>
+ <match>\\}</match>
+ </context>
+ <context ref="escape"/>
+ <context ref="environmental-variable"/>
+ </include>
+ </context>
+ <context id="simple-placeholder">
+ <include>
+ <context ref="single-placeholder"/>
+ <context ref="simple-placeholder-def"/>
+ </include>
+ </context>
+ <context id="shell-placeholder-contents">
+ <include>
+ <context ref="escape"/>
+ <context ref="environmental-variable"/>
+ <context ref="single-placeholder"/>
+ </include>
+ </context>
+ <context id="shell-placeholder">
+ <include>
+ <context style-ref="shell-placeholder">
+ <start>\$\(\%{tabstop}?</start>
+ <end>\)</end>
+ <include>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/>
+ <context sub-pattern="2" where="start" style-ref="tabstop"/>
+ <context sub-pattern="3" where="start" style-ref="seperator"/>
+ <context ref="shell-placeholder-contents"/>
+ <context>
+ <match>\\\)</match>
+ </context>
+ </include>
+ </context>
+ <context style-ref="shell-placeholder">
+ <start>`\%{tabstop}?</start>
+ <end>`</end>
+ <include>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/>
+ <context sub-pattern="2" where="start" style-ref="tabstop"/>
+ <context sub-pattern="3" where="start" style-ref="seperator"/>
+ <context ref="shell-placeholder-contents"/>
+ <context>
+ <match>\\`</match>
+ </context>
+ </include>
+ </context>
+ </include>
+ </context>
+ <context id="python-placeholder">
+ <start>\$&lt;\%{tabstop}?\%{number-list}?</start>
+ <end>&gt;</end>
+ <include>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="start"/>
+ <context sub-pattern="0" style-ref="placeholder-bounds" where="end"/>
+ <context sub-pattern="2" where="start" style-ref="tabstop"/>
+ <context sub-pattern="3" where="start" style-ref="seperator"/>
+ <context sub-pattern="5" where="start" style-ref="tabstop"/>
+ <context sub-pattern="7" where="start" style-ref="seperator"/>
+ <context>
+ <match>\\&gt;</match>
+ </context>
+ <context ref="escape"/>
+ <context ref="environmental-variable"/>
+ <context ref="single-placeholder"/>
+ <context ref="python:python"/>
+ </include>
+ </context>
+ <context id="regex-placeholder" style-ref="regex-placeholder">
+ <match>(\${)\%{tabstop}?(?:\s*(?:(\%{number})|(\%{environment})))/\%{regex-pattern}/\%{regex-pattern}(?:[/]([a-zA-Z]*))?(})</match>
+ <include>
+ <context sub-pattern="1" style-ref="placeholder-bounds"/>
+ <context sub-pattern="10" style-ref="placeholder-bounds"/>
+ <context sub-pattern="3" style-ref="tabstop"/>
+ <context sub-pattern="4" style-ref="seperator"/>
+ <context sub-pattern="5" style-ref="tabstop"/>
+ <context sub-pattern="6" style-ref="environmental-var"/>
+ <context sub-pattern="7" style-ref="regex-pattern"/>
+ <context sub-pattern="8" style-ref="replace-pattern"/>
+ <context sub-pattern="9" style-ref="modifier"/>
+ </include>
+ </context>
+ <context id="environmental-variable" style-ref="environmental-var">
+ <match>\%{environment}</match>
+ </context>
+ <context id="snippets">
+ <include>
+ <context ref="escape"/>
+ <context ref="regex-placeholder"/>
+ <context ref="simple-placeholder"/>
+ <context ref="shell-placeholder"/>
+ <context ref="python-placeholder"/>
+ <context ref="environmental-variable"/>
+ </include>
+ </context>
+ </definitions>
+</language>
diff --git a/plugins/snippets/data/latex.xml b/plugins/snippets/data/latex.xml
new file mode 100755
index 00000000..71672ec8
--- /dev/null
+++ b/plugins/snippets/data/latex.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="LaTeX">
+ <snippet id="command">
+ <text><![CDATA[{\\${1:bf} $GEDIT_SELECTED_TEXT}]]></text>
+ <accelerator><![CDATA[<Shift><Alt>w]]></accelerator>
+ <description>Wrap Selection in Command</description>
+ </snippet>
+ <snippet id="$">
+ <text><![CDATA[\[
+ $1
+\]]]></text>
+ <description>Displaymath</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="itd">
+ <text><![CDATA[\item[${1:description}] ${0:item}]]></text>
+ <description>\item[description]</description>
+ <tag>itd</tag>
+ </snippet>
+ <snippet id="sec">
+ <text><![CDATA[\section{${1:section name}}\label{${2:label}}
+]]></text>
+ <description>Section</description>
+ <tag>sec</tag>
+ </snippet>
+ <snippet id="sub">
+ <text><![CDATA[\subsection{${1:subsection name}}\label{${2:label}}
+]]></text>
+ <description>Sub Section</description>
+ <tag>sub</tag>
+ </snippet>
+ <snippet id="ssub">
+ <text><![CDATA[\subsubsection{${1:subsubsection name}}\label{${2:label}}
+]]></text>
+ <description>Sub Sub Section</description>
+ <tag>ssub</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/mallard.xml b/plugins/snippets/data/mallard.xml
new file mode 100755
index 00000000..ffa418d7
--- /dev/null
+++ b/plugins/snippets/data/mallard.xml
@@ -0,0 +1,207 @@
+<?xml version='1.0' encoding='utf-8'?>
+<snippets language="mallard">
+ <snippet>
+ <text><![CDATA[<app>${1:Application's name}</app>$0]]></text>
+ <tag>app</tag>
+ <description>app</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<![CDATA[$0]]]]><![CDATA[>]]></text>
+ <tag>cdata</tag>
+ <description>cdata</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<cite>${1:Joe Example}</cite>]]></text>
+ <tag>cite</tag>
+ <description>cite</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<cite date="$1">${2:Joe Example}</cite>]]></text>
+ <tag>cited</tag>
+ <description>cite</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<desc>${1:Description}</desc>]]></text>
+ <tag>desc</tag>
+ <description>desc</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<em>$1</em>]]></text>
+ <tag>em</tag>
+ <description>em</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<figure>
+ <title>${1:Title}</title>
+ <desc>${2:Short description}</desc>
+ <media type="${3:image}" mime="${5:image/png}" src="${4:figures/image.png}" >
+ </media>
+</figure>]]></text>
+ <tag>figure</tag>
+ <description>figure</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<file>${0:filename}</file>]]></text>
+ <tag>file</tag>
+ <description>file</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<gui>$1</gui>$2]]></text>
+ <tag>g</tag>
+ <description>gui</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<guiseq><gui>$1</gui><gui>$2</gui></guiseq>]]></text>
+ <tag>q</tag>
+ <description>guiseq</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<link href="http://$1/"<$2></link>]]></text>
+ <tag>http</tag>
+ <description>http</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<item><p>$0</p></item>]]></text>
+ <tag>item</tag>
+ <description>item</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<key>$1</key>]]></text>
+ <tag>key</tag>
+ <description>key</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<keyseq><key>$1</key><key>$2</key></keyseq>]]></text>
+ <tag>keys</tag>
+ <description>keystroke</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<link type="${1:guide}" xref="${2:index}"/>]]></text>
+ <tag>link</tag>
+ <description>link</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<list>
+ <title>${2:Title}</title>
+ <item><p>${3}</p></item>
+ <item><p>${4}</p></item>
+</list>]]></text>
+ <tag>list</tag>
+ <description>list</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<listing>
+ <title>${1:Title}</title>
+ <desc>${2:Short description}</desc>
+ <code><![CDATA[
+$0
+]]]]><![CDATA[></code>
+</listing>]]></text>
+ <tag>listing</tag>
+ <description>listing</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<note style="${1:advanced|bug|important|tip|warning}">
+ <p>
+ $0
+ </p>
+</note>]]></text>
+ <tag>note</tag>
+ <description>note</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<list type="numbered">
+ <item><p>$1</p></item>
+ <item><p>$2</p></item>
+ <item><p>$3</p></item>
+</list> ]]></text>
+ <tag>num</tag>
+ <description>numbered list</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<page xmlns="http://projectmallard.org/1.0/"
+ type="${1:topic}" style="${2:task}"
+ id="${3:id}">
+
+<info>
+ <desc>${4:Short description}</desc>
+ <revision pkgversion="${5:program_version}" version="${6:document_version}" date="$<7: import datetime as d; return d.date.isoformat(d.date.today())>" status="${8:incomplete}"/>
+ <credit type="author">
+ <name>$<9:
+import pwd, os
+try:
+ return pwd.getpwuid(os.getuid()).pw_gecos.split(',')[0]
+except KeyError:
+ return 'Joe Example' ></name>
+ <email>$<10:
+import os
+return os.getenv('EMAIL', '[email protected]') ></email>
+ </credit>
+</info>
+
+$0
+
+</page>]]></text>
+ <tag>page</tag>
+ <description>page</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[</p>]]></text>
+ <tag>/</tag>
+ <description>p close</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<p>]]></text>
+ <tag>p</tag>
+ <description>p open</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<quote>
+ <p>$0</p>
+</quote>]]></text>
+ <tag>quote</tag>
+ <description>quote</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<section id="${1}">
+ <title>${2:Section Title}</title>
+ <p>$0</p>
+</section>]]></text>
+ <tag>section</tag>
+ <description>section</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<steps>
+ <item><p>$0</p></item>
+</steps>]]></text>
+ <tag>steps</tag>
+ <description>steps</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<terms>
+ <title>$1</title>
+ <item><p>$2</p></item>
+</terms>]]></text>
+ <tag>terms</tag>
+ <description>terms</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<note style="tip">
+ <p>$1</p>
+</note> ]]></text>
+ <tag>tip</tag>
+ <description>tip note</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<title>$1</title>]]></text>
+ <tag>title</tag>
+ <description>title</description>
+ </snippet>
+ <snippet>
+ <text><![CDATA[<note style="warning>
+ <p>$1</p>
+</note> ]]></text>
+ <tag>warn</tag>
+ <description>warning note</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/perl.xml b/plugins/snippets/data/perl.xml
new file mode 100755
index 00000000..add148fa
--- /dev/null
+++ b/plugins/snippets/data/perl.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Perl">
+ <snippet id="perl">
+ <text><![CDATA[#!/usr/bin/perl
+$0]]></text>
+ <tag>perl</tag>
+ <description>#!/usr/bin/perl</description>
+ </snippet>
+ <snippet id="ife">
+ <text><![CDATA[if ($1) {
+ ${2:# body...}
+} else {
+ ${3:# else...}
+}
+]]></text>
+ <description>Conditional if..else</description>
+ <tag>ife</tag>
+ </snippet>
+ <snippet id="ifee">
+ <text><![CDATA[if ($1) {
+ ${2:# body...}
+} elsif ($3) {
+ ${4:# elsif...}
+} else {
+ ${5:# else...}
+}
+]]></text>
+ <description>Conditional if..elsif..else</description>
+ <tag>ifee</tag>
+ </snippet>
+ <snippet id="xunless">
+ <text><![CDATA[${1:expression} unless ${2:condition};
+]]></text>
+ <description>Conditional one-line</description>
+ <tag>xunless</tag>
+ </snippet>
+ <snippet id="xif">
+ <text><![CDATA[${1:expression} if ${2:condition};
+]]></text>
+ <description>Conditional one-line</description>
+ <tag>xif</tag>
+ </snippet>
+ <snippet id="eval">
+ <text><![CDATA[eval {
+ ${1:# do something risky...}
+};
+if ($@) {
+ ${2:# handle failure...}
+}
+]]></text>
+ <description>Try/Except</description>
+ <tag>eval</tag>
+ </snippet>
+ <snippet id="fore">
+ <text><![CDATA[foreach ${1:my $${2:x} }(@${3:array}) {
+ ${4:# body...}
+}
+]]></text>
+ <description>Loop</description>
+ <tag>fore</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for (my $${1:var} = 0; $$1 < ${2:expression}; $$1++) {
+ ${3:# body...}
+}
+]]></text>
+ <description>Loop</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="sub">
+ <text><![CDATA[sub ${1:function_name} {
+ ${2:# body...}
+}
+]]></text>
+ <description>Function</description>
+ <tag>sub</tag>
+ </snippet>
+ <snippet id="hashpointer">
+ <text><![CDATA[ => ]]></text>
+ <accelerator><![CDATA[<Shift><Alt>l]]></accelerator>
+ <description>hash pointer</description>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if ($1) {
+ ${2:# body...}
+}
+]]></text>
+ <description>Conditional</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="xfore">
+ <text><![CDATA[${1:expression} foreach @${2:array};
+]]></text>
+ <description>Loop one-line</description>
+ <tag>xfore</tag>
+ </snippet>
+ <snippet id="xwhile">
+ <text><![CDATA[${1:expression} while ${2:condition};
+]]></text>
+ <description>Loop one-line</description>
+ <tag>xwhile</tag>
+ </snippet>
+ <snippet id="slurp">
+ <text><![CDATA[my $${1:var};
+{ local $/ = undef; local *FILE; open FILE, "<${2:file}"; $$1 = <FILE>; close FILE }
+]]></text>
+ <description>Read File</description>
+ <tag>slurp</tag>
+ </snippet>
+ <snippet id="unless">
+ <text><![CDATA[unless ($1) {
+ ${2:# body...}
+}
+]]></text>
+ <description>Conditional</description>
+ <tag>unless</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while ($1) {
+ ${2:# body...}
+}
+]]></text>
+ <description>Loop</description>
+ <tag>while</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/php.xml b/plugins/snippets/data/php.xml
new file mode 100755
index 00000000..7d03f97d
--- /dev/null
+++ b/plugins/snippets/data/php.xml
@@ -0,0 +1,224 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="PHP">
+ <snippet id="class">
+ <text><![CDATA[#doc
+# classname: ${1:ClassName}
+# scope: ${2:PUBLIC}
+#
+#/doc
+
+class ${1:ClassName} ${3:extends AnotherClass}
+{
+ # internal variables
+
+ # Constructor
+ function __construct (${4:argument})
+ {
+ # code...
+ $0
+ }
+ ###
+
+}
+###]]></text>
+ <description>class ..</description>
+ <tag>class</tag>
+ </snippet>
+ <snippet id="$">
+ <text><![CDATA[\$_COOKIE['${1:variable}']]]></text>
+ <description>COOKIE['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do
+{
+ # code...
+ $0
+} while (${1:$a <= 10});]]></text>
+ <description>do .. while ..</description>
+ <tag>do</tag>
+ </snippet>
+ <snippet id="elseif">
+ <text><![CDATA[elseif (${1:condition})
+{
+ # code...
+ $0
+}]]></text>
+ <description>elseif ..</description>
+ <tag>elseif</tag>
+ </snippet>
+ <snippet id="else">
+ <text><![CDATA[else
+{
+ # code...
+ $0
+}]]></text>
+ <description>else ..</description>
+ <tag>else</tag>
+ </snippet>
+ <snippet id="$-1">
+ <text><![CDATA[\$_ENV['${1:variable}']]]></text>
+ <description>ENV['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-2">
+ <text><![CDATA[\$_FILES['${1:variable}']]]></text>
+ <description>FILES['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="foreach">
+ <text><![CDATA[foreach ($${1:variable} as $${2:key} => $${3:value})
+{
+ # code...
+ $0:
+}]]></text>
+ <description>foreach ..</description>
+ <tag>foreach</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for ($${1:i} = ${2:0}; $${1:i} < $3; $${1:i}++)
+{
+ # code...
+ $0
+}]]></text>
+ <description>for ..</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="function">
+ <text><![CDATA[${1:public }function ${2:FunctionName}($3)
+{
+ ${0:# code...}
+}]]></text>
+ <description>function ..</description>
+ <tag>function</tag>
+ </snippet>
+ <snippet id="$-3">
+ <text><![CDATA[\$_GET['${1:variable}']]]></text>
+ <description>GET['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="globals">
+ <text><![CDATA[\$GLOBALS['${1:variable}']${2: =} ${3:something} ${4:;}]]></text>
+ <description>$GLOBALS['..']</description>
+ <tag>globals</tag>
+ </snippet>
+ <snippet id="if?">
+ <text><![CDATA[$${1:retVal} = (${2:condition}) ? ${3:a} : ${4:b};]]></text>
+ <description>$.. =</description>
+ <tag>iff</tag>
+ </snippet>
+ <snippet id="ifelse">
+ <text><![CDATA[if (${1:condition})
+{
+ ${2:# code...}
+}
+else
+{
+ ${3:# code...}
+}
+$0]]></text>
+ <description>if .. else ..</description>
+ <tag>ifelse</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if (${1:condition})
+{
+ # code...
+ $0
+}]]></text>
+ <description>if ..</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="incl1">
+ <text><![CDATA[include_once('${1:file}');$0]]></text>
+ <description>include_once</description>
+ <tag>inclo</tag>
+ </snippet>
+ <snippet id="incl">
+ <text><![CDATA[include('${1:file}');$0]]></text>
+ <description>include</description>
+ <tag>incl</tag>
+ </snippet>
+ <snippet id="array">
+ <text><![CDATA[$${1:arrayName} = array('$2'${3:,});]]></text>
+ <description>$.. = array</description>
+ <tag>array</tag>
+ </snippet>
+ <snippet id="php">
+ <text><![CDATA[<?php
+
+ $0
+
+?>]]></text>
+ <description>&lt;?php .. ?&gt;</description>
+ <tag>php</tag>
+ </snippet>
+ <snippet id="$-4">
+ <text><![CDATA[\$_POST['${1:variable}']]]></text>
+ <description>POST['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="print">
+ <text><![CDATA[print "${1:string}"${2: . };]]></text>
+ <description>print ".."</description>
+ <tag>print</tag>
+ </snippet>
+ <snippet id="$-5">
+ <text><![CDATA[\$_REQUEST['${1:variable}']]]></text>
+ <description>REQUEST['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="req1">
+ <text><![CDATA[require_once('${1:file}');]]></text>
+ <description>require_once</description>
+ <tag>reqo</tag>
+ </snippet>
+ <snippet id="req">
+ <text><![CDATA[require('${1:file}');]]></text>
+ <description>require</description>
+ <tag>req</tag>
+ </snippet>
+ <snippet id="$-6">
+ <text><![CDATA[\$_SERVER['${1:variable}']]]></text>
+ <description>SERVER['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-7">
+ <text><![CDATA[\$_SESSION['${1:variable}']]]></text>
+ <description>SESSION['..']</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="case">
+ <text><![CDATA[case '${1:variable}':
+ # code...
+ $0
+break;]]></text>
+ <description>case ..</description>
+ <tag>case</tag>
+ </snippet>
+ <snippet id="switch">
+ <text><![CDATA[switch (${1:variable})
+{
+ case '${2:value}':
+ ${3:# code...}
+ break;
+
+ $0
+
+ default:
+ ${4:# code...}
+ break;
+}]]></text>
+ <description>switch ..</description>
+ <tag>switch</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while (${1:$a <= 10})
+{
+ # code...
+ $0
+}]]></text>
+ <description>while ..</description>
+ <tag>while</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/python.xml b/plugins/snippets/data/python.xml
new file mode 100755
index 00000000..a25617b8
--- /dev/null
+++ b/plugins/snippets/data/python.xml
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Python">
+ <snippet id="py">
+ <text><![CDATA[#!/usr/bin/env python
+#-*- coding:utf-8 -*-
+
+$0]]></text>
+ <description>#!/usr/bin/env python</description>
+ <tag>py</tag>
+ </snippet>
+ <snippet id="def">
+ <text><![CDATA[def ${1:fname}(${2:self}):
+ ${3:pass}]]></text>
+ <description>New Function</description>
+ <tag>def</tag>
+ </snippet>
+ <snippet id="doc">
+ <text><![CDATA["""
+ $1
+"""
+$0]]></text>
+ <description>doc string</description>
+ <tag>doc</tag>
+ </snippet>
+ <snippet id="get">
+ <text><![CDATA[def get$1(self): return self._$1]]></text>
+ <description>New Get Method</description>
+ <tag>get</tag>
+ </snippet>
+ <snippet id="class">
+ <text><![CDATA[class ${1:ClassName} (${2:object}):
+
+ def __init__(self${3:,}):
+ ${4:pass}
+
+$0]]></text>
+ <description>New Class</description>
+ <tag>class</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for ${1:i} in ${2:xrange}(${3:count}):
+ $0]]></text>
+ <description>for loop</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="from">
+ <text><![CDATA[from $1 import $2
+$0]]></text>
+ <description>from</description>
+ <tag>from</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if ${1:condition}:
+ $0]]></text>
+ <description>if</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="elif">
+ <text><![CDATA[elif ${1:condition}:
+ $0]]></text>
+ <description>elif</description>
+ <tag>elif</tag>
+ </snippet>
+ <snippet id="else">
+ <text><![CDATA[else:
+ $0]]></text>
+ <description>else</description>
+ <tag>else</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while ${1:condition}:
+ $0]]></text>
+ <tag>while</tag>
+ <description>while loop</description>
+ </snippet>
+ <snippet id="insert">
+ <text><![CDATA["${1:$GEDIT_SELECTED_TEXT}"]]></text>
+ <accelerator><![CDATA[<Control>2]]></accelerator>
+ <description>Inside String: Insert "…"</description>
+ </snippet>
+ <snippet id="insert-1">
+ <text><![CDATA['${1:$GEDIT_SELECTED_TEXT}']]></text>
+ <accelerator><![CDATA[<Control>apostrophe]]></accelerator>
+ <description>Inside String: Insert '…'</description>
+ </snippet>
+ <snippet id=".">
+ <text><![CDATA[self.]]></text>
+ <description>self</description>
+ <tag>.</tag>
+ </snippet>
+ <snippet id="set">
+ <text><![CDATA[def set$1(self, ${2:newValue}): self._$1 = $2]]></text>
+ <description>New Set Method</description>
+ <tag>set</tag>
+ </snippet>
+ <snippet id="try">
+ <text><![CDATA[try:
+ $1
+except ${2:Error}:
+ $0]]></text>
+ <tag>try</tag>
+ <description>Try... Except</description>
+ </snippet>
+ <snippet id="main">
+ <text><![CDATA[if __name__ == '__main__':
+ ${1:sys.exit(main())}
+
+$0]]></text>
+ <description>main</description>
+ <tag>main</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/ruby.xml b/plugins/snippets/data/ruby.xml
new file mode 100755
index 00000000..e68602f3
--- /dev/null
+++ b/plugins/snippets/data/ruby.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Ruby">
+ <snippet id="forin">
+ <text><![CDATA[for ${1:element} in ${2:collection}
+ ${1:element}.$0
+end]]></text>
+ <description>for .. in .. end</description>
+ <tag>forin</tag>
+ </snippet>
+ <snippet id="inject">
+ <text><![CDATA[inject(${1:object}) { |${2:injection}, ${3:element}| $0 }]]></text>
+ <description>inject object</description>
+ <tag>inject</tag>
+ </snippet>
+ <snippet id="reject">
+ <text><![CDATA[reject { |${1:element}| ${1:element}.$0 }]]></text>
+ <description>reject element</description>
+ <tag>reject</tag>
+ </snippet>
+ <snippet id="select">
+ <text><![CDATA[select { |${1:element}| ${1:element}.$0 }]]></text>
+ <description>select element</description>
+ <tag>select</tag>
+ </snippet>
+ <snippet id="ife">
+ <text><![CDATA[if ${1:condition}
+ $2
+else
+ $3
+end]]></text>
+ <description>if .. else .. end</description>
+ <tag>ife</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if ${1:condition}
+ $0
+end]]></text>
+ <description>if .. end</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="case">
+ <text><![CDATA[case ${1:object}
+ when ${2:condition}
+ $0
+end]]></text>
+ <description>case .. end</description>
+ <tag>case</tag>
+ </snippet>
+ <snippet id="begin">
+ <text><![CDATA[begin
+ $1
+rescue ${2:Exception} => ${3:e}
+ $0
+end]]></text>
+ <description>begin .. rescue .. end</description>
+ <tag>begin</tag>
+ </snippet>
+ <snippet id="class">
+ <text><![CDATA[class ${1:class_name}
+ $0
+end]]></text>
+ <description>class .. end</description>
+ <tag>class</tag>
+ </snippet>
+ <snippet id="collecto">
+ <text><![CDATA[collect do |${1:element}|
+ ${1:element}.$0
+end]]></text>
+ <description>collect element do</description>
+ <tag>collecto</tag>
+ </snippet>
+ <snippet id="collect">
+ <text><![CDATA[collect { |${1:element}| ${1:element}.$0 }]]></text>
+ <description>collect element</description>
+ <tag>collect</tag>
+ </snippet>
+ <snippet id="def">
+ <text><![CDATA[def ${1:method_name}
+ $0
+end]]></text>
+ <description>def .. end</description>
+ <tag>def</tag>
+ </snippet>
+ <snippet id="do">
+ <text><![CDATA[do
+ $0
+end]]></text>
+ <description>do .. end</description>
+ <tag>do</tag>
+ </snippet>
+ <snippet id="doo">
+ <text><![CDATA[do |${1:object}|
+ $0
+end]]></text>
+ <description>do |object| .. end</description>
+ <tag>doo</tag>
+ </snippet>
+ <snippet id="eacho">
+ <text><![CDATA[each do |${1:element}|
+ ${1:element}.$0
+end]]></text>
+ <description>each element do</description>
+ <tag>eacho</tag>
+ </snippet>
+ <snippet id="each">
+ <text><![CDATA[each { |${1:element}| ${1:element}.$0 }]]></text>
+ <description>each element</description>
+ <tag>each</tag>
+ </snippet>
+ <snippet id="each_with_indexo">
+ <text><![CDATA[each_with_index do |${1:element}, ${2:idx}|
+ ${1:element}.$0
+end]]></text>
+ <description>each_with_index do</description>
+ <tag>eachwithindexo</tag>
+ </snippet>
+ <snippet id="each_with_index">
+ <text><![CDATA[each_with_index { |${1:element}, ${2:idx}| ${1:element}.$0 }]]></text>
+ <description>each_with_index</description>
+ <tag>eachwithindex</tag>
+ </snippet>
+ <snippet id=":">
+ <text><![CDATA[:${1:key} => ${2:"value"}${3:, }]]></text>
+ <description>hash pair</description>
+ <tag>:</tag>
+ </snippet>
+ <snippet id="hashpointer">
+ <text><![CDATA[ => ]]></text>
+ <accelerator><![CDATA[<Shift><Alt>l]]></accelerator>
+ <description>hash pointer</description>
+ </snippet>
+ <snippet id="injecto">
+ <text><![CDATA[inject(${1:object}) do |${2:injection}, ${3:element}|
+ $0
+end]]></text>
+ <description>inject object do</description>
+ <tag>injecto</tag>
+ </snippet>
+ <snippet id="rejecto">
+ <text><![CDATA[reject do |${1:element}|
+ ${1:element}.$0
+end]]></text>
+ <description>reject element do</description>
+ <tag>rejecto</tag>
+ </snippet>
+ <snippet id="selecto">
+ <text><![CDATA[select do |${1:element}|
+ ${1:element}.$0
+end]]></text>
+ <description>select element do</description>
+ <tag>selecto</tag>
+ </snippet>
+ <snippet id="unless">
+ <text><![CDATA[unless ${1:condition}
+ $0
+end]]></text>
+ <description>unless</description>
+ <tag>unless</tag>
+ </snippet>
+ <snippet id="when">
+ <text><![CDATA[when ${1:condition}
+ $0]]></text>
+ <description>when</description>
+ <tag>when</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/sh.xml b/plugins/snippets/data/sh.xml
new file mode 100755
index 00000000..b8fc0a62
--- /dev/null
+++ b/plugins/snippets/data/sh.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="sh">
+ <snippet id="elif">
+ <text><![CDATA[elif [[ ${1:condition} ]]; then
+ $0]]></text>
+ <description>elif ..</description>
+ <tag>elif</tag>
+ </snippet>
+ <snippet id="case">
+ <text><![CDATA[case ${1:choice} in
+${2:first})
+ $3
+ ;;
+*)
+ $4
+ ;;
+esac]]></text>
+ <description>case ..</description>
+ <tag>case</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for (( ${1:i = 0}; ${2:i < 10}; ${3:i++} )); do
+ $0
+done]]></text>
+ <description>for .. done</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if [[ ${1:condition} ]]; then
+ $0
+fi]]></text>
+ <description>if .. then</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="sh">
+ <text><![CDATA[#!/bin/sh
+$0]]></text>
+ <description>#!/bin/sh</description>
+ <tag>sh</tag>
+ </snippet>
+ <snippet id="bash">
+ <text><![CDATA[#!/bin/bash
+$0]]></text>
+ <description>#!/bin/bash</description>
+ <tag>bash</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/snippets.xml b/plugins/snippets/data/snippets.xml
new file mode 100755
index 00000000..ee405e6d
--- /dev/null
+++ b/plugins/snippets/data/snippets.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="snippets">
+ <snippet id="simple">
+ <text><![CDATA[\${${1:n:default}}]]></text>
+ <description>Simple Placeholder</description>
+ <tag>simple</tag>
+ </snippet>
+ <snippet id="simple-fallback">
+ <text><![CDATA[\${${1:n:}[${2:default1,default2}]}]]></text>
+ <description>Simple Fallback Placeholder</description>
+ <tag>simplef</tag>
+ </snippet>
+ <snippet id="shell">
+ <text><![CDATA[\$(${1:n:} ${2:shell code})]]></text>
+ <description>Shell Placeholder</description>
+ <tag>shell</tag>
+ </snippet>
+ <snippet id="python">
+ <text><![CDATA[\$<${1:n:} ${2:[refs]:} return 'python code' >]]></text>
+ <description>Python Placeholder</description>
+ <tag>python</tag>
+ </snippet>
+ <snippet id="regex">
+ <text><![CDATA[\${${1:n:} ${2:input}/${3:regex-pattern}/${4:replacement}/${5:modifiers}}]]></text>
+ <description>Regular Expression Placeholder</description>
+ <tag>regex</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_PATH">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_PATH]]></text>
+ <description>Gedit Current Document Path Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_NAME">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_NAME]]></text>
+ <description>Gedit Current Document Name Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_URI">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_URI]]></text>
+ <description>Gedit Current Document Uri Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_SCHEME">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_SCHEME]]></text>
+ <description>Gedit Current Document Scheme Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_DOCUMENT_TYPE">
+ <text><![CDATA[\$GEDIT_CURRENT_DOCUMENT_TYPE]]></text>
+ <description>Gedit Current Document Type Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DOCUMENTS_URI">
+ <text><![CDATA[\$GEDIT_DOCUMENTS_URI]]></text>
+ <description>Gedit Documents Uri Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DOCUMENTS_PATH">
+ <text><![CDATA[\$GEDIT_DOCUMENTS_PATH]]></text>
+ <description>Gedit Documents Path Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-SELECTED_TEXT">
+ <text><![CDATA[\$GEDIT_SELECTED_TEXT]]></text>
+ <description>Gedit Selected Text Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_WORD">
+ <text><![CDATA[\$GEDIT_CURRENT_WORD]]></text>
+ <description>Gedit Current Word Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_LINE">
+ <text><![CDATA[\$GEDIT_CURRENT_LINE]]></text>
+ <description>Gedit Current Line Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-CURRENT_LINE_NUMBER">
+ <text><![CDATA[\$GEDIT_CURRENT_LINE_NUMBER]]></text>
+ <description>Gedit Current Line Number Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DROP_FILENAME">
+ <text><![CDATA[\$GEDIT_DROP_FILENAME]]></text>
+ <description>Gedit Drop Filename Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DROP_REL_FILENAME">
+ <text><![CDATA[\$GEDIT_DROP_REL_FILENAME]]></text>
+ <description>Gedit Drop Relative Filename Variable</description>
+ <tag>$</tag>
+ </snippet>
+ <snippet id="$-DROP_MIME_TYPE">
+ <text><![CDATA[\$GEDIT_DROP_MIME_TYPE]]></text>
+ <description>Gedit Drop Mime Type Variable</description>
+ <tag>$</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/tcl.xml b/plugins/snippets/data/tcl.xml
new file mode 100755
index 00000000..73a50c0a
--- /dev/null
+++ b/plugins/snippets/data/tcl.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="Tcl">
+ <snippet id="foreach">
+ <text><![CDATA[foreach ${1:var} ${2:$list} {
+ ${3}
+}
+]]></text>
+ <description>foreach...</description>
+ <tag>foreach</tag>
+ </snippet>
+ <snippet id="for">
+ <text><![CDATA[for {${1:set i 0}} {${2:$i < $n}} {${3:incr i}} {
+ ${4}
+}
+]]></text>
+ <description>for...</description>
+ <tag>for</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[if {${1:condition}} {
+ ${2}
+}
+]]></text>
+ <description>if...</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="proc">
+ <text><![CDATA[proc ${1:name} {${2:args}} \
+{
+ ${3}
+}
+]]></text>
+ <description>proc...</description>
+ <tag>proc</tag>
+ </snippet>
+ <snippet id="switch">
+ <text><![CDATA[switch ${1:-exact} -- ${2:$var} {
+ ${3:match} {
+ ${4}
+ }
+ default {${5}}
+}
+]]></text>
+ <description>switch...</description>
+ <tag>switch</tag>
+ </snippet>
+ <snippet id="while">
+ <text><![CDATA[while {${1:condition}} {
+ ${2}
+}
+]]></text>
+ <description>while...</description>
+ <tag>while</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/xml.xml b/plugins/snippets/data/xml.xml
new file mode 100755
index 00000000..a53d565b
--- /dev/null
+++ b/plugins/snippets/data/xml.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="XML">
+ <snippet id="&quot;">
+ <text><![CDATA[<${1:name} ${2:attr}="${3:value}">$0</${1}>]]></text>
+ <description>Long Attribute Tag</description>
+ <tag>"</tag>
+ </snippet>
+ <snippet id="&lt;">
+ <text><![CDATA[<${1:name}>$0</${1}>
+
+]]></text>
+ <description>Long Tag</description>
+ <tag>&lt;</tag>
+ </snippet>
+ <snippet id="&gt;">
+ <text><![CDATA[<${1:name} />]]></text>
+ <description>Short Tag</description>
+ <tag>&gt;</tag>
+ </snippet>
+ <snippet id="cdata">
+ <text><![CDATA[<![CDATA[$0]]]]><![CDATA[>]]></text>
+ <tag>cdata</tag>
+ <description>CDATA</description>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/data/xslt.xml b/plugins/snippets/data/xslt.xml
new file mode 100755
index 00000000..0ff5cc15
--- /dev/null
+++ b/plugins/snippets/data/xslt.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<snippets language="xslt">
+ <snippet id="stylesheet">
+ <text><![CDATA[<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+$0
+</xsl:stylesheet>
+]]></text>
+ <description>StyleSheet</description>
+ <tag>stylesheet</tag>
+ </snippet>
+ <snippet id="include">
+ <text><![CDATA[<xsl:include href="$1"/>
+]]></text>
+ <description>Include</description>
+ <tag>inc</tag>
+ </snippet>
+ <snippet id="import">
+ <text><![CDATA[<xsl:import href="$1"/>
+]]></text>
+ <description>Import</description>
+ <tag>imp</tag>
+ </snippet>
+ <snippet id="param">
+ <text><![CDATA[<xsl:param name="$1"/>
+]]></text>
+ <description>Parameter</description>
+ <tag>param</tag>
+ </snippet>
+ <snippet id="template">
+ <text><![CDATA[<xsl:template ${1:[match,name]}="$2" ${3:mode=""}>
+ $0
+</xsl:template>
+]]></text>
+ <description>Template</description>
+ <tag>templ</tag>
+ </snippet>
+ <snippet id="variable-1">
+ <text><![CDATA[<xsl:variable name="$1">
+ $0
+</xsl:variable>
+]]></text>
+ <description>Variable</description>
+ <tag>var</tag>
+ </snippet>
+ <snippet id="variable-2">
+ <text><![CDATA[<xsl:variable name="$1" select="$2"/>
+$0]]></text>
+ <description>Variable with Select Attribute</description>
+ <tag>var</tag>
+ </snippet>
+ <snippet id="choose">
+ <text><![CDATA[<xsl:choose>
+ <xsl:when test="$1">
+ $2
+ </xsl:when>
+ $3
+</xsl:choose>
+]]></text>
+ <description>Choose</description>
+ <tag>choose</tag>
+ </snippet>
+ <snippet id="when">
+ <text><![CDATA[<xsl:when test="$1">
+ $2
+</xsl:when>
+$0]]></text>
+ <description>When</description>
+ <tag>when</tag>
+ </snippet>
+ <snippet id="otherwise">
+ <text><![CDATA[<xsl:otherwise>
+ $1
+</xsl:otherwise>
+$0]]></text>
+ <description>Otherwise</description>
+ <tag>otherwise</tag>
+ </snippet>
+ <snippet id="if">
+ <text><![CDATA[<xsl:if test="$1">
+ $2
+</xsl:if>
+$0]]></text>
+ <description>If</description>
+ <tag>if</tag>
+ </snippet>
+ <snippet id="value-of">
+ <text><![CDATA[<xsl:value-of select="$1"/>
+]]></text>
+ <description>Value of</description>
+ <tag>val</tag>
+ </snippet>
+ <snippet id="element">
+ <text><![CDATA[<xsl:element name="$1">
+</xsl:element>
+$0]]></text>
+ <description>Element</description>
+ <tag>elem</tag>
+ </snippet>
+ <snippet id="attribute">
+ <text><![CDATA[<xsl:attribute name="$1">$2</xsl:attribute>
+$0]]></text>
+ <description>Attribute</description>
+ <tag>attr</tag>
+ </snippet>
+ <snippet id="text">
+ <text><![CDATA[<xsl:text>${1:$GEDIT_SELECTED_TEXT}</xsl:text>
+]]></text>
+ <description>Text</description>
+ <tag>text</tag>
+ </snippet>
+ <snippet id="comment">
+ <text><![CDATA[<xsl:comment>${1:$GEDIT_SELECTED_TEXT}</xsl:comment>
+]]></text>
+ <description>Comment</description>
+ <tag>comment</tag>
+ </snippet>
+ <snippet id="call-template">
+ <text><![CDATA[<xsl:call-template name="$1"/>
+]]></text>
+ <description>Call Template</description>
+ <tag>call</tag>
+ </snippet>
+ <snippet id="apply-templates">
+ <text><![CDATA[<xsl:apply-templates mode="$1" select="$2"/>
+$0]]></text>
+ <description>Apply Templates</description>
+ <tag>applyt</tag>
+ </snippet>
+ <snippet id="apply-imports">
+ <text><![CDATA[<xsl:apply-imports/>
+]]></text>
+ <description>Apply Imports</description>
+ <tag>applyimp</tag>
+ </snippet>
+ <snippet id="with-param">
+ <text><![CDATA[<xsl:with-param name="$1">
+ $2
+</xsl:with-param>
+$0]]></text>
+ <description>With Param</description>
+ <tag>with</tag>
+ </snippet>
+</snippets>
diff --git a/plugins/snippets/snippets.gedit-plugin.desktop.in b/plugins/snippets/snippets.gedit-plugin.desktop.in
new file mode 100755
index 00000000..dc3c8281
--- /dev/null
+++ b/plugins/snippets/snippets.gedit-plugin.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Plugin]
+Loader=python
+Module=snippets
+IAge=2
+_Name=Snippets
+_Description=Insert often-used pieces of text in a fast way
+Authors=Jesse van den Kieboom <[email protected]>
+Copyright=Copyright © 2005 Jesse van den Kieboom
+Website=http://www.gedit.org
diff --git a/plugins/snippets/snippets/Completion.py b/plugins/snippets/snippets/Completion.py
new file mode 100755
index 00000000..d83915a5
--- /dev/null
+++ b/plugins/snippets/snippets/Completion.py
@@ -0,0 +1,165 @@
+import gtksourceview2 as gsv
+import gobject
+import gedit
+import gtk
+
+from Library import Library
+from LanguageManager import get_language_manager
+from Snippet import Snippet
+
+class Proposal(gobject.GObject, gsv.CompletionProposal):
+ def __init__(self, snippet):
+ gobject.GObject.__init__(self)
+ self._snippet = Snippet(snippet)
+
+ def snippet(self):
+ return self._snippet.data
+
+ # Interface implementation
+ def do_get_markup(self):
+ return self._snippet.display()
+
+ def do_get_info(self):
+ return self._snippet.data['text']
+
+class Provider(gobject.GObject, gsv.CompletionProvider):
+ def __init__(self, name, language_id, handler):
+ gobject.GObject.__init__(self)
+
+ self.name = name
+ self.info_widget = None
+ self.proposals = []
+ self.language_id = language_id
+ self.handler = handler
+ self.info_widget = None
+ self.mark = None
+
+ theme = gtk.icon_theme_get_default()
+ w, h = gtk.icon_size_lookup(gtk.ICON_SIZE_MENU)
+
+ self.icon = theme.load_icon(gtk.STOCK_JUSTIFY_LEFT, w, 0)
+
+ def __del__(self):
+ if self.mark:
+ self.mark.get_buffer().delete_mark(self.mark)
+
+ def set_proposals(self, proposals):
+ self.proposals = proposals
+
+ def mark_position(self, it):
+ if not self.mark:
+ self.mark = it.get_buffer().create_mark(None, it, True)
+ else:
+ self.mark.get_buffer().move_mark(self.mark, it)
+
+ def get_word(self, context):
+ it = context.get_iter()
+
+ if it.starts_word() or it.starts_line() or not it.ends_word():
+ return None
+
+ start = it.copy()
+
+ if start.backward_word_start():
+ self.mark_position(start)
+ return start.get_text(it)
+ else:
+ return None
+
+ def do_get_start_iter(self, context, proposal):
+ if not self.mark or self.mark.get_deleted():
+ return None
+
+ return self.mark.get_buffer().get_iter_at_mark(self.mark)
+
+ def do_match(self, context):
+ return True
+
+ def get_proposals(self, word):
+ if self.proposals:
+ proposals = self.proposals
+ else:
+ proposals = Library().get_snippets(None)
+
+ if self.language_id:
+ proposals += Library().get_snippets(self.language_id)
+
+ # Filter based on the current word
+ if word:
+ proposals = filter(lambda x: x['tag'].startswith(word), proposals)
+
+ return map(lambda x: Proposal(x), proposals)
+
+ def do_populate(self, context):
+ proposals = self.get_proposals(self.get_word(context))
+ context.add_proposals(self, proposals, True)
+
+ def do_get_name(self):
+ return self.name
+
+ def do_activate_proposal(self, proposal, piter):
+ return self.handler(proposal, piter)
+
+ def do_get_info_widget(self, proposal):
+ if not self.info_widget:
+ view = gedit.View(gedit.Document())
+ manager = get_language_manager()
+
+ lang = manager.get_language('snippets')
+ view.get_buffer().set_language(lang)
+
+ sw = gtk.ScrolledWindow()
+ sw.add(view)
+
+ self.info_view = view
+ self.info_widget = sw
+
+ return self.info_widget
+
+ def do_update_info(self, proposal, info):
+ buf = self.info_view.get_buffer()
+
+ buf.set_text(proposal.get_info())
+ buf.move_mark(buf.get_insert(), buf.get_start_iter())
+ buf.move_mark(buf.get_selection_bound(), buf.get_start_iter())
+ self.info_view.scroll_to_iter(buf.get_start_iter(), False)
+
+ info.set_sizing(-1, -1, False, False)
+ info.process_resize()
+
+ def do_get_icon(self):
+ return self.icon
+
+ def do_get_activation(self):
+ return gsv.COMPLETION_ACTIVATION_USER_REQUESTED
+
+class Defaults(gobject.GObject, gsv.CompletionProvider):
+ def __init__(self, handler):
+ gobject.GObject.__init__(self)
+
+ self.handler = handler
+ self.proposals = []
+
+ def set_defaults(self, defaults):
+ self.proposals = []
+
+ for d in defaults:
+ self.proposals.append(gsv.CompletionItem(d))
+
+ def do_get_name(self):
+ return ""
+
+ def do_activate_proposal(self, proposal, piter):
+ return self.handler(proposal, piter)
+
+ def do_populate(self, context):
+ context.add_proposals(self, self.proposals, True)
+
+ def do_get_activation(self):
+ return gsv.COMPLETION_ACTIVATION_NONE
+
+gobject.type_register(Proposal)
+gobject.type_register(Provider)
+gobject.type_register(Defaults)
+
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/Document.py b/plugins/snippets/snippets/Document.py
new file mode 100755
index 00000000..da166668
--- /dev/null
+++ b/plugins/snippets/snippets/Document.py
@@ -0,0 +1,1089 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <[email protected]>
+#
+# 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 gtk
+from gtk import gdk
+import gio
+import gedit
+import gtksourceview2 as gsv
+import gobject
+
+from Library import Library
+from Snippet import Snippet
+from Placeholder import *
+import Completion
+
+class DynamicSnippet(dict):
+ def __init__(self, text):
+ self['text'] = text
+ self.valid = True
+
+class Document:
+ TAB_KEY_VAL = (gtk.keysyms.Tab, \
+ gtk.keysyms.ISO_Left_Tab)
+ SPACE_KEY_VAL = (gtk.keysyms.space,)
+
+ def __init__(self, instance, view):
+ self.view = None
+ self.instance = instance
+
+ self.placeholders = []
+ self.active_snippets = []
+ self.active_placeholder = None
+ self.signal_ids = {}
+
+ self.ordered_placeholders = []
+ self.update_placeholders = []
+ self.jump_placeholders = []
+ self.language_id = 0
+ self.timeout_update_id = 0
+
+ self.provider = Completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated)
+ self.defaults_provider = Completion.Defaults(self.on_default_activated)
+
+ # Always have a reference to the global snippets
+ Library().ref(None)
+ self.set_view(view)
+
+ # Stop controlling the view. Remove all active snippets, remove references
+ # to the view and the plugin instance, disconnect all signal handlers
+ def stop(self):
+ if self.timeout_update_id != 0:
+ gobject.source_remove(self.timeout_update_id)
+ self.timeout_update_id = 0
+ del self.update_placeholders[:]
+ del self.jump_placeholders[:]
+
+ # Always release the reference to the global snippets
+ Library().unref(None)
+ self.set_view(None)
+ self.instance = None
+ self.active_placeholder = None
+
+ def disconnect_signal(self, obj, signal):
+ if (obj, signal) in self.signal_ids:
+ obj.disconnect(self.signal_ids[(obj, signal)])
+ del self.signal_ids[(obj, signal)]
+
+ def connect_signal(self, obj, signal, cb):
+ self.disconnect_signal(obj, signal)
+ self.signal_ids[(obj, signal)] = obj.connect(signal, cb)
+
+ def connect_signal_after(self, obj, signal, cb):
+ self.disconnect_signal(obj, signal)
+ self.signal_ids[(obj, signal)] = obj.connect_after(signal, cb)
+
+ # Set the view to be controlled. Installs signal handlers and sets current
+ # language. If there is already a view set this function will first remove
+ # all currently active snippets and disconnect all current signals. So
+ # self.set_view(None) will effectively remove all the control from the
+ # current view
+ def _set_view(self, view):
+ if self.view:
+ buf = self.view.get_buffer()
+
+ # Remove signals
+ signals = {self.view: ('key-press-event', 'destroy',
+ 'notify::editable', 'drag-data-received', 'expose-event'),
+ buf: ('notify::language', 'changed', 'cursor-moved', 'insert-text'),
+ self.view.get_completion(): ('hide',)}
+
+ for obj, sig in signals.items():
+ for s in sig:
+ self.disconnect_signal(obj, s)
+
+ # Remove all active snippets
+ for snippet in list(self.active_snippets):
+ self.deactivate_snippet(snippet, True)
+
+ completion = self.view.get_completion()
+ completion.remove_provider(self.provider)
+ completion.remove_provider(self.defaults_provider)
+
+ self.view = view
+
+ if view != None:
+ buf = view.get_buffer()
+
+ self.connect_signal(view, 'destroy', self.on_view_destroy)
+
+ if view.get_editable():
+ self.connect_signal(view, 'key-press-event', self.on_view_key_press)
+
+ self.connect_signal(buf, 'notify::language', self.on_notify_language)
+ self.connect_signal(view, 'notify::editable', self.on_notify_editable)
+ self.connect_signal(view, 'drag-data-received', self.on_drag_data_received)
+ self.connect_signal_after(view, 'expose-event', self.on_expose_event)
+
+ self.update_language()
+
+ completion = view.get_completion()
+ completion.add_provider(self.provider)
+
+ completion.add_provider(self.defaults_provider)
+
+ self.connect_signal(completion, 'hide', self.on_completion_hide)
+ elif self.language_id != 0:
+ langid = self.language_id
+
+ self.language_id = None;
+ self.provider.language_id = self.language_id
+
+ if self.instance:
+ self.instance.language_changed(self)
+
+ Library().unref(langid)
+
+ def set_view(self, view):
+ if view == self.view:
+ return
+
+ self._set_view(view)
+
+ # Call this whenever the language in the view changes. This makes sure that
+ # the correct language is used when finding snippets
+ def update_language(self):
+ lang = self.view.get_buffer().get_language()
+
+ if lang == None and self.language_id == None:
+ return
+ elif lang and lang.get_id() == self.language_id:
+ return
+
+ langid = self.language_id
+
+ if lang:
+ self.language_id = lang.get_id()
+ else:
+ self.language_id = None
+
+ if self.instance:
+ self.instance.language_changed(self)
+
+ if langid != 0:
+ Library().unref(langid)
+
+ Library().ref(self.language_id)
+ self.provider.language_id = self.language_id
+
+ def accelerator_activate(self, keyval, mod):
+ if not self.view or not self.view.get_editable():
+ return False
+
+ accelerator = gtk.accelerator_name(keyval, mod)
+ snippets = Library().from_accelerator(accelerator, \
+ self.language_id)
+
+ snippets_debug('Accel!')
+
+ if len(snippets) == 0:
+ return False
+ elif len(snippets) == 1:
+ self.apply_snippet(snippets[0])
+ else:
+ # Do the fancy completion dialog
+ self.provider.set_proposals(snippets)
+ self.view.show_completion((self,))
+
+ return True
+
+ def first_snippet_inserted(self):
+ buf = self.view.get_buffer()
+
+ self.connect_signal(buf, 'changed', self.on_buffer_changed)
+ self.connect_signal(buf, 'cursor-moved', self.on_buffer_cursor_moved)
+ self.connect_signal_after(buf, 'insert-text', self.on_buffer_insert_text)
+
+ def last_snippet_removed(self):
+ buf = self.view.get_buffer()
+ self.disconnect_signal(buf, 'changed')
+ self.disconnect_signal(buf, 'cursor-moved')
+ self.disconnect_signal(buf, 'insert-text')
+
+ def current_placeholder(self):
+ buf = self.view.get_buffer()
+
+ piter = buf.get_iter_at_mark(buf.get_insert())
+ found = []
+
+ for placeholder in self.placeholders:
+ begin = placeholder.begin_iter()
+ end = placeholder.end_iter()
+
+ if piter.compare(begin) >= 0 and piter.compare(end) <= 0:
+ found.append(placeholder)
+
+ if self.active_placeholder in found:
+ return self.active_placeholder
+ elif len(found) > 0:
+ return found[0]
+ else:
+ return None
+
+ def advance_placeholder(self, direction):
+ # Returns (CurrentPlaceholder, NextPlaceholder), depending on direction
+ buf = self.view.get_buffer()
+
+ piter = buf.get_iter_at_mark(buf.get_insert())
+ found = current = next = None
+ length = len(self.placeholders)
+
+ placeholders = list(self.placeholders)
+
+ if self.active_placeholder:
+ begin = self.active_placeholder.begin_iter()
+ end = self.active_placeholder.end_iter()
+
+ if piter.compare(begin) >= 0 and piter.compare(end) <= 0:
+ current = self.active_placeholder
+ currentIndex = placeholders.index(self.active_placeholder)
+
+ if direction == 1:
+ # w = piter, x = begin, y = end, z = found
+ nearest = lambda w, x, y, z: (w.compare(x) <= 0 and (not z or \
+ x.compare(z.begin_iter()) < 0))
+ indexer = lambda x: x < length - 1
+ else:
+ # w = piter, x = begin, y = end, z = prev
+ nearest = lambda w, x, y, z: (w.compare(x) >= 0 and (not z or \
+ x.compare(z.begin_iter()) >= 0))
+ indexer = lambda x: x > 0
+
+ for index in range(0, length):
+ placeholder = placeholders[index]
+ begin = placeholder.begin_iter()
+ end = placeholder.end_iter()
+
+ # Find the nearest placeholder
+ if nearest(piter, begin, end, found):
+ foundIndex = index
+ found = placeholder
+
+ # Find the current placeholder
+ if piter.compare(begin) >= 0 and \
+ piter.compare(end) <= 0 and \
+ current == None:
+ currentIndex = index
+ current = placeholder
+
+ if current and current != found and \
+ (current.begin_iter().compare(found.begin_iter()) == 0 or \
+ current.end_iter().compare(found.begin_iter()) == 0) and \
+ self.active_placeholder and \
+ current.begin_iter().compare(self.active_placeholder.begin_iter()) == 0:
+ # if current and found are at the same place, then
+ # resolve the 'hugging' problem
+ current = self.active_placeholder
+ currentIndex = placeholders.index(current)
+
+ if current:
+ if indexer(currentIndex):
+ next = placeholders[currentIndex + direction]
+ elif found:
+ next = found
+ elif length > 0:
+ next = self.placeholders[0]
+
+ return current, next
+
+ def next_placeholder(self):
+ return self.advance_placeholder(1)
+
+ def previous_placeholder(self):
+ return self.advance_placeholder(-1)
+
+ def cursor_on_screen(self):
+ buf = self.view.get_buffer()
+ self.view.scroll_mark_onscreen(buf.get_insert())
+
+ def set_active_placeholder(self, placeholder):
+ self.active_placeholder = placeholder
+
+ def goto_placeholder(self, current, next):
+ last = None
+
+ if current:
+ # Signal this placeholder to end action
+ self.view.get_completion().hide()
+ current.leave()
+
+ if current.__class__ == PlaceholderEnd:
+ last = current
+
+ self.set_active_placeholder(next)
+
+ if next:
+ next.enter()
+
+ if next.__class__ == PlaceholderEnd:
+ last = next
+ elif len(next.defaults) > 1 and next.get_text() == next.default:
+ self.defaults_provider.set_defaults(next.defaults)
+
+ cm = self.view.get_completion()
+ cm.show([self.defaults_provider], cm.create_context())
+
+ if last:
+ # This is the end of the placeholder, remove the snippet etc
+ for snippet in list(self.active_snippets):
+ if snippet.placeholders[0] == last:
+ self.deactivate_snippet(snippet)
+ break
+
+ self.cursor_on_screen()
+
+ return next != None
+
+ def skip_to_next_placeholder(self):
+ (current, next) = self.next_placeholder()
+ return self.goto_placeholder(current, next)
+
+ def skip_to_previous_placeholder(self):
+ (current, prev) = self.previous_placeholder()
+ return self.goto_placeholder(current, prev)
+
+ def env_get_selected_text(self, buf):
+ bounds = buf.get_selection_bounds()
+
+ if bounds:
+ return buf.get_text(bounds[0], bounds[1])
+ else:
+ return ''
+
+ def env_get_current_word(self, buf):
+ start, end = buffer_word_boundary(buf)
+
+ return buf.get_text(start, end)
+
+ def env_get_current_line(self, buf):
+ start, end = buffer_line_boundary(buf)
+
+ return buf.get_text(start, end)
+
+ def env_get_current_line_number(self, buf):
+ start, end = buffer_line_boundary(buf)
+
+ return str(start.get_line() + 1)
+
+ def env_get_document_uri(self, buf):
+ location = buf.get_location()
+
+ if location:
+ return location.get_uri()
+ else:
+ return ''
+
+ def env_get_document_name(self, buf):
+ location = buf.get_location()
+
+ if location:
+ return location.get_basename()
+ else:
+ return ''
+
+ def env_get_document_scheme(self, buf):
+ location = buf.get_location()
+
+ if location:
+ return location.get_uri_scheme()
+ else:
+ return ''
+
+ def env_get_document_path(self, buf):
+ location = buf.get_location()
+
+ if location:
+ return location.get_path()
+ else:
+ return ''
+
+ def env_get_document_dir(self, buf):
+ location = buf.get_location()
+
+ if location:
+ return location.get_parent().get_path() or ''
+ else:
+ return ''
+
+ def env_get_document_type(self, buf):
+ typ = buf.get_mime_type()
+
+ if typ:
+ return typ
+ else:
+ return ''
+
+ def env_get_documents_uri(self, buf):
+ toplevel = self.view.get_toplevel()
+
+ if isinstance(toplevel, gedit.Window):
+ documents_uri = [doc.get_location().get_uri()
+ for doc in toplevel.get_documents()
+ if doc.get_location() is not None]
+ else:
+ documents_uri = []
+
+ return ' '.join(documents_uri)
+
+ def env_get_documents_path(self, buf):
+ toplevel = self.view.get_toplevel()
+
+ if isinstance(toplevel, gedit.Window):
+ documents_location = [doc.get_location()
+ for doc in toplevel.get_documents()
+ if doc.get_location() is not None]
+
+ documents_path = [location.get_path()
+ for location in documents_location
+ if gedit.utils.uri_has_file_scheme(location.get_uri())]
+ else:
+ documents_path = []
+
+ return ' '.join(documents_path)
+
+ def update_environment(self):
+ buf = self.view.get_buffer()
+
+ variables = {'GEDIT_SELECTED_TEXT': self.env_get_selected_text,
+ 'GEDIT_CURRENT_WORD': self.env_get_current_word,
+ 'GEDIT_CURRENT_LINE': self.env_get_current_line,
+ 'GEDIT_CURRENT_LINE_NUMBER': self.env_get_current_line_number,
+ 'GEDIT_CURRENT_DOCUMENT_URI': self.env_get_document_uri,
+ 'GEDIT_CURRENT_DOCUMENT_NAME': self.env_get_document_name,
+ 'GEDIT_CURRENT_DOCUMENT_SCHEME': self.env_get_document_scheme,
+ 'GEDIT_CURRENT_DOCUMENT_PATH': self.env_get_document_path,
+ 'GEDIT_CURRENT_DOCUMENT_DIR': self.env_get_document_dir,
+ 'GEDIT_CURRENT_DOCUMENT_TYPE': self.env_get_document_type,
+ 'GEDIT_DOCUMENTS_URI': self.env_get_documents_uri,
+ 'GEDIT_DOCUMENTS_PATH': self.env_get_documents_path,
+ }
+
+ for var in variables:
+ os.environ[var] = variables[var](buf)
+
+ def uses_current_word(self, snippet):
+ matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_WORD', snippet['text'])
+
+ for match in matches:
+ if len(match) % 2 == 0:
+ return True
+
+ return False
+
+ def uses_current_line(self, snippet):
+ matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_LINE', snippet['text'])
+
+ for match in matches:
+ if len(match) % 2 == 0:
+ return True
+
+ return False
+
+ def apply_snippet(self, snippet, start = None, end = None):
+ if not snippet.valid:
+ return False
+
+ buf = self.view.get_buffer()
+ s = Snippet(snippet)
+
+ if not start:
+ start = buf.get_iter_at_mark(buf.get_insert())
+
+ if not end:
+ end = buf.get_iter_at_mark(buf.get_selection_bound())
+
+ if start.equal(end) and self.uses_current_word(s):
+ # There is no tab trigger and no selection and the snippet uses
+ # the current word. Set start and end to the word boundary so that
+ # it will be removed
+ start, end = buffer_word_boundary(buf)
+ elif start.equal(end) and self.uses_current_line(s):
+ # There is no tab trigger and no selection and the snippet uses
+ # the current line. Set start and end to the line boundary so that
+ # it will be removed
+ start, end = buffer_line_boundary(buf)
+
+ # Set environmental variables
+ self.update_environment()
+
+ # You know, we could be in an end placeholder
+ (current, next) = self.next_placeholder()
+ if current and current.__class__ == PlaceholderEnd:
+ self.goto_placeholder(current, None)
+
+ buf.begin_user_action()
+
+ # Remove the tag, selection or current word
+ buf.delete(start, end)
+
+ # Insert the snippet
+ holders = len(self.placeholders)
+
+ if len(self.active_snippets) == 0:
+ self.first_snippet_inserted()
+
+ sn = s.insert_into(self, start)
+ self.active_snippets.append(sn)
+
+ # Put cursor at first tab placeholder
+ keys = filter(lambda x: x > 0, sn.placeholders.keys())
+
+ if len(keys) == 0:
+ if 0 in sn.placeholders:
+ self.goto_placeholder(self.active_placeholder, sn.placeholders[0])
+ else:
+ buf.place_cursor(sn.begin_iter())
+ else:
+ self.goto_placeholder(self.active_placeholder, sn.placeholders[keys[0]])
+
+ if sn in self.active_snippets:
+ # Check if we can get end_iter in view without moving the
+ # current cursor position out of view
+ cur = buf.get_iter_at_mark(buf.get_insert())
+ last = sn.end_iter()
+
+ curloc = self.view.get_iter_location(cur)
+ lastloc = self.view.get_iter_location(last)
+
+ if (lastloc.y + lastloc.height) - curloc.y <= \
+ self.view.get_visible_rect().height:
+ self.view.scroll_mark_onscreen(sn.end_mark)
+
+ buf.end_user_action()
+ self.view.grab_focus()
+
+ return True
+
+ def get_tab_tag(self, buf, end = None):
+ if not end:
+ end = buf.get_iter_at_mark(buf.get_insert())
+
+ start = end.copy()
+
+ word = None
+
+ if start.backward_word_start():
+ # Check if we were at a word start ourselves
+ tmp = start.copy()
+ tmp.forward_word_end()
+
+ if tmp.equal(end):
+ word = buf.get_text(start, end)
+ else:
+ start = end.copy()
+ else:
+ start = end.copy()
+
+ if not word or word == '':
+ if start.backward_char():
+ word = start.get_char()
+
+ if word.isalnum() or word.isspace():
+ return (None, None, None)
+ else:
+ return (None, None, None)
+
+ return (word, start, end)
+
+ def parse_and_run_snippet(self, data, iter):
+ self.apply_snippet(DynamicSnippet(data), iter, iter)
+
+ def run_snippet_trigger(self, trigger, bounds):
+ if not self.view:
+ return False
+
+ snippets = Library().from_tag(trigger, self.language_id)
+ buf = self.view.get_buffer()
+
+ if snippets:
+ if len(snippets) == 1:
+ return self.apply_snippet(snippets[0], bounds[0], bounds[1])
+ else:
+ # Do the fancy completion dialog
+ self.provider.set_proposals(snippets)
+ cm = self.view.get_completion()
+
+ cm.show([self.provider], cm.create_context())
+ return True
+
+ return False
+
+ def run_snippet(self):
+ if not self.view:
+ return False
+
+ buf = self.view.get_buffer()
+
+ # get the word preceding the current insertion position
+ (word, start, end) = self.get_tab_tag(buf)
+
+ if not word:
+ return self.skip_to_next_placeholder()
+
+ if not self.run_snippet_trigger(word, (start, end)):
+ return self.skip_to_next_placeholder()
+ else:
+ return True
+
+ def deactivate_snippet(self, snippet, force = False):
+ buf = self.view.get_buffer()
+ remove = []
+ ordered_remove = []
+
+ for tabstop in snippet.placeholders:
+ if tabstop == -1:
+ placeholders = snippet.placeholders[-1]
+ else:
+ placeholders = [snippet.placeholders[tabstop]]
+
+ for placeholder in placeholders:
+ if placeholder in self.placeholders:
+ if placeholder in self.update_placeholders:
+ placeholder.update_contents()
+
+ self.update_placeholders.remove(placeholder)
+ elif placeholder in self.jump_placeholders:
+ placeholder[0].leave()
+
+ remove.append(placeholder)
+ elif placeholder in self.ordered_placeholders:
+ ordered_remove.append(placeholder)
+
+ for placeholder in remove:
+ if placeholder == self.active_placeholder:
+ self.active_placeholder = None
+
+ self.placeholders.remove(placeholder)
+ self.ordered_placeholders.remove(placeholder)
+
+ placeholder.remove(force)
+
+ for placeholder in ordered_remove:
+ self.ordered_placeholders.remove(placeholder)
+ placeholder.remove(force)
+
+ snippet.deactivate()
+ self.active_snippets.remove(snippet)
+
+ if len(self.active_snippets) == 0:
+ self.last_snippet_removed()
+
+ self.view.queue_draw()
+
+ def update_snippet_contents(self):
+ self.timeout_update_id = 0
+
+ for placeholder in self.update_placeholders:
+ placeholder.update_contents()
+
+ for placeholder in self.jump_placeholders:
+ self.goto_placeholder(placeholder[0], placeholder[1])
+
+ del self.update_placeholders[:]
+ del self.jump_placeholders[:]
+
+ return False
+
+ # Callbacks
+ def on_view_destroy(self, view):
+ self.stop()
+ return
+
+ def on_buffer_cursor_moved(self, buf):
+ piter = buf.get_iter_at_mark(buf.get_insert())
+
+ # Check for all snippets if the cursor is outside its scope
+ for snippet in list(self.active_snippets):
+ if snippet.begin_mark.get_deleted() or snippet.end_mark.get_deleted():
+ self.deactivate(snippet)
+ else:
+ begin = snippet.begin_iter()
+ end = snippet.end_iter()
+
+ if piter.compare(begin) < 0 or piter.compare(end) > 0:
+ # Oh no! Remove the snippet this instant!!
+ self.deactivate_snippet(snippet)
+
+ current = self.current_placeholder()
+
+ if current != self.active_placeholder:
+ self.jump_placeholders.append((self.active_placeholder, current))
+
+ if self.timeout_update_id == 0:
+ self.timeout_update_id = gobject.timeout_add(0,
+ self.update_snippet_contents)
+
+ def on_buffer_changed(self, buf):
+ current = self.current_placeholder()
+
+ if current:
+ if not current in self.update_placeholders:
+ self.update_placeholders.append(current)
+
+ if self.timeout_update_id == 0:
+ self.timeout_update_id = gobject.timeout_add(0, \
+ self.update_snippet_contents)
+
+ def on_buffer_insert_text(self, buf, piter, text, length):
+ ctx = get_buffer_context(buf)
+
+ # do nothing special if there is no context and no active
+ # placeholder
+ if (not ctx) and (not self.active_placeholder):
+ return
+
+ if not ctx:
+ ctx = self.active_placeholder
+
+ if not ctx in self.ordered_placeholders:
+ return
+
+ # move any marks that were incorrectly moved by this insertion
+ # back to where they belong
+ begin = ctx.begin_iter()
+ end = ctx.end_iter()
+ idx = self.ordered_placeholders.index(ctx)
+
+ for placeholder in self.ordered_placeholders:
+ if placeholder == ctx:
+ continue
+
+ ob = placeholder.begin_iter()
+ oe = placeholder.end_iter()
+
+ if ob.compare(begin) == 0 and ((not oe) or oe.compare(end) == 0):
+ oidx = self.ordered_placeholders.index(placeholder)
+
+ if oidx > idx and ob:
+ buf.move_mark(placeholder.begin, end)
+ elif oidx < idx and oe:
+ buf.move_mark(placeholder.end, begin)
+ elif ob.compare(begin) >= 0 and ob.compare(end) < 0 and (oe and oe.compare(end) >= 0):
+ buf.move_mark(placeholder.begin, end)
+ elif (oe and oe.compare(begin) > 0) and ob.compare(begin) <= 0:
+ buf.move_mark(placeholder.end, begin)
+
+ def on_notify_language(self, buf, spec):
+ self.update_language()
+
+ def on_notify_editable(self, view, spec):
+ self._set_view(view)
+
+ def on_view_key_press(self, view, event):
+ library = Library()
+
+ if not (event.state & gdk.CONTROL_MASK) and \
+ not (event.state & gdk.MOD1_MASK) and \
+ event.keyval in self.TAB_KEY_VAL:
+ if not event.state & gdk.SHIFT_MASK:
+ return self.run_snippet()
+ else:
+ return self.skip_to_previous_placeholder()
+ elif not library.loaded and \
+ library.valid_accelerator(event.keyval, event.state):
+ library.ensure_files()
+ library.ensure(self.language_id)
+ self.accelerator_activate(event.keyval, \
+ event.state & gtk.accelerator_get_default_mod_mask())
+
+ return False
+
+ def path_split(self, path, components=[]):
+ head, tail = os.path.split(path)
+
+ if not tail and head:
+ return [head] + components
+ elif tail:
+ return self.path_split(head, [tail] + components)
+ else:
+ return components
+
+ def relative_path(self, first, second, mime):
+ prot1 = re.match('(^[a-z]+:\/\/|\/)(.*)', first)
+ prot2 = re.match('(^[a-z]+:\/\/|\/)(.*)', second)
+
+ if not prot1 or not prot2:
+ return second
+
+ # Different protocols
+ if prot1.group(1) != prot2.group(1):
+ return second
+
+ # Split on backslash
+ path1 = self.path_split(prot1.group(2))
+ path2 = self.path_split(prot2.group(2))
+
+ # Remove as long as common
+ while path1 and path2 and path1[0] == path2[0]:
+ path1.pop(0)
+ path2.pop(0)
+
+ # If we need to ../ more than 3 times, then just return
+ # the absolute path
+ if len(path1) - 1 > 3:
+ return second
+
+ if mime.startswith('x-directory'):
+ # directory, special case
+ if not path2:
+ result = './'
+ else:
+ result = '../' * (len(path1) - 1)
+ else:
+ # Insert ../
+ result = '../' * (len(path1) - 1)
+
+ if not path2:
+ result = os.path.basename(second)
+
+ if path2:
+ result += os.path.join(*path2)
+
+ return result
+
+ def apply_uri_snippet(self, snippet, mime, uri):
+ # Remove file scheme
+ gfile = gio.File(uri)
+ pathname = ''
+ dirname = ''
+ ruri = ''
+
+ if gedit.utils.uri_has_file_scheme(uri):
+ pathname = gfile.get_path()
+ dirname = gfile.get_parent().get_path()
+
+ name = os.path.basename(uri)
+ scheme = gfile.get_uri_scheme()
+
+ os.environ['GEDIT_DROP_DOCUMENT_URI'] = uri
+ os.environ['GEDIT_DROP_DOCUMENT_NAME'] = name
+ os.environ['GEDIT_DROP_DOCUMENT_SCHEME'] = scheme
+ os.environ['GEDIT_DROP_DOCUMENT_PATH'] = pathname
+ os.environ['GEDIT_DROP_DOCUMENT_DIR'] = dirname
+ os.environ['GEDIT_DROP_DOCUMENT_TYPE'] = mime
+
+ buf = self.view.get_buffer()
+ location = buf.get_location()
+ if location:
+ ruri = location.get_uri()
+
+ relpath = self.relative_path(ruri, uri, mime)
+
+ os.environ['GEDIT_DROP_DOCUMENT_RELATIVE_PATH'] = relpath
+
+ mark = buf.get_mark('gtk_drag_target')
+
+ if not mark:
+ mark = buf.get_insert()
+
+ piter = buf.get_iter_at_mark(mark)
+ self.apply_snippet(snippet, piter, piter)
+
+ def in_bounds(self, x, y):
+ rect = self.view.get_visible_rect()
+ rect.x, rect.y = self.view.buffer_to_window_coords(gtk.TEXT_WINDOW_WIDGET, rect.x, rect.y)
+
+ return not (x < rect.x or x > rect.x + rect.width or y < rect.y or y > rect.y + rect.height)
+
+ def on_drag_data_received(self, view, context, x, y, data, info, timestamp):
+ if not (gtk.targets_include_uri(context.targets) and data.data and self.in_bounds(x, y)):
+ return
+
+ uris = drop_get_uris(data)
+ uris.reverse()
+ stop = False
+
+ for uri in uris:
+ try:
+ mime = gio.content_type_guess(uri)
+ except:
+ mime = None
+
+ if not mime:
+ continue
+
+ snippets = Library().from_drop_target(mime, self.language_id)
+
+ if snippets:
+ stop = True
+ self.apply_uri_snippet(snippets[0], mime, uri)
+
+ if stop:
+ context.finish(True, False, timestamp)
+ view.stop_emission('drag-data-received')
+ view.get_toplevel().present()
+ view.grab_focus()
+
+ def find_uri_target(self, context):
+ lst = gtk.target_list_add_uri_targets((), 0)
+
+ return self.view.drag_dest_find_target(context, lst)
+
+ def on_completion_hide(self, completion):
+ self.provider.set_proposals(None)
+
+ def on_proposal_activated(self, proposal, piter):
+ buf = self.view.get_buffer()
+ bounds = buf.get_selection_bounds()
+
+ if bounds:
+ self.apply_snippet(proposal.snippet(), None, None)
+ else:
+ (word, start, end) = self.get_tab_tag(buf, piter)
+ self.apply_snippet(proposal.snippet(), start, end)
+
+ return True
+
+ def on_default_activated(self, proposal, piter):
+ buf = self.view.get_buffer()
+ bounds = buf.get_selection_bounds()
+
+ if bounds:
+ buf.begin_user_action()
+ buf.delete(bounds[0], bounds[1])
+ buf.insert(bounds[0], proposal.props.label)
+ buf.end_user_action()
+
+ return True
+ else:
+ return False
+
+ def iter_coords(self, piter):
+ rect = self.view.get_iter_location(piter)
+ rect.x, rect.y = self.view.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, rect.x, rect.y)
+
+ return rect
+
+ def placeholder_in_area(self, placeholder, area):
+ start = placeholder.begin_iter()
+ end = placeholder.end_iter()
+
+ if not start or not end:
+ return False
+
+ # Test if start is before bottom, and end is after top
+ start_rect = self.iter_coords(start)
+ end_rect = self.iter_coords(end)
+
+ return start_rect.y <= area.y + area.height and \
+ end_rect.y + end_rect.height >= area.y
+
+ def draw_placeholder_rect(self, ctx, placeholder, col):
+ start = placeholder.begin_iter()
+ start_rect = self.iter_coords(start)
+ start_line = start.get_line()
+
+ end = placeholder.end_iter()
+ end_rect = self.iter_coords(end)
+ end_line = end.get_line()
+
+ line = start.copy()
+ line.set_line_offset(0)
+ geom = self.view.get_window(gtk.TEXT_WINDOW_TEXT).get_geometry()
+
+ ctx.translate(0.5, 0.5)
+
+ while line.get_line() <= end_line:
+ ypos, height = self.view.get_line_yrange(line)
+ x_, ypos = self.view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, 0, ypos)
+
+ if line.get_line() == start_line and line.get_line() == end_line:
+ # Simply draw a box, both are on the same line
+ ctx.rectangle(start_rect.x, start_rect.y, end_rect.x - start_rect.x, start_rect.height - 1)
+ ctx.stroke()
+ elif line.get_line() == start_line or line.get_line() == end_line:
+ if line.get_line() == start_line:
+ rect = start_rect
+ else:
+ rect = end_rect
+
+ ctx.move_to(0, rect.y + rect.height - 1)
+ ctx.rel_line_to(rect.x, 0)
+ ctx.rel_line_to(0, -rect.height + 1)
+ ctx.rel_line_to(geom[2], 0)
+ ctx.stroke()
+
+ if not line.forward_line():
+ break
+
+ def draw_placeholder_bar(self, ctx, placeholder, col):
+ start = placeholder.begin_iter()
+ start_rect = self.iter_coords(start)
+
+ ctx.translate(0.5, 0.5)
+ extend_width = 2.5
+
+ ctx.move_to(start_rect.x - extend_width, start_rect.y)
+ ctx.rel_line_to(extend_width * 2, 0)
+
+ ctx.move_to(start_rect.x, start_rect.y)
+ ctx.rel_line_to(0, start_rect.height - 1)
+
+ ctx.rel_move_to(-extend_width, 0)
+ ctx.rel_line_to(extend_width * 2, 0)
+ ctx.stroke()
+
+ def from_color(self, col):
+ return [col.red / 0x10000, col.green / 0x10000, col.blue / 0x10000]
+
+ def draw_placeholder(self, ctx, placeholder):
+ if isinstance(placeholder, PlaceholderEnd):
+ return
+
+ buf = self.view.get_buffer()
+
+ col = self.from_color(self.view.get_style().text[gtk.STATE_INSENSITIVE])
+ ctx.set_source_rgba(col[0], col[1], col[2], 0.5)
+
+ if placeholder.tabstop > 0:
+ ctx.set_dash([], 0)
+ else:
+ ctx.set_dash([2], 0)
+
+ start = placeholder.begin_iter()
+ end = placeholder.end_iter()
+
+ if start.equal(end):
+ self.draw_placeholder_bar(ctx, placeholder, col)
+ else:
+ self.draw_placeholder_rect(ctx, placeholder, col)
+
+ def on_expose_event(self, view, event):
+ if event.window != view.get_window(gtk.TEXT_WINDOW_TEXT):
+ return False
+
+ # Draw something
+ ctx = event.window.cairo_create()
+ ctx.rectangle(event.area)
+ ctx.clip()
+
+ ctx.set_line_width(1.0)
+
+ for placeholder in self.ordered_placeholders:
+ if not self.placeholder_in_area(placeholder, event.area):
+ continue
+
+ ctx.save()
+ self.draw_placeholder(ctx, placeholder)
+ ctx.restore()
+
+ return False
+
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/Exporter.py b/plugins/snippets/snippets/Exporter.py
new file mode 100755
index 00000000..51d49104
--- /dev/null
+++ b/plugins/snippets/snippets/Exporter.py
@@ -0,0 +1,98 @@
+import os
+import tempfile
+import sys
+import shutil
+
+from snippets.Library import *
+import xml.etree.ElementTree as et
+from Helper import *
+
+class Exporter:
+ def __init__(self, filename, snippets):
+ self.filename = filename
+ self.set_snippets(snippets)
+
+ def set_snippets(self, snippets):
+ self.snippets = {}
+
+ for snippet in snippets:
+ lang = snippet.language()
+
+ if lang in self.snippets:
+ self.snippets[lang].append(snippet)
+ else:
+ self.snippets[lang] = [snippet]
+
+ def export_xml(self, dirname, language, snippets):
+ # Create the root snippets node
+ root = et.Element('snippets')
+
+ # Create filename based on language
+ if language:
+ filename = os.path.join(dirname, language + '.xml')
+
+ # Set the language attribute
+ root.attrib['language'] = language
+ else:
+ filename = os.path.join(dirname, 'global.xml')
+
+ # Add all snippets to the root node
+ for snippet in snippets:
+ root.append(snippet.to_xml())
+
+ # Write xml
+ write_xml(root, filename, ('text', 'accelerator'))
+
+ def export_archive(self, cmd):
+ dirname = tempfile.mkdtemp()
+
+ # Save current working directory and change to temporary directory
+ curdir = os.getcwd()
+
+ try:
+ os.chdir(dirname)
+
+ # Write snippet xml files
+ for language, snippets in self.snippets.items():
+ self.export_xml(dirname, language , snippets)
+
+ # Archive files
+ status = os.system('%s "%s" *.xml' % (cmd, self.filename))
+ finally:
+ os.chdir(curdir)
+
+ if status != 0:
+ return _('The archive "%s" could not be created' % self.filename)
+
+ # Remove the temporary directory
+ shutil.rmtree(dirname)
+
+ def export_targz(self):
+ self.export_archive('tar -c --gzip -f')
+
+ def export_tarbz2(self):
+ self.export_archive('tar -c --bzip2 -f')
+
+ def export_tar(self):
+ self.export_archive('tar -cf')
+
+ def run(self):
+ dirname = os.path.dirname(self.filename)
+ if not os.path.exists(dirname):
+ return _('Target directory "%s" does not exist') % dirname
+
+ if not os.path.isdir(dirname):
+ return _('Target directory "%s" is not a valid directory') % dirname
+
+ (root, ext) = os.path.splitext(self.filename)
+
+ actions = {'.tar.gz': self.export_targz,
+ '.tar.bz2': self.export_tarbz2,
+ '.tar': self.export_tar}
+
+ for k, v in actions.items():
+ if self.filename.endswith(k):
+ return v()
+
+ return self.export_targz()
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/Helper.py b/plugins/snippets/snippets/Helper.py
new file mode 100755
index 00000000..de363360
--- /dev/null
+++ b/plugins/snippets/snippets/Helper.py
@@ -0,0 +1,182 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <[email protected]>
+#
+# 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 string
+from xml.sax import saxutils
+from xml.etree.ElementTree import *
+import re
+
+import gtk
+from gtk import gdk
+
+def message_dialog(par, typ, msg):
+ d = gtk.MessageDialog(par, gtk.DIALOG_MODAL, typ, gtk.BUTTONS_OK, msg)
+ d.set_property('use-markup', True)
+
+ d.run()
+ d.destroy()
+
+def compute_indentation(view, piter):
+ line = piter.get_line()
+ start = view.get_buffer().get_iter_at_line(line)
+ end = start.copy()
+
+ ch = end.get_char()
+
+ while (ch.isspace() and ch != '\r' and ch != '\n' and \
+ end.compare(piter) < 0):
+ if not end.forward_char():
+ break;
+
+ ch = end.get_char()
+
+ if start.equal(end):
+ return ''
+
+ return start.get_slice(end)
+
+def markup_escape(text):
+ return saxutils.escape(text)
+
+def spaces_instead_of_tabs(view, text):
+ if not view.get_insert_spaces_instead_of_tabs():
+ return text
+
+ return text.replace("\t", view.get_tab_width() * ' ')
+
+def insert_with_indent(view, piter, text, indentfirst = True, context = None):
+ text = spaces_instead_of_tabs(view, text)
+ lines = text.split('\n')
+
+ view.get_buffer().set_data('GeditSnippetsPluginContext', context)
+
+ if len(lines) == 1:
+ view.get_buffer().insert(piter, text)
+ else:
+ # Compute indentation
+ indent = compute_indentation(view, piter)
+ text = ''
+
+ for i in range(0, len(lines)):
+ if indentfirst or i > 0:
+ text += indent + lines[i] + '\n'
+ else:
+ text += lines[i] + '\n'
+
+ view.get_buffer().insert(piter, text[:-1])
+
+ view.get_buffer().set_data('GeditSnippetsPluginContext', None)
+
+def get_buffer_context(buf):
+ return buf.get_data('GeditSnippetsPluginContext')
+
+def snippets_debug(*s):
+ return
+
+def write_xml(node, f, cdata_nodes=()):
+ assert node is not None
+
+ if not hasattr(f, "write"):
+ f = open(f, "wb")
+
+ # Encoding
+ f.write("<?xml version='1.0' encoding='utf-8'?>\n")
+
+ _write_node(node, f, cdata_nodes)
+
+def _write_indent(file, text, indent):
+ file.write(' ' * indent + text)
+
+def _write_node(node, file, cdata_nodes=(), indent=0):
+ # write XML to file
+ tag = node.tag
+
+ if node is Comment:
+ _write_indent(file, "<!-- %s -->\n" % saxutils.escape(node.text.encode('utf-8')), indent)
+ elif node is ProcessingInstruction:
+ _write_indent(file, "<?%s?>\n" % saxutils.escape(node.text.encode('utf-8')), indent)
+ else:
+ items = node.items()
+
+ if items or node.text or len(node):
+ _write_indent(file, "<" + tag.encode('utf-8'), indent)
+
+ if items:
+ items.sort() # lexical order
+ for k, v in items:
+ file.write(" %s=%s" % (k.encode('utf-8'), saxutils.quoteattr(v.encode('utf-8'))))
+ if node.text or len(node):
+ file.write(">")
+ if node.text and node.text.strip() != "":
+ if tag in cdata_nodes:
+ file.write(_cdata(node.text))
+ else:
+ file.write(saxutils.escape(node.text.encode('utf-8')))
+ else:
+ file.write("\n")
+
+ for n in node:
+ _write_node(n, file, cdata_nodes, indent + 1)
+
+ if not len(node):
+ file.write("</" + tag.encode('utf-8') + ">\n")
+ else:
+ _write_indent(file, "</" + tag.encode('utf-8') + ">\n", \
+ indent)
+ else:
+ file.write(" />\n")
+
+ if node.tail and node.tail.strip() != "":
+ file.write(saxutils.escape(node.tail.encode('utf-8')))
+
+def _cdata(text, replace=string.replace):
+ text = text.encode('utf-8')
+ return '<![CDATA[' + replace(text, ']]>', ']]]]><![CDATA[>') + ']]>'
+
+def buffer_word_boundary(buf):
+ iter = buf.get_iter_at_mark(buf.get_insert())
+ start = iter.copy()
+
+ if not iter.starts_word() and (iter.inside_word() or iter.ends_word()):
+ start.backward_word_start()
+
+ if not iter.ends_word() and iter.inside_word():
+ iter.forward_word_end()
+
+ return (start, iter)
+
+def buffer_line_boundary(buf):
+ iter = buf.get_iter_at_mark(buf.get_insert())
+ start = iter.copy()
+ start.set_line_offset(0)
+
+ if not iter.ends_line():
+ iter.forward_to_line_end()
+
+ return (start, iter)
+
+def drop_get_uris(selection):
+ lines = re.split('\\s*[\\n\\r]+\\s*', selection.data.strip())
+ result = []
+
+ for line in lines:
+ if not line.startswith('#'):
+ result.append(line)
+
+ return result
+
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/Importer.py b/plugins/snippets/snippets/Importer.py
new file mode 100755
index 00000000..6151027d
--- /dev/null
+++ b/plugins/snippets/snippets/Importer.py
@@ -0,0 +1,100 @@
+import os
+import tempfile
+import sys
+import shutil
+
+from snippets.Library import *
+
+class Importer:
+ def __init__(self, filename):
+ self.filename = filename
+
+ def import_destination(self, filename):
+ userdir = Library().userdir
+
+ filename = os.path.basename(filename)
+ (root, ext) = os.path.splitext(filename)
+
+ filename = os.path.join(userdir, root + ext)
+ i = 1
+
+ while os.path.exists(filename):
+ filename = os.path.join(userdir, root + '_' + str(i) + ext)
+ i += 1
+
+ return filename
+
+ def import_file(self, filename):
+ if not os.path.exists(filename):
+ return _('File "%s" does not exist') % filename
+
+ if not os.path.isfile(filename):
+ return _('File "%s" is not a valid snippets file') % filename
+
+ # Find destination for file to copy to
+ dest = self.import_destination(filename)
+
+ # Copy file
+ shutil.copy(filename, dest)
+
+ # Add library
+ if not Library().add_user_library(dest):
+ return _('Imported file "%s" is not a valid snippets file') % os.path.basename(dest)
+
+ def import_xml(self):
+ return self.import_file(self.filename)
+
+ def import_archive(self, cmd):
+ dirname = tempfile.mkdtemp()
+ status = os.system('cd %s; %s "%s"' % (dirname, cmd, self.filename))
+
+ if status != 0:
+ return _('The archive "%s" could not be extracted' % self.filename)
+
+ errors = []
+
+ # Now import all the files from the archive
+ for f in os.listdir(dirname):
+ f = os.path.join(dirname, f)
+
+ if os.path.isfile(f):
+ if self.import_file(f):
+ errors.append(os.path.basename(f))
+ else:
+ sys.stderr.write('Skipping %s, not a valid snippets file' % os.path.basename(f))
+
+ # Remove the temporary directory
+ shutil.rmtree(dirname)
+
+ if len(errors) > 0:
+ return _('The following files could not be imported: %s') % ', '.join(errors)
+
+ def import_targz(self):
+ self.import_archive('tar -x --gzip -f')
+
+ def import_tarbz2(self):
+ self.import_archive('tar -x --bzip2 -f')
+
+ def import_tar(self):
+ self.import_archive('tar -xf')
+
+ def run(self):
+ if not os.path.exists(self.filename):
+ return _('File "%s" does not exist') % self.filename
+
+ if not os.path.isfile(self.filename):
+ return _('File "%s" is not a valid snippets archive') % self.filename
+
+ (root, ext) = os.path.splitext(self.filename)
+
+ actions = {'.tar.gz': self.import_targz,
+ '.tar.bz2': self.import_tarbz2,
+ '.xml': self.import_xml,
+ '.tar': self.import_tar}
+
+ for k, v in actions.items():
+ if self.filename.endswith(k):
+ return v()
+
+ return _('File "%s" is not a valid snippets archive') % self.filename
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/LanguageManager.py b/plugins/snippets/snippets/LanguageManager.py
new file mode 100755
index 00000000..9646ef17
--- /dev/null
+++ b/plugins/snippets/snippets/LanguageManager.py
@@ -0,0 +1,21 @@
+import gtksourceview2 as gsv
+import os
+
+from Library import Library
+
+global manager
+manager = None
+
+def get_language_manager():
+ global manager
+
+ if not manager:
+ dirs = []
+
+ for d in Library().systemdirs:
+ dirs.append(os.path.join(d, 'lang'))
+
+ manager = gsv.LanguageManager()
+ manager.set_search_path(dirs + manager.get_search_path())
+
+ return manager
diff --git a/plugins/snippets/snippets/Library.py b/plugins/snippets/snippets/Library.py
new file mode 100755
index 00000000..4717280c
--- /dev/null
+++ b/plugins/snippets/snippets/Library.py
@@ -0,0 +1,993 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <[email protected]>
+#
+# 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 weakref
+import sys
+import tempfile
+import re
+
+import gtk
+
+import xml.etree.ElementTree as et
+from Helper import *
+
+class NamespacedId:
+ def __init__(self, namespace, id):
+ if not id:
+ self.id = None
+ else:
+ if namespace:
+ self.id = namespace + '-'
+ else:
+ self.id = 'global-'
+
+ self.id += id
+
+class SnippetData:
+ PROPS = {'tag': '', 'text': '', 'description': 'New snippet',
+ 'accelerator': '', 'drop-targets': ''}
+
+ def __init__(self, node, library):
+ self.priv_id = node.attrib.get('id')
+
+ self.set_library(library)
+ self.valid = False
+ self.set_node(node)
+
+ def can_modify(self):
+ return (self.library and (isinstance(self.library(), SnippetsUserFile)))
+
+ def set_library(self, library):
+ if library:
+ self.library = weakref.ref(library)
+ else:
+ self.library = None
+
+ self.id = NamespacedId(self.language(), self.priv_id).id
+
+ def set_node(self, node):
+ if self.can_modify():
+ self.node = node
+ else:
+ self.node = None
+
+ self.init_snippet_data(node)
+
+ def init_snippet_data(self, node):
+ if node == None:
+ return
+
+ self.override = node.attrib.get('override')
+
+ self.properties = {}
+ props = SnippetData.PROPS.copy()
+
+ # Store all properties present
+ for child in node:
+ if child.tag in props:
+ del props[child.tag]
+
+ # Normalize accelerator
+ if child.tag == 'accelerator' and child.text != None:
+ keyval, mod = gtk.accelerator_parse(child.text)
+
+ if gtk.accelerator_valid(keyval, mod):
+ child.text = gtk.accelerator_name(keyval, mod)
+ else:
+ child.text = ''
+
+ if self.can_modify():
+ self.properties[child.tag] = child
+ else:
+ self.properties[child.tag] = child.text or ''
+
+ # Create all the props that were not found so we stay consistent
+ for prop in props:
+ if self.can_modify():
+ child = et.SubElement(node, prop)
+
+ child.text = props[prop]
+ self.properties[prop] = child
+ else:
+ self.properties[prop] = props[prop]
+
+ self.check_validation()
+
+ def check_validation(self):
+ if not self['tag'] and not self['accelerator'] and not self['drop-targets']:
+ return False
+
+ library = Library()
+ keyval, mod = gtk.accelerator_parse(self['accelerator'])
+
+ self.valid = library.valid_tab_trigger(self['tag']) and \
+ (not self['accelerator'] or library.valid_accelerator(keyval, mod))
+
+ def _format_prop(self, prop, value):
+ if prop == 'drop-targets' and value != '':
+ return re.split('\\s*[,;]\\s*', value)
+ else:
+ return value
+
+ def __getitem__(self, prop):
+ if prop in self.properties:
+ if self.can_modify():
+ return self._format_prop(prop, self.properties[prop].text or '')
+ else:
+ return self._format_prop(prop, self.properties[prop] or '')
+
+ return self._format_prop(prop, '')
+
+ def __setitem__(self, prop, value):
+ if not prop in self.properties:
+ return
+
+ if isinstance(value, list):
+ value = ','.join(value)
+
+ if not self.can_modify() and self.properties[prop] != value:
+ # ohoh, this is not can_modify, but it needs to be changed...
+ # make sure it is transfered to the changes file and set all the
+ # fields.
+ # This snippet data container will effectively become the container
+ # for the newly created node, but transparently to whoever uses
+ # it
+ self._override()
+
+ if self.can_modify() and self.properties[prop].text != value:
+ if self.library():
+ self.library().tainted = True
+
+ oldvalue = self.properties[prop].text
+ self.properties[prop].text = value
+
+ if prop == 'tag' or prop == 'accelerator' or prop == 'drop-targets':
+ container = Library().container(self.language())
+ container.prop_changed(self, prop, oldvalue)
+
+ self.check_validation()
+
+ def language(self):
+ if self.library and self.library():
+ return self.library().language
+ else:
+ return None
+
+ def is_override(self):
+ return self.override and Library().overridden[self.override]
+
+ def to_xml(self):
+ return self._create_xml()
+
+ def _create_xml(self, parent=None, update=False, attrib={}):
+ # Create a new node
+ if parent != None:
+ element = et.SubElement(parent, 'snippet', attrib)
+ else:
+ element = et.Element('snippet')
+
+ # Create all the properties
+ for p in self.properties:
+ prop = et.SubElement(element, p)
+ prop.text = self[p]
+
+ if update:
+ self.properties[p] = prop
+
+ return element
+
+ def _override(self):
+ # Find the user file
+ target = Library().get_user_library(self.language())
+
+ # Create a new node there with override
+ element = self._create_xml(target.root, True, {'override': self.id})
+
+ # Create an override snippet data, feed it element so that it stores
+ # all the values and then set the node to None so that it only contains
+ # the values in .properties
+ override = SnippetData(element, self.library())
+ override.set_node(None)
+ override.id = self.id
+
+ # Set our node to the new element
+ self.node = element
+
+ # Set the override to our id
+ self.override = self.id
+ self.id = None
+
+ # Set the new library
+ self.set_library(target)
+
+ # The library is tainted because we added this snippet
+ target.tainted = True
+
+ # Add the override
+ Library().overridden[self.override] = override
+
+ def revert(self, snippet):
+ userlib = self.library()
+ self.set_library(snippet.library())
+
+ userlib.remove(self.node)
+
+ self.set_node(None)
+
+ # Copy the properties
+ self.properties = snippet.properties
+
+ # Set the id
+ self.id = snippet.id
+
+ # Reset the override flag
+ self.override = None
+
+class SnippetsTreeBuilder(et.TreeBuilder):
+ def __init__(self, start=None, end=None):
+ et.TreeBuilder.__init__(self)
+ self.set_start(start)
+ self.set_end(end)
+
+ def set_start(self, start):
+ self._start_cb = start
+
+ def set_end(self, end):
+ self._end_cb = end
+
+ def start(self, tag, attrs):
+ result = et.TreeBuilder.start(self, tag, attrs)
+
+ if self._start_cb:
+ self._start_cb(result)
+
+ return result
+
+ def end(self, tag):
+ result = et.TreeBuilder.end(self, tag)
+
+ if self._end_cb:
+ self._end_cb(result)
+
+ return result
+
+class LanguageContainer:
+ def __init__(self, language):
+ self.language = language
+ self.snippets = []
+ self.snippets_by_prop = {'tag': {}, 'accelerator': {}, 'drop-targets': {}}
+ self.accel_group = gtk.AccelGroup()
+ self._refs = 0
+
+ def _add_prop(self, snippet, prop, value=0):
+ if value == 0:
+ value = snippet[prop]
+
+ if not value or value == '':
+ return
+
+ snippets_debug('Added ', prop ,' ', value, ' to ', str(self.language))
+
+ if prop == 'accelerator':
+ keyval, mod = gtk.accelerator_parse(value)
+ self.accel_group.connect_group(keyval, mod, 0, \
+ Library().accelerator_activated)
+
+ snippets = self.snippets_by_prop[prop]
+
+ if not isinstance(value, list):
+ value = [value]
+
+ for val in value:
+ if val in snippets:
+ snippets[val].append(snippet)
+ else:
+ snippets[val] = [snippet]
+
+ def _remove_prop(self, snippet, prop, value=0):
+ if value == 0:
+ value = snippet[prop]
+
+ if not value or value == '':
+ return
+
+ snippets_debug('Removed ', prop, ' ', value, ' from ', str(self.language))
+
+ if prop == 'accelerator':
+ keyval, mod = gtk.accelerator_parse(value)
+ self.accel_group.disconnect_key(keyval, mod)
+
+ snippets = self.snippets_by_prop[prop]
+
+ if not isinstance(value, list):
+ value = [value]
+
+ for val in value:
+ try:
+ snippets[val].remove(snippet)
+ except:
+ True
+
+ def append(self, snippet):
+ tag = snippet['tag']
+ accelerator = snippet['accelerator']
+
+ self.snippets.append(snippet)
+
+ self._add_prop(snippet, 'tag')
+ self._add_prop(snippet, 'accelerator')
+ self._add_prop(snippet, 'drop-targets')
+
+ return snippet
+
+ def remove(self, snippet):
+ try:
+ self.snippets.remove(snippet)
+ except:
+ True
+
+ self._remove_prop(snippet, 'tag')
+ self._remove_prop(snippet, 'accelerator')
+ self._remove_prop(snippet, 'drop-targets')
+
+ def prop_changed(self, snippet, prop, oldvalue):
+ snippets_debug('PROP CHANGED (', prop, ')', oldvalue)
+
+ self._remove_prop(snippet, prop, oldvalue)
+ self._add_prop(snippet, prop)
+
+ def from_prop(self, prop, value):
+ snippets = self.snippets_by_prop[prop]
+
+ if prop == 'drop-targets':
+ s = []
+
+ # FIXME: change this to use
+ # matevfs.mime_type_get_equivalence when it comes
+ # available
+ for key, val in snippets.items():
+ if not value.startswith(key):
+ continue
+
+ for snippet in snippets[key]:
+ if not snippet in s:
+ s.append(snippet)
+
+ return s
+ else:
+ if value in snippets:
+ return snippets[value]
+ else:
+ return []
+
+ def ref(self):
+ self._refs += 1
+
+ return True
+
+ def unref(self):
+ if self._refs > 0:
+ self._refs -= 1
+
+ return self._refs != 0
+
+class SnippetsSystemFile:
+ def __init__(self, path=None):
+ self.path = path
+ self.loaded = False
+ self.language = None
+ self.ok = True
+ self.need_id = True
+
+ def load_error(self, message):
+ sys.stderr.write("An error occurred loading " + self.path + ":\n")
+ sys.stderr.write(message + "\nSnippets in this file will not be " \
+ "available, please correct or remove the file.\n")
+
+ def _add_snippet(self, element):
+ if not self.need_id or element.attrib.get('id'):
+ self.loading_elements.append(element)
+
+ def set_language(self, element):
+ self.language = element.attrib.get('language')
+
+ if self.language:
+ self.language = self.language.lower()
+
+ def _set_root(self, element):
+ self.set_language(element)
+
+ def _preprocess_element(self, element):
+ if not self.loaded:
+ if not element.tag == "snippets":
+ self.load_error("Root element should be `snippets' instead " \
+ "of `%s'" % element.tag)
+ return False
+ else:
+ self._set_root(element)
+ self.loaded = True
+ elif element.tag != 'snippet' and not self.insnippet:
+ self.load_error("Element should be `snippet' instead of `%s'" \
+ % element.tag)
+ return False
+ else:
+ self.insnippet = True
+
+ return True
+
+ def _process_element(self, element):
+ if element.tag == 'snippet':
+ self._add_snippet(element)
+ self.insnippet = False
+
+ return True
+
+ def ensure(self):
+ if not self.ok or self.loaded:
+ return
+
+ self.load()
+
+ def parse_xml(self, readsize=16384):
+ if not self.path:
+ return
+
+ elements = []
+
+ builder = SnippetsTreeBuilder( \
+ lambda node: elements.append((node, True)), \
+ lambda node: elements.append((node, False)))
+
+ parser = et.XMLTreeBuilder(target=builder)
+ self.insnippet = False
+
+ try:
+ f = open(self.path, "r")
+
+ while True:
+ data = f.read(readsize)
+
+ if not data:
+ break
+
+ parser.feed(data)
+
+ for element in elements:
+ yield element
+
+ del elements[:]
+
+ f.close()
+ except IOError:
+ self.ok = False
+
+ def load(self):
+ if not self.ok:
+ return
+
+ snippets_debug("Loading library (" + str(self.language) + "): " + \
+ self.path)
+
+ self.loaded = False
+ self.ok = False
+ self.loading_elements = []
+
+ for element in self.parse_xml():
+ if element[1]:
+ if not self._preprocess_element(element[0]):
+ del self.loading_elements[:]
+ return
+ else:
+ if not self._process_element(element[0]):
+ del self.loading_elements[:]
+ return
+
+ for element in self.loading_elements:
+ snippet = Library().add_snippet(self, element)
+
+ del self.loading_elements[:]
+ self.ok = True
+
+ # This function will get the language for a file by just inspecting the
+ # root element of the file. This is provided so that a cache can be built
+ # for which file contains which language.
+ # It returns the name of the language
+ def ensure_language(self):
+ if not self.loaded:
+ self.ok = False
+
+ for element in self.parse_xml(256):
+ if element[1]:
+ if element[0].tag == 'snippets':
+ self.set_language(element[0])
+ self.ok = True
+
+ break
+
+ def unload(self):
+ snippets_debug("Unloading library (" + str(self.language) + "): " + \
+ self.path)
+ self.language = None
+ self.loaded = False
+ self.ok = True
+
+class SnippetsUserFile(SnippetsSystemFile):
+ def __init__(self, path=None):
+ SnippetsSystemFile.__init__(self, path)
+ self.tainted = False
+ self.need_id = False
+
+ def _set_root(self, element):
+ SnippetsSystemFile._set_root(self, element)
+ self.root = element
+
+ def add_prop(self, node, tag, data):
+ if data[tag]:
+ prop = et.SubElement(node, tag)
+ prop.text = data[tag]
+
+ return prop
+ else:
+ return None
+
+ def new_snippet(self, properties=None):
+ if (not self.ok) or self.root == None:
+ return None
+
+ element = et.SubElement(self.root, 'snippet')
+
+ if properties:
+ for prop in properties:
+ sub = et.SubElement(element, prop)
+ sub.text = properties[prop]
+
+ self.tainted = True
+
+ return Library().add_snippet(self, element)
+
+ def set_language(self, element):
+ SnippetsSystemFile.set_language(self, element)
+
+ filename = os.path.basename(self.path).lower()
+
+ if not self.language and filename == "global.xml":
+ self.modifier = True
+ elif self.language and filename == self.language + ".xml":
+ self.modifier = True
+ else:
+ self.modifier = False
+
+ def create_root(self, language):
+ if self.loaded:
+ snippets_debug('Not creating root, already loaded')
+ return
+
+ if language:
+ root = et.Element('snippets', {'language': language})
+ self.path = os.path.join(Library().userdir, language.lower() + '.xml')
+ else:
+ root = et.Element('snippets')
+ self.path = os.path.join(Library().userdir, 'global.xml')
+
+ self._set_root(root)
+ self.loaded = True
+ self.ok = True
+ self.tainted = True
+ self.save()
+
+ def remove(self, element):
+ try:
+ self.root.remove(element)
+ self.tainted = True
+ except:
+ return
+
+ try:
+ first = self.root[0]
+ except:
+ # No more elements, this library is useless now
+ Library().remove_library(self)
+
+ def save(self):
+ if not self.ok or self.root == None or not self.tainted:
+ return
+
+ path = os.path.dirname(self.path)
+
+ try:
+ if not os.path.isdir(path):
+ os.makedirs(path, 0755)
+ except OSError:
+ # TODO: this is bad...
+ sys.stderr.write("Error in making dirs\n")
+
+ try:
+ write_xml(self.root, self.path, ('text', 'accelerator'))
+ self.tainted = False
+ except IOError:
+ # Couldn't save, what to do
+ sys.stderr.write("Could not save user snippets file to " + \
+ self.path + "\n")
+
+ def unload(self):
+ SnippetsSystemFile.unload(self)
+ self.root = None
+
+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 Library(Singleton):
+ def __init_once__(self):
+ self._accelerator_activated_cb = None
+ self.loaded = False
+ self.check_buffer = gtk.TextBuffer()
+
+ def set_dirs(self, userdir, systemdirs):
+ self.userdir = userdir
+ self.systemdirs = systemdirs
+
+ self.libraries = {}
+ self.containers = {}
+ self.overridden = {}
+ self.loaded_ids = []
+
+ self.loaded = False
+
+ def set_accelerator_callback(self, cb):
+ self._accelerator_activated_cb = cb
+
+ def accelerator_activated(self, group, obj, keyval, mod):
+ ret = False
+
+ if self._accelerator_activated_cb:
+ ret = self._accelerator_activated_cb(group, obj, keyval, mod)
+
+ return ret
+
+ def add_snippet(self, library, element):
+ container = self.container(library.language)
+ overrided = self.overrided(library, element)
+
+ if overrided:
+ overrided.set_library(library)
+ snippets_debug('Snippet is overriden: ' + overrided['description'])
+ return None
+
+ snippet = SnippetData(element, library)
+
+ if snippet.id in self.loaded_ids:
+ snippets_debug('Not added snippet ' + str(library.language) + \
+ '::' + snippet['description'] + ' (duplicate)')
+ return None
+
+ snippet = container.append(snippet)
+ snippets_debug('Added snippet ' + str(library.language) + '::' + \
+ snippet['description'])
+
+ if snippet and snippet.override:
+ self.add_override(snippet)
+
+ if snippet.id:
+ self.loaded_ids.append(snippet.id)
+
+ return snippet
+
+ def container(self, language):
+ language = self.normalize_language(language)
+
+ if not language in self.containers:
+ self.containers[language] = LanguageContainer(language)
+
+ return self.containers[language]
+
+ def get_user_library(self, language):
+ target = None
+
+ if language in self.libraries:
+ for library in self.libraries[language]:
+ if isinstance(library, SnippetsUserFile) and library.modifier:
+ target = library
+ elif not isinstance(library, SnippetsUserFile):
+ break
+
+ if not target:
+ # Create a new user file then
+ snippets_debug('Creating a new user file for language ' + \
+ str(language))
+ target = SnippetsUserFile()
+ target.create_root(language)
+ self.add_library(target)
+
+ return target
+
+ def new_snippet(self, language, properties=None):
+ language = self.normalize_language(language)
+ library = self.get_user_library(language)
+
+ return library.new_snippet(properties)
+
+ def revert_snippet(self, snippet):
+ # This will revert the snippet to the one it overrides
+ if not snippet.can_modify() or not snippet.override in self.overridden:
+ # It can't be reverted, shouldn't happen, but oh..
+ return
+
+ # The snippet in self.overriden only contains the property contents and
+ # the library it belongs to
+ revertto = self.overridden[snippet.override]
+ del self.overridden[snippet.override]
+
+ if revertto:
+ snippet.revert(revertto)
+
+ if revertto.id:
+ self.loaded_ids.append(revertto.id)
+
+ def remove_snippet(self, snippet):
+ if not snippet.can_modify() or snippet.is_override():
+ return
+
+ # Remove from the library
+ userlib = snippet.library()
+ userlib.remove(snippet.node)
+
+ # Remove from the container
+ container = self.containers[userlib.language]
+ container.remove(snippet)
+
+ def overrided(self, library, element):
+ id = NamespacedId(library.language, element.attrib.get('id')).id
+
+ if id in self.overridden:
+ snippet = SnippetData(element, None)
+ snippet.set_node(None)
+
+ self.overridden[id] = snippet
+ return snippet
+ else:
+ return None
+
+ def add_override(self, snippet):
+ snippets_debug('Add override:', snippet.override)
+ if not snippet.override in self.overridden:
+ self.overridden[snippet.override] = None
+
+ def add_library(self, library):
+ library.ensure_language()
+
+ if not library.ok:
+ snippets_debug('Library in wrong format, ignoring')
+ return False
+
+ snippets_debug('Adding library (' + str(library.language) + '): ' + \
+ library.path)
+
+ if library.language in self.libraries:
+ # Make sure all the user files are before the system files
+ if isinstance(library, SnippetsUserFile):
+ self.libraries[library.language].insert(0, library)
+ else:
+ self.libraries[library.language].append(library)
+ else:
+ self.libraries[library.language] = [library]
+
+ return True
+
+ def remove_library(self, library):
+ if not library.ok:
+ return
+
+ if library.path and os.path.isfile(library.path):
+ os.unlink(library.path)
+
+ try:
+ self.libraries[library.language].remove(library)
+ except KeyError:
+ True
+
+ container = self.containers[library.language]
+
+ for snippet in list(container.snippets):
+ if snippet.library() == library:
+ container.remove(snippet)
+
+ def add_user_library(self, path):
+ library = SnippetsUserFile(path)
+ return self.add_library(library)
+
+ def add_system_library(self, path):
+ library = SnippetsSystemFile(path)
+ return self.add_library(library)
+
+ def find_libraries(self, path, searched, addcb):
+ snippets_debug("Finding in: " + path)
+
+ if not os.path.isdir(path):
+ return searched
+
+ files = os.listdir(path)
+ searched.append(path)
+
+ for f in files:
+ f = os.path.realpath(os.path.join(path, f))
+
+ # Determine what language this file provides snippets for
+ if os.path.isfile(f):
+ addcb(f)
+
+ return searched
+
+ def normalize_language(self, language):
+ if language:
+ return language.lower()
+
+ return language
+
+ def remove_container(self, language):
+ for snippet in self.containers[language].snippets:
+ if snippet.id in self.loaded_ids:
+ self.loaded_ids.remove(snippet.id)
+
+ if snippet.override in self.overridden:
+ del self.overridden[snippet.override]
+
+ del self.containers[language]
+
+ def get_accel_group(self, language):
+ language = self.normalize_language(language)
+ container = self.container(language)
+
+ self.ensure(language)
+ return container.accel_group
+
+ def save(self, language):
+ language = self.normalize_language(language)
+
+ if language in self.libraries:
+ for library in self.libraries[language]:
+ if isinstance(library, SnippetsUserFile):
+ library.save()
+ else:
+ break
+
+ def ref(self, language):
+ language = self.normalize_language(language)
+
+ snippets_debug('Ref:', language)
+ self.container(language).ref()
+
+ def unref(self, language):
+ language = self.normalize_language(language)
+
+ snippets_debug('Unref:', language)
+
+ if language in self.containers:
+ if not self.containers[language].unref() and \
+ language in self.libraries:
+
+ for library in self.libraries[language]:
+ library.unload()
+
+ self.remove_container(language)
+
+ def ensure(self, language):
+ self.ensure_files()
+ language = self.normalize_language(language)
+
+ # Ensure language as well as the global snippets (None)
+ for lang in (None, language):
+ if lang in self.libraries:
+ # Ensure the container exists
+ self.container(lang)
+
+ for library in self.libraries[lang]:
+ library.ensure()
+
+ def ensure_files(self):
+ if self.loaded:
+ return
+
+ searched = []
+ searched = self.find_libraries(self.userdir, searched, \
+ self.add_user_library)
+
+ for d in self.systemdirs:
+ searched = self.find_libraries(d, searched, \
+ self.add_system_library)
+
+ self.loaded = True
+
+ def valid_accelerator(self, keyval, mod):
+ mod &= gtk.accelerator_get_default_mod_mask()
+
+ return (mod and (gdk.keyval_to_unicode(keyval) or \
+ keyval in range(gtk.keysyms.F1, gtk.keysyms.F12 + 1)))
+
+ def valid_tab_trigger(self, trigger):
+ if not trigger:
+ return True
+
+ if trigger.isdigit():
+ return False
+
+ self.check_buffer.set_text(trigger)
+
+ start, end = self.check_buffer.get_bounds()
+ text = self.check_buffer.get_text(start, end)
+
+ s = start.copy()
+ e = end.copy()
+
+ end.backward_word_start()
+ start.forward_word_end()
+
+ return (s.equal(end) and e.equal(start)) or (len(text) == 1 and not (text.isalnum() or text.isspace()))
+
+ # Snippet getters
+ # ===============
+ def _from_prop(self, prop, value, language=None):
+ self.ensure_files()
+
+ result = []
+ language = self.normalize_language(language)
+
+ if not language in self.containers:
+ return []
+
+ self.ensure(language)
+ result = self.containers[language].from_prop(prop, value)
+
+ if len(result) == 0 and language and None in self.containers:
+ result = self.containers[None].from_prop(prop, value)
+
+ return result
+
+ # Get snippets for a given language
+ def get_snippets(self, language=None):
+ self.ensure_files()
+ language = self.normalize_language(language)
+
+ if not language in self.libraries:
+ return []
+
+ snippets = []
+ self.ensure(language)
+
+ return list(self.containers[language].snippets)
+
+ # Get snippets for a given accelerator
+ def from_accelerator(self, accelerator, language=None):
+ return self._from_prop('accelerator', accelerator, language)
+
+ # Get snippets for a given tag
+ def from_tag(self, tag, language=None):
+ return self._from_prop('tag', tag, language)
+
+ # Get snippets for a given drop target
+ def from_drop_target(self, drop_target, language=None):
+ return self._from_prop('drop-targets', drop_target, language)
+
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/Makefile.am b/plugins/snippets/snippets/Makefile.am
new file mode 100755
index 00000000..7a05b562
--- /dev/null
+++ b/plugins/snippets/snippets/Makefile.am
@@ -0,0 +1,28 @@
+# Python snippets plugin
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/snippets
+
+plugin_PYTHON = \
+ __init__.py \
+ WindowHelper.py \
+ Document.py \
+ Library.py \
+ Snippet.py \
+ Parser.py \
+ Placeholder.py \
+ Manager.py \
+ Helper.py \
+ SubstitutionParser.py \
+ Importer.py \
+ Exporter.py \
+ LanguageManager.py \
+ Completion.py
+
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/snippets/ui
+ui_DATA = snippets.ui
+
+EXTRA_DIST = $(ui_DATA)
+
+CLEANFILES = *.bak *.gladep *.pyc
+DISTCLEANFILES = *.bak *.gladep *.pyc
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/snippets/snippets/Manager.py b/plugins/snippets/snippets/Manager.py
new file mode 100755
index 00000000..16acbdb4
--- /dev/null
+++ b/plugins/snippets/snippets/Manager.py
@@ -0,0 +1,1157 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <[email protected]>
+#
+# 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 tempfile
+import shutil
+
+import gobject
+import gtk
+from gtk import gdk
+import gtksourceview2 as gsv
+import pango
+import gedit
+import gio
+
+from Snippet import Snippet
+from Helper import *
+from Library import *
+from Importer import *
+from Exporter import *
+from Document import Document
+from LanguageManager import get_language_manager
+
+class Manager:
+ NAME_COLUMN = 0
+ SORT_COLUMN = 1
+ OBJ_COLUMN = 2
+ TARGET_URI = 105
+
+ model = None
+ drag_icons = ('mate-mime-application-x-tarz', 'mate-package', 'package')
+ default_export_name = _('Snippets archive') + '.tar.gz'
+ dragging = False
+ dnd_target_list = [('text/uri-list', 0, TARGET_URI)]
+
+ def __init__(self, datadir):
+ self.datadir = datadir
+ self.snippet = None
+ self.dlg = None
+ self._temp_export = None
+ self.snippets_doc = None
+ self.manager = None
+ self.default_size = None
+
+ self.key_press_id = 0
+ self.run()
+
+ def get_language_snippets(self, path, name = None):
+ library = Library()
+
+ name = self.get_language(path)
+ nodes = library.get_snippets(name)
+
+ return nodes
+
+ def add_new_snippet_node(self, parent):
+ return self.model.append(parent, ('<i>' + _('Add a new snippet...') + \
+ '</i>', '', None))
+
+ def fill_language(self, piter, expand=True):
+ # Remove all children
+ child = self.model.iter_children(piter)
+
+ while child and self.model.remove(child):
+ True
+
+ path = self.model.get_path(piter)
+ nodes = self.get_language_snippets(path)
+ language = self.get_language(path)
+
+ Library().ref(language)
+
+ if nodes:
+ for node in nodes:
+ self.add_snippet(piter, node)
+ else:
+ # Add node that tells there are no snippets currently
+ self.add_new_snippet_node(piter)
+
+ if expand:
+ self.tree_view.expand_row(path, False)
+
+ def build_model(self, force_reload = False):
+ window = gedit.app_get_default().get_active_window()
+
+ if window:
+ view = window.get_active_view()
+
+ if not view:
+ current_lang = None
+ else:
+ current_lang = view.get_buffer().get_language()
+ source_view = self['source_view_snippet']
+
+ else:
+ current_lang = None
+
+ tree_view = self['tree_view_snippets']
+ expand = None
+
+ if not self.model or force_reload:
+ self.model = gtk.TreeStore(str, str, object)
+ self.model.set_sort_column_id(self.SORT_COLUMN, gtk.SORT_ASCENDING)
+ manager = get_language_manager()
+ langs = gedit.language_manager_list_languages_sorted(manager, True)
+
+ piter = self.model.append(None, (_('Global'), '', None))
+ # Add dummy node
+ self.model.append(piter, ('', '', None))
+
+ nm = None
+
+ if current_lang:
+ nm = current_lang.get_name()
+
+ for lang in langs:
+ name = lang.get_name()
+ parent = self.model.append(None, (name, name, lang))
+
+ # Add dummy node
+ self.model.append(parent, ('', '', None))
+
+ if (nm == name):
+ expand = parent
+ else:
+ if current_lang:
+ piter = self.model.get_iter_first()
+ nm = current_lang.get_name()
+
+ while piter:
+ lang = self.model.get_value(piter, \
+ self.SORT_COLUMN)
+
+ if lang == nm:
+ expand = piter
+ break;
+
+ piter = self.model.iter_next(piter)
+
+ tree_view.set_model(self.model)
+
+ if not expand:
+ expand = self.model.get_iter_root()
+
+ tree_view.expand_row(self.model.get_path(expand), False)
+ self.select_iter(expand)
+
+ def get_cell_data_pixbuf_cb(self, column, cell, model, iter):
+ s = model.get_value(iter, self.OBJ_COLUMN)
+
+ snippet = isinstance(s, SnippetData)
+
+ if snippet and not s.valid:
+ cell.set_property('stock-id', gtk.STOCK_DIALOG_ERROR)
+ else:
+ cell.set_property('stock-id', None)
+
+ cell.set_property('xalign', 1.0)
+
+ def get_cell_data_cb(self, column, cell, model, iter):
+ s = model.get_value(iter, self.OBJ_COLUMN)
+
+ snippet = isinstance(s, SnippetData)
+
+ cell.set_property('editable', snippet)
+ cell.set_property('markup', model.get_value(iter, self.NAME_COLUMN))
+
+ def on_tree_view_drag_data_get(self, widget, context, selection_data, info, time):
+ gfile = gio.File(self._temp_export)
+ selection_data.set_uris([gfile.get_uri()])
+
+ def on_tree_view_drag_begin(self, widget, context):
+ self.dragging = True
+
+ if self._temp_export:
+ shutil.rmtree(os.path.dirname(self._temp_export))
+ self._temp_export = None
+
+ if self.dnd_name:
+ context.set_icon_name(self.dnd_name, 0, 0)
+
+ dirname = tempfile.mkdtemp()
+ filename = os.path.join(dirname, self.default_export_name)
+
+ # Generate temporary file name
+ self.export_snippets(filename, False)
+ self._temp_export = filename
+
+ def on_tree_view_drag_end(self, widget, context):
+ self.dragging = False
+
+ def on_tree_view_drag_data_received(self, widget, context, x, y, selection, info, timestamp):
+ uris = selection.get_uris()
+
+ self.import_snippets(uris)
+
+ def on_tree_view_drag_motion(self, widget, context, x, y, timestamp):
+ # Return False if we are dragging
+ if self.dragging:
+ return False
+
+ # Check uri target
+ if not gtk.targets_include_uri(context.targets):
+ return False
+
+ # Check action
+ action = None
+ if context.suggested_action == gdk.ACTION_COPY:
+ action = gdk.ACTION_COPY
+ else:
+ for act in context.actions:
+ if act == gdk.ACTION_COPY:
+ action = gdk.ACTION_COPY
+ break
+
+ if action == gdk.ACTION_COPY:
+ context.drag_status(gdk.ACTION_COPY, timestamp)
+ return True
+ else:
+ return False
+
+ def build_dnd(self):
+ tv = self.tree_view
+
+ # Set it as a drag source for exporting snippets
+ tv.drag_source_set(gdk.BUTTON1_MASK, self.dnd_target_list, gdk.ACTION_DEFAULT | gdk.ACTION_COPY)
+
+ # Set it as a drag destination for importing snippets
+ tv.drag_dest_set(gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
+ self.dnd_target_list, gdk.ACTION_DEFAULT | gdk.ACTION_COPY)
+
+ tv.connect('drag_data_get', self.on_tree_view_drag_data_get)
+ tv.connect('drag_begin', self.on_tree_view_drag_begin)
+ tv.connect('drag_end', self.on_tree_view_drag_end)
+ tv.connect('drag_data_received', self.on_tree_view_drag_data_received)
+ tv.connect('drag_motion', self.on_tree_view_drag_motion)
+
+ theme = gtk.icon_theme_get_for_screen(tv.get_screen())
+
+ self.dnd_name = None
+ for name in self.drag_icons:
+ icon = theme.lookup_icon(name, gtk.ICON_SIZE_DND, 0)
+
+ if icon:
+ self.dnd_name = name
+ break
+
+ def build_tree_view(self):
+ self.tree_view = self['tree_view_snippets']
+
+ self.column = gtk.TreeViewColumn(None)
+
+ self.renderer = gtk.CellRendererText()
+ self.column.pack_start(self.renderer, False)
+ self.column.set_cell_data_func(self.renderer, self.get_cell_data_cb)
+
+ renderer = gtk.CellRendererPixbuf()
+ self.column.pack_start(renderer, True)
+ self.column.set_cell_data_func(renderer, self.get_cell_data_pixbuf_cb)
+
+ self.tree_view.append_column(self.column)
+
+ self.renderer.connect('edited', self.on_cell_edited)
+ self.renderer.connect('editing-started', self.on_cell_editing_started)
+
+ selection = self.tree_view.get_selection()
+ selection.set_mode(gtk.SELECTION_MULTIPLE)
+ selection.connect('changed', self.on_tree_view_selection_changed)
+
+ self.build_dnd()
+
+ def build(self):
+ self.builder = gtk.Builder()
+ self.builder.add_from_file(os.path.join(self.datadir, 'ui', 'snippets.ui'))
+
+ handlers_dic = {
+ 'on_dialog_snippets_response': self.on_dialog_snippets_response,
+ 'on_dialog_snippets_destroy': self.on_dialog_snippets_destroy,
+ 'on_button_new_snippet_clicked': self.on_button_new_snippet_clicked,
+ 'on_button_import_snippets_clicked': self.on_button_import_snippets_clicked,
+ 'on_button_export_snippets_clicked': self.on_button_export_snippets_clicked,
+ 'on_button_remove_snippet_clicked': self.on_button_remove_snippet_clicked,
+ 'on_entry_tab_trigger_focus_out': self.on_entry_tab_trigger_focus_out,
+ 'on_entry_tab_trigger_changed': self.on_entry_tab_trigger_changed,
+ 'on_entry_accelerator_focus_out': self.on_entry_accelerator_focus_out,
+ 'on_entry_accelerator_focus_in': self.on_entry_accelerator_focus_in,
+ 'on_entry_accelerator_key_press': self.on_entry_accelerator_key_press,
+ 'on_source_view_snippet_focus_out': self.on_source_view_snippet_focus_out,
+ 'on_tree_view_snippets_row_expanded': self.on_tree_view_snippets_row_expanded,
+ 'on_tree_view_snippets_key_press': self.on_tree_view_snippets_key_press}
+
+ self.builder.connect_signals(handlers_dic)
+
+ self.build_tree_view()
+ self.build_model()
+
+ image = self['image_remove']
+ image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_SMALL_TOOLBAR)
+
+ source_view = self['source_view_snippet']
+ manager = get_language_manager()
+ lang = manager.get_language('snippets')
+
+ if lang:
+ source_view.get_buffer().set_highlight_syntax(True)
+ source_view.get_buffer().set_language(lang)
+ self.snippets_doc = Document(None, source_view)
+
+ combo = self['combo_drop_targets']
+ combo.set_text_column(0)
+
+ entry = combo.child
+ entry.connect('focus-out-event', self.on_entry_drop_targets_focus_out)
+ entry.connect('drag-data-received', self.on_entry_drop_targets_drag_data_received)
+
+ lst = entry.drag_dest_get_target_list()
+ lst = gtk.target_list_add_uri_targets(entry.drag_dest_get_target_list(), self.TARGET_URI)
+ entry.drag_dest_set_target_list(lst)
+
+ self.dlg = self['dialog_snippets']
+
+ if self.default_size:
+ self.dlg.set_default_size(*self.default_size)
+
+ def __getitem__(self, key):
+ return self.builder.get_object(key)
+
+ def is_filled(self, piter):
+ if not self.model.iter_has_child(piter):
+ return True
+
+ child = self.model.iter_children(piter)
+ nm = self.model.get_value(child, self.NAME_COLUMN)
+ obj = self.model.get_value(child, self.OBJ_COLUMN)
+
+ return (obj or nm)
+
+ def fill_if_needed(self, piter, expand=True):
+ if not self.is_filled(piter):
+ self.fill_language(piter, expand)
+
+ def find_iter(self, parent, snippet):
+ self.fill_if_needed(parent)
+ piter = self.model.iter_children(parent)
+
+ while (piter):
+ node = self.model.get_value(piter, self.OBJ_COLUMN)
+
+ if node == snippet.data:
+ return piter
+
+ piter = self.model.iter_next(piter)
+
+ return None
+
+ def selected_snippets_state(self):
+ snippets = self.selected_snippets(False)
+ override = False
+ remove = False
+ system = False
+
+ for snippet in snippets:
+ if not snippet:
+ continue
+
+ if snippet.is_override():
+ override = True
+ elif snippet.can_modify():
+ remove = True
+ else:
+ system = True
+
+ # No need to continue if both are found
+ if override and remove:
+ break
+
+ return (override, remove, system)
+
+ def update_buttons(self):
+ button_remove = self['button_remove_snippet']
+ button_new = self['button_new_snippet']
+ image_remove = self['image_remove']
+
+ button_new.set_sensitive(self.language_path != None)
+ override, remove, system = self.selected_snippets_state()
+
+ if not (override ^ remove) or system:
+ button_remove.set_sensitive(False)
+ image_remove.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON)
+ else:
+ button_remove.set_sensitive(True)
+
+ if override:
+ image_remove.set_from_stock(gtk.STOCK_UNDO, gtk.ICON_SIZE_BUTTON)
+ tooltip = _('Revert selected snippet')
+ else:
+ image_remove.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON)
+ tooltip = _('Delete selected snippet')
+
+ button_remove.set_tooltip_text(tooltip)
+
+ def snippet_changed(self, piter = None):
+ if piter:
+ node = self.model.get_value(piter, self.OBJ_COLUMN)
+ s = Snippet(node)
+ else:
+ s = self.snippet
+ piter = self.find_iter(self.model.get_iter(self.language_path), s)
+
+ if piter:
+ nm = s.display()
+
+ self.model.set(piter, self.NAME_COLUMN, nm, self.SORT_COLUMN, nm)
+ self.update_buttons()
+ self.entry_tab_trigger_update_valid()
+
+ return piter
+
+ def add_snippet(self, parent, snippet):
+ piter = self.model.append(parent, ('', '', snippet))
+
+ return self.snippet_changed(piter)
+
+ def run(self):
+ if not self.dlg:
+ self.build()
+ self.dlg.show()
+ else:
+ self.build_model()
+ self.dlg.present()
+
+
+ def snippet_from_iter(self, model, piter):
+ parent = model.iter_parent(piter)
+
+ if parent:
+ return model.get_value(piter, self.OBJ_COLUMN)
+ else:
+ return None
+
+ def language_snippets(self, model, parent, as_path=False):
+ self.fill_if_needed(parent, False)
+ piter = model.iter_children(parent)
+ snippets = []
+
+ if not piter:
+ return snippets
+
+ while piter:
+ snippet = self.snippet_from_iter(model, piter)
+
+ if snippet:
+ if as_path:
+ snippets.append(model.get_path(piter))
+ else:
+ snippets.append(snippet)
+
+ piter = model.iter_next(piter)
+
+ return snippets
+
+ def selected_snippets(self, include_languages=True, as_path=False):
+ selection = self.tree_view.get_selection()
+ (model, paths) = selection.get_selected_rows()
+ snippets = []
+
+ if paths and len(paths) != 0:
+ for p in paths:
+ piter = model.get_iter(p)
+ parent = model.iter_parent(piter)
+
+ if not piter:
+ continue
+
+ if parent:
+ snippet = self.snippet_from_iter(model, piter)
+
+ if not snippet:
+ continue
+
+ if as_path:
+ snippets.append(p)
+ else:
+ snippets.append(snippet)
+ elif include_languages:
+ snippets += self.language_snippets(model, piter, as_path)
+
+ return snippets
+
+ def selected_snippet(self):
+ selection = self.tree_view.get_selection()
+ (model, paths) = selection.get_selected_rows()
+
+ if len(paths) == 1:
+ piter = model.get_iter(paths[0])
+ parent = model.iter_parent(piter)
+ snippet = self.snippet_from_iter(model, piter)
+
+ return parent, piter, snippet
+ else:
+ return None, None, None
+
+ def selection_changed(self):
+ if not self.snippet:
+ sens = False
+
+ self['entry_tab_trigger'].set_text('')
+ self['entry_accelerator'].set_text('')
+ buf = self['source_view_snippet'].get_buffer()
+ buf.begin_not_undoable_action()
+ buf.set_text('')
+ buf.end_not_undoable_action()
+ self['combo_drop_targets'].child.set_text('')
+
+ else:
+ sens = True
+
+ self['entry_tab_trigger'].set_text(self.snippet['tag'])
+ self['entry_accelerator'].set_text( \
+ self.snippet.accelerator_display())
+ self['combo_drop_targets'].child.set_text(', '.join(self.snippet['drop-targets']))
+
+ buf = self['source_view_snippet'].get_buffer()
+ buf.begin_not_undoable_action()
+ buf.set_text(self.snippet['text'])
+ buf.end_not_undoable_action()
+
+
+ for name in ['source_view_snippet', 'label_tab_trigger',
+ 'entry_tab_trigger', 'label_accelerator',
+ 'entry_accelerator', 'label_drop_targets',
+ 'combo_drop_targets']:
+ self[name].set_sensitive(sens)
+
+ self.update_buttons()
+
+ def select_iter(self, piter, unselect=True):
+ selection = self.tree_view.get_selection()
+
+ if unselect:
+ selection.unselect_all()
+
+ selection.select_iter(piter)
+
+ self.tree_view.scroll_to_cell(self.model.get_path(piter), None, \
+ True, 0.5, 0.5)
+
+ def get_language(self, path):
+ if path[0] == 0:
+ return None
+ else:
+ return self.model.get_value(self.model.get_iter( \
+ (path[0],)), self.OBJ_COLUMN).get_id()
+
+ def new_snippet(self, properties=None):
+ if not self.language_path:
+ return None
+
+ snippet = Library().new_snippet(self.get_language(self.language_path), properties)
+
+ return Snippet(snippet)
+
+ def get_dummy(self, parent):
+ if not self.model.iter_n_children(parent) == 1:
+ return None
+
+ dummy = self.model.iter_children(parent)
+
+ if not self.model.get_value(dummy, self.OBJ_COLUMN):
+ return dummy
+
+ return None
+
+ def unref_languages(self):
+ piter = self.model.get_iter_first()
+ library = Library()
+
+ while piter:
+ if self.is_filled(piter):
+ language = self.get_language(self.model.get_path(piter))
+ library.save(language)
+
+ library.unref(language)
+
+ piter = self.model.iter_next(piter)
+
+ # Callbacks
+ def on_dialog_snippets_destroy(self, dlg):
+ # Remove temporary drag export
+ if self._temp_export:
+ shutil.rmtree(os.path.dirname(self._temp_export))
+ self._temp_export = None
+
+ if self.snippets_doc:
+ self.snippets_doc.stop()
+
+ self.default_size = [dlg.allocation.width, dlg.allocation.height]
+ self.manager = None
+
+ self.unref_languages()
+ self.snippet = None
+ self.model = None
+ self.dlg = None
+
+ def on_dialog_snippets_response(self, dlg, resp):
+ if resp == gtk.RESPONSE_HELP:
+ gedit.help_display(self.dlg, 'gedit', 'gedit-snippets-plugin')
+ return
+
+ self.dlg.destroy()
+
+ def on_cell_editing_started(self, renderer, editable, path):
+ piter = self.model.get_iter(path)
+
+ if not self.model.iter_parent(piter):
+ renderer.stop_editing(True)
+ editable.remove_widget()
+ elif isinstance(editable, gtk.Entry):
+ if self.snippet:
+ editable.set_text(self.snippet['description'])
+ else:
+ # This is the `Add a new snippet...` item
+ editable.set_text('')
+
+ editable.grab_focus()
+
+ def on_cell_edited(self, cell, path, new_text):
+ if new_text != '':
+ piter = self.model.get_iter(path)
+ node = self.model.get_value(piter, self.OBJ_COLUMN)
+
+ if node:
+ if node == self.snippet.data:
+ s = self.snippet
+ else:
+ s = Snippet(node)
+
+ s['description'] = new_text
+ self.snippet_changed(piter)
+ self.select_iter(piter)
+ else:
+ # This is the `Add a new snippet...` item
+ # We create a new snippet
+ snippet = self.new_snippet({'description': new_text})
+
+ if snippet:
+ self.model.set(piter, self.OBJ_COLUMN, snippet.data)
+ self.snippet_changed(piter)
+ self.snippet = snippet
+ self.selection_changed()
+
+ def on_entry_accelerator_focus_out(self, entry, event):
+ if not self.snippet:
+ return
+
+ entry.set_text(self.snippet.accelerator_display())
+
+ def entry_tab_trigger_update_valid(self):
+ entry = self['entry_tab_trigger']
+ text = entry.get_text()
+
+ if text and not Library().valid_tab_trigger(text):
+ img = self['image_tab_trigger']
+ img.set_from_stock(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_BUTTON)
+ img.show()
+
+ #self['hbox_tab_trigger'].set_spacing(3)
+ tip = _('This is not a valid Tab trigger. Triggers can either contain letters or a single (non-alphanumeric) character like: {, [, etc.')
+
+ entry.set_tooltip_text(tip)
+ img.set_tooltip_text(tip)
+ else:
+ self['image_tab_trigger'].hide()
+ #self['hbox_tab_trigger'].set_spacing(0)
+ entry.set_tooltip_text(_('Single word the snippet is activated with after pressing Tab'))
+
+ return False
+
+ def on_entry_tab_trigger_focus_out(self, entry, event):
+ if not self.snippet:
+ return
+
+ text = entry.get_text()
+
+ # save tag
+ self.snippet['tag'] = text
+ self.snippet_changed()
+
+ def on_entry_drop_targets_focus_out(self, entry, event):
+ if not self.snippet:
+ return
+
+ text = entry.get_text()
+
+ # save drop targets
+ self.snippet['drop-targets'] = text
+ self.snippet_changed()
+
+ def on_entry_tab_trigger_changed(self, entry):
+ self.entry_tab_trigger_update_valid()
+
+ def on_source_view_snippet_focus_out(self, source_view, event):
+ if not self.snippet:
+ return
+
+ buf = source_view.get_buffer()
+ text = buf.get_text(buf.get_start_iter(), \
+ buf.get_end_iter())
+
+ self.snippet['text'] = text
+ self.snippet_changed()
+
+ def on_button_new_snippet_clicked(self, button):
+ snippet = self.new_snippet()
+
+ if not snippet:
+ return
+
+ parent = self.model.get_iter(self.language_path)
+ path = self.model.get_path(parent)
+
+ dummy = self.get_dummy(parent)
+
+ if dummy:
+ # Remove the dummy
+ self.model.remove(dummy)
+
+ # Add the snippet
+ piter = self.add_snippet(parent, snippet.data)
+ self.select_iter(piter)
+
+ if not self.tree_view.row_expanded(path):
+ self.tree_view.expand_row(path, False)
+ self.select_iter(piter)
+
+ self.tree_view.grab_focus()
+
+ path = self.model.get_path(piter)
+ self.tree_view.set_cursor(path, self.column, True)
+
+ def file_filter(self, name, pattern):
+ fil = gtk.FileFilter()
+ fil.set_name(name)
+
+ for p in pattern:
+ fil.add_pattern(p)
+
+ return fil
+
+ def import_snippets(self, filenames):
+ success = True
+
+ for filename in filenames:
+ if not gedit.utils.uri_has_file_scheme(filename):
+ continue
+
+ # Remove file://
+ gfile = gio.File(filename)
+ filename = gfile.get_path()
+
+ importer = Importer(filename)
+ error = importer.run()
+
+ if error:
+ message = _('The following error occurred while importing: %s') % error
+ success = False
+ message_dialog(self.dlg, gtk.MESSAGE_ERROR, message)
+
+ self.build_model(True)
+
+ if success:
+ message = _('Import successfully completed')
+ message_dialog(self.dlg, gtk.MESSAGE_INFO, message)
+
+ def on_import_response(self, dialog, response):
+ if response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_CLOSE:
+ dialog.destroy()
+ return
+
+ f = dialog.get_uris()
+ dialog.destroy()
+
+ self.import_snippets(f)
+
+ def on_button_import_snippets_clicked(self, button):
+ dlg = gtk.FileChooserDialog(parent=self.dlg, title=_("Import snippets"),
+ action=gtk.FILE_CHOOSER_ACTION_OPEN,
+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+
+ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar', '*.xml')))
+ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',)))
+ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',)))
+ dlg.add_filter(self.file_filter(_('Single snippets file'), ('*.xml',)))
+ dlg.add_filter(self.file_filter(_('All files'), '*'))
+
+ dlg.connect('response', self.on_import_response)
+ dlg.set_local_only(True)
+
+ dlg.show()
+
+ def export_snippets_real(self, filename, snippets, show_dialogs=True):
+ export = Exporter(filename, snippets)
+ error = export.run()
+
+ if error:
+ message = _('The following error occurred while exporting: %s') % error
+ msgtype = gtk.MESSAGE_ERROR
+ retval = False
+ else:
+ message = _('Export successfully completed')
+ msgtype = gtk.MESSAGE_INFO
+ retval = True
+
+ if show_dialogs:
+ message_dialog(self.dlg, msgtype, message)
+
+ return retval
+
+ def on_export_response(self, dialog, response):
+ filename = dialog.get_filename()
+ snippets = dialog._export_snippets
+
+ dialog.destroy()
+
+ if response != gtk.RESPONSE_OK:
+ return
+
+ self.export_snippets_real(filename, snippets);
+
+ def export_snippets(self, filename=None, show_dialogs=True):
+ snippets = self.selected_snippets()
+
+ if not snippets or len(snippets) == 0:
+ return False
+
+ usersnippets = []
+ systemsnippets = []
+
+ # Iterate through snippets and look for system snippets
+ for snippet in snippets:
+ if snippet.can_modify():
+ usersnippets.append(snippet)
+ else:
+ systemsnippets.append(snippet)
+
+ export_snippets = snippets
+
+ if len(systemsnippets) != 0 and show_dialogs:
+ # Ask if system snippets should also be exported
+ message = _('Do you want to include selected <b>system</b> snippets in your export?')
+ mes = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
+ type=gtk.MESSAGE_QUESTION,
+ buttons=gtk.BUTTONS_YES_NO,
+ message_format=message)
+ mes.set_property('use-markup', True)
+ resp = mes.run()
+ mes.destroy()
+
+ if resp == gtk.RESPONSE_NO:
+ export_snippets = usersnippets
+ elif resp != gtk.RESPONSE_YES:
+ return False
+
+ if len(export_snippets) == 0 and show_dialogs:
+ message = _('There are no snippets selected to be exported')
+ message_dialog(self.dlg, gtk.MESSAGE_INFORMATION, message)
+ return False
+
+ if not filename:
+ dlg = gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'),
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+
+ dlg._export_snippets = export_snippets
+ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar')))
+ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',)))
+ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',)))
+
+ dlg.add_filter(self.file_filter(_('All files'), '*'))
+ dlg.set_do_overwrite_confirmation(True)
+ dlg.set_current_name(self.default_export_name)
+
+ dlg.connect('response', self.on_export_response)
+ dlg.set_local_only(True)
+
+ dlg.show()
+ return True
+ else:
+ return self.export_snippets_real(filename, export_snippets, show_dialogs)
+
+ def on_button_export_snippets_clicked(self, button):
+ snippets = self.selected_snippets()
+
+ if not snippets or len(snippets) == 0:
+ return
+
+ usersnippets = []
+ systemsnippets = []
+
+ # Iterate through snippets and look for system snippets
+ for snippet in snippets:
+ if snippet.can_modify():
+ usersnippets.append(snippet)
+ else:
+ systemsnippets.append(snippet)
+
+ dlg = gtk.FileChooserDialog(parent=self.dlg, title=_('Export snippets'),
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+
+ dlg._export_snippets = snippets
+
+ if len(systemsnippets) != 0:
+ # Ask if system snippets should also be exported
+ message = _('Do you want to include selected <b>system</b> snippets in your export?')
+ mes = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
+ type=gtk.MESSAGE_QUESTION,
+ buttons=gtk.BUTTONS_YES_NO,
+ message_format=message)
+ mes.set_property('use-markup', True)
+ resp = mes.run()
+ mes.destroy()
+
+ if resp == gtk.RESPONSE_NO:
+ dlg._export_snippets = usersnippets
+ elif resp != gtk.RESPONSE_YES:
+ dlg.destroy()
+ return
+
+ if len(dlg._export_snippets) == 0:
+ dlg.destroy()
+
+ message = _('There are no snippets selected to be exported')
+ message_dialog(self.dlg, gtk.MESSAGE_INFORMATION, message)
+ return
+
+ dlg.add_filter(self.file_filter(_('All supported archives'), ('*.gz','*.bz2','*.tar')))
+ dlg.add_filter(self.file_filter(_('Gzip compressed archive'), ('*.tar.gz',)))
+ dlg.add_filter(self.file_filter(_('Bzip2 compressed archive'), ('*.tar.bz2',)))
+
+ dlg.add_filter(self.file_filter(_('All files'), '*'))
+ dlg.set_do_overwrite_confirmation(True)
+ dlg.set_current_name(self.default_export_name)
+
+ dlg.connect('response', self.on_export_response)
+ dlg.set_local_only(True)
+
+ dlg.show()
+
+ def remove_snippet_revert(self, path, piter):
+ node = self.snippet_from_iter(self.model, piter)
+ Library().revert_snippet(node)
+
+ return piter
+
+ def remove_snippet_delete(self, path, piter):
+ node = self.snippet_from_iter(self.model, piter)
+ parent = self.model.iter_parent(piter)
+
+ Library().remove_snippet(node)
+
+ if self.model.remove(piter):
+ return piter
+ elif path[-1] != 0:
+ self.select_iter(self.model.get_iter((path[0], path[1] - 1)))
+ else:
+ dummy = self.add_new_snippet_node(parent)
+ self.tree_view.expand_row(self.model.get_path(parent), False)
+ return dummy
+
+ def on_button_remove_snippet_clicked(self, button):
+ override, remove, system = self.selected_snippets_state()
+
+ if not (override ^ remove) or system:
+ return
+
+ paths = self.selected_snippets(include_languages=False, as_path=True)
+
+ if override:
+ action = self.remove_snippet_revert
+ else:
+ action = self.remove_snippet_delete
+
+ # Remove selection
+ self.tree_view.get_selection().unselect_all()
+
+ # Create tree row references
+ references = []
+ for path in paths:
+ references.append(gtk.TreeRowReference(self.model, path))
+
+ # Remove/revert snippets
+ select = None
+ for reference in references:
+ path = reference.get_path()
+ piter = self.model.get_iter(path)
+
+ res = action(path, piter)
+
+ if res:
+ select = res
+
+ if select:
+ self.select_iter(select)
+
+ self.selection_changed()
+
+ def set_accelerator(self, keyval, mod):
+ accelerator = gtk.accelerator_name(keyval, mod)
+ self.snippet['accelerator'] = accelerator
+
+ return True
+
+ def on_entry_accelerator_key_press(self, entry, event):
+ source_view = self['source_view_snippet']
+
+ if event.keyval == gdk.keyval_from_name('Escape'):
+ # Reset
+ entry.set_text(self.snippet.accelerator_display())
+ self.tree_view.grab_focus()
+
+ return True
+ elif event.keyval == gdk.keyval_from_name('Delete') or \
+ event.keyval == gdk.keyval_from_name('BackSpace'):
+ # Remove the accelerator
+ entry.set_text('')
+ self.snippet['accelerator'] = ''
+ self.tree_view.grab_focus()
+
+ self.snippet_changed()
+ return True
+ elif Library().valid_accelerator(event.keyval, event.state):
+ # New accelerator
+ self.set_accelerator(event.keyval, \
+ event.state & gtk.accelerator_get_default_mod_mask())
+ entry.set_text(self.snippet.accelerator_display())
+ self.snippet_changed()
+ self.tree_view.grab_focus()
+
+ else:
+ return True
+
+ def on_entry_accelerator_focus_in(self, entry, event):
+ if self.snippet['accelerator']:
+ entry.set_text(_('Type a new shortcut, or press Backspace to clear'))
+ else:
+ entry.set_text(_('Type a new shortcut'))
+
+ def update_language_path(self):
+ model, paths = self.tree_view.get_selection().get_selected_rows()
+
+ # Check if all have the same language parent
+ current_parent = None
+
+ for path in paths:
+ piter = model.get_iter(path)
+ parent = model.iter_parent(piter)
+
+ if parent:
+ path = model.get_path(parent)
+
+ if current_parent != None and current_parent != path:
+ current_parent = None
+ break
+ else:
+ current_parent = path
+
+ self.language_path = current_parent
+
+ def on_tree_view_selection_changed(self, selection):
+ parent, piter, node = self.selected_snippet()
+
+ if self.snippet:
+ self.on_entry_tab_trigger_focus_out(self['entry_tab_trigger'],
+ None)
+ self.on_source_view_snippet_focus_out(self['source_view_snippet'],
+ None)
+ self.on_entry_drop_targets_focus_out(self['combo_drop_targets'].child,
+ None)
+
+ self.update_language_path()
+
+ if node:
+ self.snippet = Snippet(node)
+ else:
+ self.snippet = None
+
+ self.selection_changed()
+
+ def iter_after(self, target, after):
+ if not after:
+ return True
+
+ tp = self.model.get_path(target)
+ ap = self.model.get_path(after)
+
+ if tp[0] > ap[0] or (tp[0] == ap[0] and (len(ap) == 1 or tp[1] > ap[1])):
+ return True
+
+ return False
+
+ def on_tree_view_snippets_key_press(self, treeview, event):
+ if event.keyval == gdk.keyval_from_name('Delete'):
+ self.on_button_remove_snippet_clicked(None)
+ return True
+
+ def on_tree_view_snippets_row_expanded(self, treeview, piter, path):
+ # Check if it is already filled
+ self.fill_if_needed(piter)
+ self.select_iter(piter)
+
+ def on_entry_drop_targets_drag_data_received(self, entry, context, x, y, selection_data, info, timestamp):
+ if not gtk.targets_include_uri(context.targets):
+ return
+
+ uris = drop_get_uris(selection_data)
+
+ if not uris:
+ return
+
+ if entry.get_text():
+ mimes = [entry.get_text()]
+ else:
+ mimes = []
+
+ for uri in uris:
+ try:
+ mime = gio.content_type_guess(uri)
+ except:
+ mime = None
+
+ if mime:
+ mimes.append(mime)
+
+ entry.set_text(', '.join(mimes))
+ self.on_entry_drop_targets_focus_out(entry, None)
+ context.finish(True, False, timestamp)
+
+ entry.stop_emission('drag_data_received')
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/Parser.py b/plugins/snippets/snippets/Parser.py
new file mode 100755
index 00000000..3bbaf6e7
--- /dev/null
+++ b/plugins/snippets/snippets/Parser.py
@@ -0,0 +1,259 @@
+# Gedit snippets plugin
+# Copyright (C) 2006-2007 Jesse van den Kieboom <[email protected]>
+#
+# 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 sys
+from SubstitutionParser import SubstitutionParser
+
+class Token:
+ def __init__(self, klass, data):
+ self.klass = klass
+ self.data = data
+
+ def __str__(self):
+ return '%s: [%s]' % (self.klass, self.data)
+
+ def __eq__(self, other):
+ return self.klass == other.klass and self.data == other.data
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+class Parser:
+ SREG_ENV = '[A-Z_]+'
+ SREG_ID = '[0-9]+'
+
+ REG_ESCAPE = re.compile('(\\$(%s|\\(|\\{|<|%s)|`|\\\\)' % (SREG_ENV, SREG_ID))
+
+ def __init__(self, **kwargs):
+ for k, v in kwargs.items():
+ setattr(self, k, v)
+
+ self.position = 0
+ self.data_length = len(self.data)
+
+ self.RULES = (self._match_env, self._match_regex, self._match_placeholder, self._match_shell, self._match_eval, self._text)
+
+ def remains(self):
+ return self.data[self.position:]
+
+ def next_char(self):
+ if self.position + 1 >= self.data_length:
+ return ''
+ else:
+ return self.data[self.position + 1]
+
+ def char(self):
+ if self.position >= self.data_length:
+ return ''
+ else:
+ return self.data[self.position]
+
+ def token(self):
+ self.tktext = ''
+
+ while self.position < self.data_length:
+ try:
+ # Get first character
+ func = {'$': self._rule,
+ '`': self._try_match_shell}[self.char()]
+ except:
+ func = self._text
+
+ # Detect end of text token
+ if func != self._text and self.tktext != '':
+ return Token('text', self.tktext)
+
+ tk = func()
+
+ if tk:
+ return tk
+
+ if self.tktext != '':
+ return Token('text', self.tktext)
+
+ def _need_escape(self):
+ text = self.remains()[1:]
+
+ if text == '':
+ return False
+
+ return self.REG_ESCAPE.match(text)
+
+ def _escape(self):
+ if not self._need_escape():
+ return
+
+ # Increase position with 1
+ self.position += 1
+
+ def _text(self):
+ if self.char() == '\\':
+ self._escape()
+
+ self.tktext += self.char()
+ self.position += 1
+
+ def _rule(self):
+ for rule in self.RULES:
+ res = rule()
+
+ if res:
+ return res
+
+ def _match_env(self):
+ text = self.remains()
+ match = re.match('\\$(%s)' % self.SREG_ENV, text) or re.match('\\${(%s)}' % self.SREG_ENV, text)
+
+ if match:
+ self.position += len(match.group(0))
+ return Token('environment', match.group(1))
+
+ def _parse_list(self, lst):
+ pos = 0
+ length = len(lst)
+ items = []
+ last = None
+
+ while pos < length:
+ char = lst[pos]
+ next = pos < length - 1 and lst[pos + 1]
+
+ if char == '\\' and (next == ',' or next == ']'):
+ char = next
+ pos += 1
+ elif char == ',':
+ if last != None:
+ items.append(last)
+
+ last = None
+ pos += 1
+ continue
+
+ last = (last != None and last + char) or char
+ pos += 1
+
+ if last != None:
+ items.append(last)
+
+ return items
+
+ def _parse_default(self, default):
+ match = re.match('^\\s*(\\\\)?(\\[((\\\\]|[^\\]])+)\\]\\s*)$', default)
+
+ if not match:
+ return [default]
+
+ groups = match.groups()
+
+ if groups[0]:
+ return [groups[1]]
+
+ return self._parse_list(groups[2])
+
+ def _match_placeholder(self):
+ text = self.remains()
+
+ match = re.match('\\${(%s)(:((\\\\\\}|[^}])+))?}' % self.SREG_ID, text) or re.match('\\$(%s)' % self.SREG_ID, text)
+
+ if not match:
+ return None
+
+ groups = match.groups()
+ default = ''
+ tabstop = int(groups[0])
+ self.position += len(match.group(0))
+
+ if len(groups) > 1 and groups[2]:
+ default = self._parse_default(groups[2].replace('\\}', '}'))
+
+ return Token('placeholder', {'tabstop': tabstop, 'default': default})
+
+ def _match_shell(self):
+ text = self.remains()
+ match = re.match('`((%s):)?((\\\\`|[^`])+?)`' % self.SREG_ID, text) or re.match('\\$\\(((%s):)?((\\\\\\)|[^\\)])+?)\\)' % self.SREG_ID, text)
+
+ if not match:
+ return None
+
+ groups = match.groups()
+ tabstop = (groups[1] and int(groups[1])) or -1
+ self.position += len(match.group(0))
+
+ if text[0] == '`':
+ contents = groups[2].replace('\\`', '`')
+ else:
+ contents = groups[2].replace('\\)', ')')
+
+ return Token('shell', {'tabstop': tabstop, 'contents': contents})
+
+ def _try_match_shell(self):
+ return self._match_shell() or self._text()
+
+ def _eval_options(self, options):
+ reg = re.compile(self.SREG_ID)
+ tabstop = -1
+ depend = []
+
+ options = options.split(':')
+
+ for opt in options:
+ if reg.match(opt):
+ tabstop = int(opt)
+ else:
+ depend += self._parse_list(opt[1:-1])
+
+ return (tabstop, depend)
+
+ def _match_eval(self):
+ text = self.remains()
+
+ options = '((%s)|\\[([0-9, ]+)\\])' % self.SREG_ID
+ match = re.match('\\$<((%s:)*)((\\\\>|[^>])+?)>' % options, text)
+
+ if not match:
+ return None
+
+ groups = match.groups()
+ (tabstop, depend) = (groups[0] and self._eval_options(groups[0][:-1])) or (-1, [])
+ self.position += len(match.group(0))
+
+ return Token('eval', {'tabstop': tabstop, 'dependencies': depend, 'contents': groups[5].replace('\\>', '>')})
+
+ def _match_regex(self):
+ text = self.remains()
+
+ content = '((?:\\\\[/]|\\\\}|[^/}])+)'
+ match = re.match('\\${(?:(%s):)?\\s*(%s|\\$([A-Z_]+))?[/]%s[/]%s(?:[/]([a-zA-Z]*))?}' % (self.SREG_ID, self.SREG_ID, content, content), text)
+
+ if not match:
+ return None
+
+ groups = match.groups()
+ tabstop = (groups[0] and int(groups[0])) or -1
+ inp = (groups[2] or (groups[1] and int(groups[1]))) or ''
+
+ pattern = re.sub('\\\\([/}])', '\\1', groups[3])
+ substitution = re.sub('\\\\([/}])', '\\1', groups[4])
+ modifiers = groups[5] or ''
+
+ self.position += len(match.group(0))
+
+ return Token('regex', {'tabstop': tabstop, 'input': inp, 'pattern': pattern, 'substitution': substitution, 'modifiers': modifiers})
+
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/Placeholder.py b/plugins/snippets/snippets/Placeholder.py
new file mode 100755
index 00000000..c43eecac
--- /dev/null
+++ b/plugins/snippets/snippets/Placeholder.py
@@ -0,0 +1,700 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <[email protected]>
+#
+# 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 traceback
+import re
+import os
+import sys
+import signal
+import select
+import locale
+import subprocess
+from SubstitutionParser import SubstitutionParser
+import gobject
+
+from Helper import *
+
+# These are places in a view where the cursor can go and do things
+class Placeholder:
+ def __init__(self, view, tabstop, defaults, begin):
+ self.ok = True
+ self.done = False
+ self.buf = view.get_buffer()
+ self.view = view
+ self.has_references = False
+ self.mirrors = []
+ self.leave_mirrors = []
+ self.tabstop = tabstop
+ self.set_default(defaults)
+ self.prev_contents = self.default
+ self.set_mark_gravity()
+
+ if begin:
+ self.begin = self.buf.create_mark(None, begin, self.mark_gravity[0])
+ else:
+ self.begin = None
+
+ self.end = None
+
+ def __str__(self):
+ return '%s (%s)' % (str(self.__class__), str(self.default))
+
+ def set_mark_gravity(self):
+ self.mark_gravity = [True, False]
+
+ def set_default(self, defaults):
+ self.default = None
+ self.defaults = []
+
+ if not defaults:
+ return
+
+ for d in defaults:
+ dm = self.expand_environment(d)
+
+ if dm:
+ self.defaults.append(dm)
+
+ if not self.default:
+ self.default = dm
+
+ if dm != d:
+ break
+
+
+ def literal(self, s):
+ return repr(s)
+
+ def format_environment(self, s):
+ return s
+
+ def re_environment(self, m):
+ if m.group(1) or not m.group(2) in os.environ:
+ return '$' + m.group(2)
+ else:
+ return self.format_environment(os.environ[m.group(2)])
+
+ def expand_environment(self, text):
+ if not text:
+ return text
+
+ return re.sub('(\\\\)?\\$([A-Z_]+)', self.re_environment, text)
+
+ def get_iter(self, mark):
+ if mark and not mark.get_deleted():
+ return self.buf.get_iter_at_mark(mark)
+ else:
+ return None
+
+ def begin_iter(self):
+ return self.get_iter(self.begin)
+
+ def end_iter(self):
+ return self.get_iter(self.end)
+
+ def run_last(self, placeholders):
+ begin = self.begin_iter()
+ self.end = self.buf.create_mark(None, begin, self.mark_gravity[1])
+
+ if self.default:
+ insert_with_indent(self.view, begin, self.default, False, self)
+
+ def remove(self, force = False):
+ if self.begin and not self.begin.get_deleted():
+ self.buf.delete_mark(self.begin)
+
+ if self.end and not self.end.get_deleted():
+ self.buf.delete_mark(self.end)
+
+ # Do something on beginning this placeholder
+ def enter(self):
+ if not self.begin or self.begin.get_deleted():
+ return
+
+ self.buf.move_mark(self.buf.get_insert(), self.begin_iter())
+
+ if self.end:
+ self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter())
+ else:
+ self.buf.move_mark(self.buf.get_selection_bound(), self.begin_iter())
+
+ def get_text(self):
+ if self.begin and self.end:
+ biter = self.begin_iter()
+ eiter = self.end_iter()
+
+ if biter and eiter:
+ return self.buf.get_text(self.begin_iter(), self.end_iter())
+ else:
+ return ''
+ else:
+ return ''
+
+ def add_mirror(self, mirror, onleave = False):
+ mirror.has_references = True
+
+ if onleave:
+ self.leave_mirrors.append(mirror)
+ else:
+ self.mirrors.append(mirror)
+
+ def set_text(self, text):
+ if self.begin.get_deleted() or self.end.get_deleted():
+ return
+
+ # Set from self.begin to self.end to text!
+ self.buf.begin_user_action()
+ # Remove everything between self.begin and self.end
+ begin = self.begin_iter()
+ self.buf.delete(begin, self.end_iter())
+
+ # Insert the text from the mirror
+ insert_with_indent(self.view, begin, text, True, self)
+ self.buf.end_user_action()
+
+ self.update_contents()
+
+ def update_contents(self):
+ prev = self.prev_contents
+ self.prev_contents = self.get_text()
+
+ if prev != self.get_text():
+ for mirror in self.mirrors:
+ if not mirror.update(self):
+ return
+
+ def update_leave_mirrors(self):
+ # Notify mirrors
+ for mirror in self.leave_mirrors:
+ if not mirror.update(self):
+ return
+
+ # Do something on ending this placeholder
+ def leave(self):
+ self.update_leave_mirrors()
+
+ def find_mirrors(self, text, placeholders):
+ mirrors = []
+
+ while (True):
+ m = re.search('(\\\\)?\\$(?:{([0-9]+)}|([0-9]+))', text)
+
+ if not m:
+ break
+
+ # Skip escaped mirrors
+ if m.group(1):
+ text = text[m.end():]
+ continue
+
+ tabstop = int(m.group(2) or m.group(3))
+
+ if tabstop in placeholders:
+ if not tabstop in mirrors:
+ mirrors.append(tabstop)
+
+ text = text[m.end():]
+ else:
+ self.ok = False
+ return None
+
+ return mirrors
+
+# This is an placeholder which inserts a mirror of another Placeholder
+class PlaceholderMirror(Placeholder):
+ def __init__(self, view, tabstop, begin):
+ Placeholder.__init__(self, view, -1, None, begin)
+ self.mirror_stop = tabstop
+
+ def update(self, mirror):
+ self.set_text(mirror.get_text())
+ return True
+
+ def run_last(self, placeholders):
+ Placeholder.run_last(self, placeholders)
+
+ if self.mirror_stop in placeholders:
+ mirror = placeholders[self.mirror_stop]
+
+ mirror.add_mirror(self)
+
+ if mirror.default:
+ self.set_text(mirror.default)
+ else:
+ self.ok = False
+
+# This placeholder indicates the end of a snippet
+class PlaceholderEnd(Placeholder):
+ def __init__(self, view, begin, default):
+ Placeholder.__init__(self, view, 0, default, begin)
+
+ def run_last(self, placeholders):
+ Placeholder.run_last(self, placeholders)
+
+ # Remove the begin mark and set the begin mark
+ # to the end mark, this is needed so the end placeholder won't contain
+ # any text
+
+ if not self.default:
+ self.mark_gravity[0] = False
+ self.buf.delete_mark(self.begin)
+ self.begin = self.buf.create_mark(None, self.end_iter(), self.mark_gravity[0])
+
+ def enter(self):
+ if self.begin and not self.begin.get_deleted():
+ self.buf.move_mark(self.buf.get_insert(), self.begin_iter())
+
+ if self.end and not self.end.get_deleted():
+ self.buf.move_mark(self.buf.get_selection_bound(), self.end_iter())
+
+ def leave(self):
+ self.enter()
+
+# This placeholder is used to expand a command with embedded mirrors
+class PlaceholderExpand(Placeholder):
+ def __init__(self, view, tabstop, begin, s):
+ Placeholder.__init__(self, view, tabstop, None, begin)
+
+ self.mirror_text = {0: ''}
+ self.timeout_id = None
+ self.cmd = s
+ self.instant_update = False
+
+ def __str__(self):
+ s = Placeholder.__str__(self)
+
+ return s + ' ' + self.cmd
+
+ def get_mirrors(self, placeholders):
+ return self.find_mirrors(self.cmd, placeholders)
+
+ # Check if all substitution placeholders are accounted for
+ def run_last(self, placeholders):
+ Placeholder.run_last(self, placeholders)
+
+ self.ok = True
+ mirrors = self.get_mirrors(placeholders)
+
+ if mirrors:
+ allDefault = True
+
+ for mirror in mirrors:
+ p = placeholders[mirror]
+ p.add_mirror(self, not self.instant_update)
+ self.mirror_text[p.tabstop] = p.default
+
+ if not p.default and not isinstance(p, PlaceholderExpand):
+ allDefault = False
+
+ if allDefault:
+ self.update(None)
+ self.default = self.get_text() or None
+ else:
+ self.update(None)
+ self.default = self.get_text() or None
+
+ if self.tabstop == -1:
+ self.done = True
+
+ def re_placeholder(self, m, formatter):
+ if m.group(1):
+ return '"$' + m.group(2) + '"'
+ else:
+ if m.group(3):
+ index = int(m.group(3))
+ else:
+ index = int(m.group(4))
+
+ return formatter(self.mirror_text[index])
+
+ def remove_timeout(self):
+ if self.timeout_id != None:
+ gobject.source_remove(self.timeout_id)
+ self.timeout_id = None
+
+ def install_timeout(self):
+ self.remove_timeout()
+ self.timeout_id = gobject.timeout_add(1000, self.timeout_cb)
+
+ def timeout_cb(self):
+ self.timeout_id = None
+
+ return False
+
+ def format_environment(self, text):
+ return self.literal(text)
+
+ def substitute(self, text, formatter = None):
+ formatter = formatter or self.literal
+
+ # substitute all mirrors, but also environmental variables
+ text = re.sub('(\\\\)?\\$({([0-9]+)}|([0-9]+))', lambda m: self.re_placeholder(m, formatter),
+ text)
+
+ return self.expand_environment(text)
+
+ def run_update(self):
+ text = self.substitute(self.cmd)
+
+ if text:
+ ret = self.expand(text)
+
+ if ret:
+ self.update_leave_mirrors()
+ else:
+ ret = True
+
+ return ret
+
+ def update(self, mirror):
+ text = None
+
+ if mirror:
+ self.mirror_text[mirror.tabstop] = mirror.get_text()
+
+ # Check if all substitutions have been made
+ for tabstop in self.mirror_text:
+ if tabstop == 0:
+ continue
+
+ if self.mirror_text[tabstop] == None:
+ return False
+
+ return self.run_update()
+
+ def expand(self, text):
+ return True
+
+# The shell placeholder executes commands in a subshell
+class PlaceholderShell(PlaceholderExpand):
+ def __init__(self, view, tabstop, begin, s):
+ PlaceholderExpand.__init__(self, view, tabstop, begin, s)
+
+ self.shell = None
+ self.remove_me = False
+
+ def close_shell(self):
+ self.shell.stdout.close()
+ self.shell = None
+
+ def timeout_cb(self):
+ PlaceholderExpand.timeout_cb(self)
+ self.remove_timeout()
+
+ if not self.shell:
+ return False
+
+ gobject.source_remove(self.watch_id)
+ self.close_shell()
+
+ if self.remove_me:
+ PlaceholderExpand.remove(self)
+
+ message_dialog(None, gtk.MESSAGE_ERROR, 'Execution of the shell ' \
+ 'command (%s) exceeded the maximum time; ' \
+ 'execution aborted.' % self.command)
+
+ return False
+
+ def process_close(self):
+ self.close_shell()
+ self.remove_timeout()
+
+ self.set_text(str.join('', self.shell_output).rstrip('\n'))
+
+ if self.default == None:
+ self.default = self.get_text()
+ self.leave()
+
+ if self.remove_me:
+ PlaceholderExpand.remove(self, True)
+
+ def process_cb(self, source, condition):
+ if condition & gobject.IO_IN:
+ line = source.readline()
+
+ if len(line) > 0:
+ try:
+ line = unicode(line, 'utf-8')
+ except:
+ line = unicode(line, locale.getdefaultlocale()[1],
+ 'replace')
+
+ self.shell_output += line
+ self.install_timeout()
+
+ return True
+
+ self.process_close()
+ return False
+
+ def literal_replace(self, match):
+ return "\\%s" % (match.group(0))
+
+ def literal(self, text):
+ return '"' + re.sub('([\\\\"])', self.literal_replace, text) + '"'
+
+ def expand(self, text):
+ self.remove_timeout()
+
+ if self.shell:
+ gobject.source_remove(self.watch_id)
+ self.close_shell()
+
+ popen_args = {
+ 'cwd' : None,
+ 'shell': True,
+ 'env' : os.environ,
+ 'stdout': subprocess.PIPE
+ }
+
+ self.command = text
+ self.shell = subprocess.Popen(text, **popen_args)
+ self.shell_output = ''
+ self.watch_id = gobject.io_add_watch(self.shell.stdout, gobject.IO_IN | \
+ gobject.IO_HUP, self.process_cb)
+ self.install_timeout()
+
+ return True
+
+ def remove(self, force = False):
+ if not force and self.shell:
+ # Still executing shell command
+ self.remove_me = True
+ else:
+ if force:
+ self.remove_timeout()
+
+ if self.shell:
+ self.close_shell()
+
+ PlaceholderExpand.remove(self, force)
+
+class TimeoutError(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+# The python placeholder evaluates commands in python
+class PlaceholderEval(PlaceholderExpand):
+ def __init__(self, view, tabstop, refs, begin, s, namespace):
+ PlaceholderExpand.__init__(self, view, tabstop, begin, s)
+
+ self.fdread = 0
+ self.remove_me = False
+ self.namespace = namespace
+
+ self.refs = []
+
+ if refs:
+ for ref in refs:
+ self.refs.append(int(ref.strip()))
+
+ def get_mirrors(self, placeholders):
+ mirrors = PlaceholderExpand.get_mirrors(self, placeholders)
+
+ if not self.ok:
+ return None
+
+ for ref in self.refs:
+ if ref in placeholders:
+ if ref not in mirrors:
+ mirrors.append(ref)
+ else:
+ self.ok = False
+ return None
+
+ return mirrors
+
+ # SIGALRM is not supported on all platforms (e.g. windows). Timeout
+ # with SIGALRM will not be used on those platforms. This will
+ # potentially block gedit if you have a placeholder which gets stuck,
+ # but it's better than not supporting them at all. At some point we
+ # might have proper thread support and we can fix this in a better way
+ def timeout_supported(self):
+ return hasattr(signal, 'SIGALRM')
+
+ def timeout_cb(self, signum = 0, frame = 0):
+ raise TimeoutError, "Operation timed out (>2 seconds)"
+
+ def install_timeout(self):
+ if not self.timeout_supported():
+ return
+
+ if self.timeout_id != None:
+ self.remove_timeout()
+
+ self.timeout_id = signal.signal(signal.SIGALRM, self.timeout_cb)
+ signal.alarm(2)
+
+ def remove_timeout(self):
+ if not self.timeout_supported():
+ return
+
+ if self.timeout_id != None:
+ signal.alarm(0)
+
+ signal.signal(signal.SIGALRM, self.timeout_id)
+
+ self.timeout_id = None
+
+ def expand(self, text):
+ self.remove_timeout()
+
+ text = text.strip()
+ self.command = text
+
+ if not self.command or self.command == '':
+ self.set_text('')
+ return
+
+ text = "def process_snippet():\n\t" + "\n\t".join(text.split("\n"))
+
+ if 'process_snippet' in self.namespace:
+ del self.namespace['process_snippet']
+
+ try:
+ exec text in self.namespace
+ except:
+ traceback.print_exc()
+
+ if 'process_snippet' in self.namespace:
+ try:
+ # Install a sigalarm signal. This is a HACK to make sure
+ # gedit doesn't get freezed by someone creating a python
+ # placeholder which for instance loops indefinately. Since
+ # the code is executed synchronously it will hang gedit. With
+ # the alarm signal we raise an exception and catch this
+ # (see below). We show an error message and return False.
+ # ___this is a HACK___ and should be fixed properly (I just
+ # don't know how)
+ self.install_timeout()
+ result = self.namespace['process_snippet']()
+ self.remove_timeout()
+ except TimeoutError:
+ self.remove_timeout()
+
+ message_dialog(None, gtk.MESSAGE_ERROR, \
+ _('Execution of the Python command (%s) exceeds the maximum ' \
+ 'time, execution aborted.') % self.command)
+
+ return False
+ except Exception, detail:
+ self.remove_timeout()
+
+ message_dialog(None, gtk.MESSAGE_ERROR,
+ _('Execution of the Python command (%s) failed: %s') %
+ (self.command, detail))
+
+ return False
+
+ if result == None:
+ # sys.stderr.write("%s:\n>> %s\n" % (_('The following python code, run in a snippet, does not return a value'), "\n>> ".join(self.command.split("\n"))))
+ result = ''
+
+ self.set_text(str(result))
+
+ return True
+
+# Regular expression placeholder
+class PlaceholderRegex(PlaceholderExpand):
+ def __init__(self, view, tabstop, begin, inp, pattern, substitution, modifiers):
+ PlaceholderExpand.__init__(self, view, tabstop, begin, '')
+
+ self.instant_update = True
+ self.inp = inp
+ self.pattern = pattern
+ self.substitution = substitution
+
+ self.init_modifiers(modifiers)
+
+ def init_modifiers(self, modifiers):
+ mods = {'I': re.I,
+ 'L': re.L,
+ 'M': re.M,
+ 'S': re.S,
+ 'U': re.U,
+ 'X': re.X}
+
+ self.modifiers = 0
+
+ for modifier in modifiers:
+ if modifier in mods:
+ self.modifiers |= mods[modifier]
+
+ def get_mirrors(self, placeholders):
+ mirrors = self.find_mirrors(self.pattern, placeholders) + self.find_mirrors(self.substitution, placeholders)
+
+ if isinstance(self.inp, int):
+ if self.inp not in placeholders:
+ self.ok = False
+ return None
+ elif self.inp not in mirrors:
+ mirrors.append(self.inp)
+
+ return mirrors
+
+ def literal(self, s):
+ return re.escape(s)
+
+ def get_input(self):
+ if isinstance(self.inp, int):
+ return self.mirror_text[self.inp]
+ elif self.inp in os.environ:
+ return os.environ[self.inp]
+ else:
+ return ''
+
+ def run_update(self):
+ pattern = self.substitute(self.pattern)
+ substitution = self.substitute(self.substitution, SubstitutionParser.escape_substitution)
+
+ if pattern:
+ return self.expand(pattern, substitution)
+
+ return True
+
+ def expand(self, pattern, substitution):
+ # Try to compile pattern
+ try:
+ regex = re.compile(pattern, self.modifiers)
+ except re.error, message:
+ sys.stderr.write('Could not compile regular expression: %s\n%s\n' % (pattern, message))
+ return False
+
+ inp = self.get_input()
+ match = regex.search(inp)
+
+ if not match:
+ self.set_text(inp)
+ else:
+ groups = match.groupdict()
+
+ idx = 0
+ for group in match.groups():
+ groups[str(idx + 1)] = group
+ idx += 1
+
+ groups['0'] = match.group(0)
+
+ parser = SubstitutionParser(substitution, groups)
+ self.set_text(parser.parse())
+
+ return True
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/Snippet.py b/plugins/snippets/snippets/Snippet.py
new file mode 100755
index 00000000..d7baead5
--- /dev/null
+++ b/plugins/snippets/snippets/Snippet.py
@@ -0,0 +1,355 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <[email protected]>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import os
+import gio
+
+from Placeholder import *
+from Parser import Parser, Token
+from Helper import *
+
+class EvalUtilities:
+ def __init__(self, view=None):
+ self.view = view
+ self._init_namespace()
+
+ def _init_namespace(self):
+ self.namespace = {
+ '__builtins__': __builtins__,
+ 'align': self.util_align,
+ 'readfile': self.util_readfile,
+ 'filesize': self.util_filesize
+ }
+
+ def _real_len(self, s, tablen = 0):
+ if tablen == 0:
+ tablen = self.view.get_tab_width()
+
+ return len(s.expandtabs(tablen))
+
+ def _filename_to_uri(self, filename):
+ gfile = gio.File(filename)
+
+ return gfile.get_uri()
+
+ def util_readfile(self, filename):
+ stream = gio.File(filename).read()
+
+ if not stream:
+ return ''
+
+ res = stream.read()
+ stream.close()
+
+ return res
+
+ def util_filesize(self, filename):
+ gfile = gio.File(filename)
+ info = gfile.query_info(gio.FILE_ATTRIBUTE_STANDARD_SIZE)
+
+ if not info:
+ return 0
+
+ return info.get_size()
+
+ def util_align(self, items):
+ maxlen = []
+ tablen = self.view.get_tab_width()
+
+ for row in range(0, len(items)):
+ for col in range(0, len(items[row]) - 1):
+ if row == 0:
+ maxlen.append(0)
+
+ items[row][col] += "\t"
+ rl = self._real_len(items[row][col], tablen)
+
+ if (rl > maxlen[col]):
+ maxlen[col] = rl
+
+ result = ''
+
+ for row in range(0, len(items)):
+ for col in range(0, len(items[row]) - 1):
+ item = items[row][col]
+
+ result += item + ("\t" * ((maxlen[col] - \
+ self._real_len(item, tablen)) / tablen))
+
+ result += items[row][len(items[row]) - 1]
+
+ if row != len(items) - 1:
+ result += "\n"
+
+ return result
+
+class Snippet:
+ def __init__(self, data):
+ self.data = data
+
+ def __getitem__(self, prop):
+ return self.data[prop]
+
+ def __setitem__(self, prop, value):
+ self.data[prop] = value
+
+ def accelerator_display(self):
+ accel = self['accelerator']
+
+ if accel:
+ keyval, mod = gtk.accelerator_parse(accel)
+ accel = gtk.accelerator_get_label(keyval, mod)
+
+ return accel or ''
+
+ def display(self):
+ nm = markup_escape(self['description'])
+
+ tag = self['tag']
+ accel = self.accelerator_display()
+ detail = []
+
+ if tag and tag != '':
+ detail.append(tag)
+
+ if accel and accel != '':
+ detail.append(accel)
+
+ if not detail:
+ return nm
+ else:
+ return nm + ' (<b>' + markup_escape(str.join(', ', detail)) + \
+ '</b>)'
+
+ def _add_placeholder(self, placeholder):
+ if placeholder.tabstop in self.placeholders:
+ if placeholder.tabstop == -1:
+ self.placeholders[-1].append(placeholder)
+ self.plugin_data.ordered_placeholders.append(placeholder)
+ elif placeholder.tabstop == -1:
+ self.placeholders[-1] = [placeholder]
+ self.plugin_data.ordered_placeholders.append(placeholder)
+ else:
+ self.placeholders[placeholder.tabstop] = placeholder
+ self.plugin_data.ordered_placeholders.append(placeholder)
+
+ def _insert_text(self, text):
+ # Insert text keeping indentation in mind
+ indented = unicode.join('\n' + unicode(self._indent), spaces_instead_of_tabs(self._view, text).split('\n'))
+ self._view.get_buffer().insert(self._insert_iter(), indented)
+
+ def _insert_iter(self):
+ return self._view.get_buffer().get_iter_at_mark(self._insert_mark)
+
+ def _create_environment(self, data):
+ val = ((data in os.environ) and os.environ[data]) or ''
+
+ # Get all the current indentation
+ all_indent = compute_indentation(self._view, self._insert_iter())
+
+ # Substract initial indentation to get the snippet indentation
+ indent = all_indent[len(self._indent):]
+
+ # Keep indentation
+ return unicode.join('\n' + unicode(indent), val.split('\n'))
+
+ def _create_placeholder(self, data):
+ tabstop = data['tabstop']
+ begin = self._insert_iter()
+
+ if tabstop == 0:
+ # End placeholder
+ return PlaceholderEnd(self._view, begin, data['default'])
+ elif tabstop in self.placeholders:
+ # Mirror placeholder
+ return PlaceholderMirror(self._view, tabstop, begin)
+ else:
+ # Default placeholder
+ return Placeholder(self._view, tabstop, data['default'], begin)
+
+ def _create_shell(self, data):
+ begin = self._insert_iter()
+ return PlaceholderShell(self._view, data['tabstop'], begin, data['contents'])
+
+ def _create_eval(self, data):
+ begin = self._insert_iter()
+ return PlaceholderEval(self._view, data['tabstop'], data['dependencies'], begin, data['contents'], self._utils.namespace)
+
+ def _create_regex(self, data):
+ begin = self._insert_iter()
+ return PlaceholderRegex(self._view, data['tabstop'], begin, data['input'], data['pattern'], data['substitution'], data['modifiers'])
+
+ def _create_text(self, data):
+ return data
+
+ def _invalid_placeholder(self, placeholder, remove):
+ buf = self._view.get_buffer()
+
+ # Remove the text because this placeholder is invalid
+ if placeholder.default and remove:
+ buf.delete(placeholder.begin_iter(), placeholder.end_iter())
+
+ placeholder.remove()
+
+ if placeholder.tabstop == -1:
+ index = self.placeholders[-1].index(placeholder)
+ del self.placeholders[-1][index]
+ else:
+ del self.placeholders[placeholder.tabstop]
+
+ self.plugin_data.ordered_placeholders.remove(placeholder)
+
+ def _parse(self, plugin_data):
+ # Initialize current variables
+ self._view = plugin_data.view
+ self._indent = compute_indentation(self._view, self._view.get_buffer().get_iter_at_mark(self.begin_mark))
+ self._utils = EvalUtilities(self._view)
+ self.placeholders = {}
+ self._insert_mark = self.end_mark
+ self.plugin_data = plugin_data
+
+ # Create parser
+ parser = Parser(data=self['text'])
+
+ # Parse tokens
+ while (True):
+ token = parser.token()
+
+ if not token:
+ break
+
+ try:
+ val = {'environment': self._create_environment,
+ 'placeholder': self._create_placeholder,
+ 'shell': self._create_shell,
+ 'eval': self._create_eval,
+ 'regex': self._create_regex,
+ 'text': self._create_text}[token.klass](token.data)
+ except:
+ sys.stderr.write('Token class not supported: %s\n' % token.klass)
+ continue
+
+ if isinstance(val, basestring):
+ # Insert text
+ self._insert_text(val)
+ else:
+ # Insert placeholder
+ self._add_placeholder(val)
+
+ # Create end placeholder if there isn't one yet
+ if 0 not in self.placeholders:
+ self.placeholders[0] = PlaceholderEnd(self._view, self.end_iter(), None)
+ self.plugin_data.ordered_placeholders.append(self.placeholders[0])
+
+ # Make sure run_last is ran for all placeholders and remove any
+ # non `ok` placeholders
+ for tabstop in self.placeholders.copy():
+ ph = (tabstop == -1 and list(self.placeholders[-1])) or [self.placeholders[tabstop]]
+
+ for placeholder in ph:
+ placeholder.run_last(self.placeholders)
+
+ if not placeholder.ok or placeholder.done:
+ self._invalid_placeholder(placeholder, not placeholder.ok)
+
+ # Remove all the Expand placeholders which have a tabstop because
+ # they can be used to mirror, but they shouldn't be real tabstops
+ # (if they have mirrors installed). This is problably a bit of
+ # a dirty hack :)
+ if -1 not in self.placeholders:
+ self.placeholders[-1] = []
+
+ for tabstop in self.placeholders.copy():
+ placeholder = self.placeholders[tabstop]
+
+ if tabstop != -1:
+ if isinstance(placeholder, PlaceholderExpand) and \
+ placeholder.has_references:
+ # Add to anonymous placeholders
+ self.placeholders[-1].append(placeholder)
+
+ # Remove placeholder
+ del self.placeholders[tabstop]
+
+ self.plugin_data = None
+
+ def insert_into(self, plugin_data, insert):
+ buf = plugin_data.view.get_buffer()
+ last_index = 0
+
+ # Find closest mark at current insertion, so that we may insert
+ # our marks in the correct order
+ (current, next) = plugin_data.next_placeholder()
+
+ if current:
+ # Insert AFTER current
+ last_index = plugin_data.placeholders.index(current) + 1
+ elif next:
+ # Insert BEFORE next
+ last_index = plugin_data.placeholders.index(next)
+ else:
+ # Insert at first position
+ last_index = 0
+
+ # lastIndex now contains the position of the last mark
+ # Create snippet bounding marks
+ self.begin_mark = buf.create_mark(None, insert, True)
+ self.end_mark = buf.create_mark(None, insert, False)
+
+ # Now parse the contents of this snippet, create Placeholders
+ # and insert the placholder marks in the marks array of plugin_data
+ self._parse(plugin_data)
+
+ # So now all of the snippet is in the buffer, we have all our
+ # placeholders right here, what's next, put all marks in the
+ # plugin_data.marks
+ k = self.placeholders.keys()
+ k.sort(reverse=True)
+
+ plugin_data.placeholders.insert(last_index, self.placeholders[0])
+ last_iter = self.placeholders[0].end_iter()
+
+ for tabstop in k:
+ if tabstop != -1 and tabstop != 0:
+ placeholder = self.placeholders[tabstop]
+ end_iter = placeholder.end_iter()
+
+ if last_iter.compare(end_iter) < 0:
+ last_iter = end_iter
+
+ # Inserting placeholder
+ plugin_data.placeholders.insert(last_index, placeholder)
+
+ # Move end mark to last placeholder
+ buf.move_mark(self.end_mark, last_iter)
+
+ return self
+
+ def deactivate(self):
+ buf = self.begin_mark.get_buffer()
+
+ buf.delete_mark(self.begin_mark)
+ buf.delete_mark(self.end_mark)
+
+ self.placeholders = {}
+
+ def begin_iter(self):
+ return self.begin_mark.get_buffer().get_iter_at_mark(self.begin_mark)
+
+ def end_iter(self):
+ return self.end_mark.get_buffer().get_iter_at_mark(self.end_mark)
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/SubstitutionParser.py b/plugins/snippets/snippets/SubstitutionParser.py
new file mode 100755
index 00000000..6522222b
--- /dev/null
+++ b/plugins/snippets/snippets/SubstitutionParser.py
@@ -0,0 +1,202 @@
+# Gedit snippets plugin
+# Copyright (C) 2006-2007 Jesse van den Kieboom <[email protected]>
+#
+# 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 ParseError(Exception):
+ def __str__(self):
+ return 'Parse error, resume next'
+
+class Modifiers:
+ def _first_char(s):
+ first = (s != '' and s[0]) or ''
+ rest = (len(s) > 1 and s[1:]) or ''
+
+ return first, rest
+
+ def upper_first(s):
+ first, rest = Modifiers._first_char(s)
+
+ return '%s%s' % (first.upper(), rest)
+
+ def upper(s):
+ return s.upper()
+
+ def lower_first(s):
+ first, rest = Modifiers._first_char(s)
+
+ return '%s%s' % (first.lower(), rest)
+
+ def lower(s):
+ return s.lower()
+
+ def title(s):
+ return s.title()
+
+ upper_first = staticmethod(upper_first)
+ upper = staticmethod(upper)
+ lower_first = staticmethod(lower_first)
+ lower = staticmethod(lower)
+ title = staticmethod(title)
+ _first_char = staticmethod(_first_char)
+
+class SubstitutionParser:
+ REG_ID = '[0-9]+'
+ REG_NAME = '[a-zA-Z_]+'
+ REG_MOD = '[a-zA-Z]+'
+ REG_ESCAPE = '\\\\|\\(\\?|,|\\)'
+
+ def __init__(self, pattern, groups = {}, modifiers = {}):
+ self.pattern = pattern
+ self.groups = groups
+
+ self.REG_GROUP = '(?:(%s)|<(%s|%s)(?:,(%s))?>)' % (self.REG_ID, self.REG_ID, self.REG_NAME, self.REG_MOD)
+ self.modifiers = {'u': Modifiers.upper_first,
+ 'U': Modifiers.upper,
+ 'l': Modifiers.lower_first,
+ 'L': Modifiers.lower,
+ 't': Modifiers.title}
+
+ for k, v in modifiers.items():
+ self.modifiers[k] = v
+
+ def parse(self):
+ result, tokens = self._parse(self.pattern, None)
+
+ return result
+
+ def _parse(self, tokens, terminator):
+ result = ''
+
+ while tokens != '':
+ if self._peek(tokens) == '' or self._peek(tokens) == terminator:
+ tokens = self._remains(tokens)
+ break
+
+ try:
+ res, tokens = self._expr(tokens, terminator)
+ except ParseError:
+ res, tokens = self._text(tokens)
+
+ result += res
+
+ return result, tokens
+
+ def _peek(self, tokens, num = 0):
+ return (num < len(tokens) and tokens[num])
+
+ def _token(self, tokens):
+ if tokens == '':
+ return '', '';
+
+ return tokens[0], (len(tokens) > 1 and tokens[1:]) or ''
+
+ def _remains(self, tokens, num = 1):
+ return (num < len(tokens) and tokens[num:]) or ''
+
+ def _expr(self, tokens, terminator):
+ if tokens == '':
+ return ''
+
+ try:
+ return {'\\': self._escape,
+ '(': self._condition}[self._peek(tokens)](tokens, terminator)
+ except KeyError:
+ raise ParseError
+
+ def _text(self, tokens):
+ return self._token(tokens)
+
+ def _substitute(self, group, modifiers = ''):
+ result = (self.groups.has_key(group) and self.groups[group]) or ''
+
+ for modifier in modifiers:
+ if self.modifiers.has_key(modifier):
+ result = self.modifiers[modifier](result)
+
+ return result
+
+ def _match_group(self, tokens):
+ match = re.match('\\\\%s' % self.REG_GROUP, tokens)
+
+ if not match:
+ return None, tokens
+
+ return self._substitute(match.group(1) or match.group(2), match.group(3) or ''), tokens[match.end():]
+
+ def _escape(self, tokens, terminator):
+ # Try to match a group
+ result, tokens = self._match_group(tokens)
+
+ if result != None:
+ return result, tokens
+
+ s = self.REG_GROUP
+
+ if terminator:
+ s += '|%s' % re.escape(terminator)
+
+ match = re.match('\\\\(\\\\%s|%s)' % (s, self.REG_ESCAPE), tokens)
+
+ if not match:
+ raise ParseError
+
+ return match.group(1), tokens[match.end():]
+
+ def _condition_value(self, tokens):
+ match = re.match('\\\\?%s\s*' % self.REG_GROUP, tokens)
+
+ if not match:
+ return None, tokens
+
+ groups = match.groups()
+ name = groups[0] or groups[1]
+
+ return self.groups.has_key(name) and self.groups[name] != None, tokens[match.end():]
+
+ def _condition(self, tokens, terminator):
+ # Match ? after (
+ if self._peek(tokens, 1) != '?':
+ raise ParseError
+
+ # Remove initial (? token
+ tokens = self._remains(tokens, 2)
+ condition, tokens = self._condition_value(tokens)
+
+ if condition == None or self._peek(tokens) != ',':
+ raise ParseError
+
+ truepart, tokens = self._parse(self._remains(tokens), ',')
+
+ if truepart == None:
+ raise ParseError
+
+ falsepart, tokens = self._parse(tokens, ')')
+
+ if falsepart == None:
+ raise ParseError
+
+ if condition:
+ return truepart, tokens
+ else:
+ return falsepart, tokens
+
+ def escape_substitution(substitution):
+ return re.sub('(%s|%s)' % (self.REG_GROUP, self.REG_ESCAPE), '\\\\\\1', substitution)
+
+ escapesubstitution = staticmethod(escape_substitution)
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/WindowHelper.py b/plugins/snippets/snippets/WindowHelper.py
new file mode 100755
index 00000000..29bf3a58
--- /dev/null
+++ b/plugins/snippets/snippets/WindowHelper.py
@@ -0,0 +1,209 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <[email protected]>
+#
+# 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
+import os
+import gettext
+
+import gtk
+from gtk import gdk
+import gedit
+
+from Document import Document
+from Library import Library
+
+class WindowHelper:
+ def __init__(self, plugin):
+ self.plugin = plugin
+ self.current_controller = None
+ self.current_language = None
+ self.signal_ids = {}
+
+ def run(self, window):
+ self.window = window
+
+ self.insert_menu()
+ self.register_messages()
+
+ self.accel_group = Library().get_accel_group(None)
+
+ window.add_accel_group(self.accel_group)
+ window.connect('tab-added', self.on_tab_added)
+
+ # Add controllers to all the current views
+ for view in self.window.get_views():
+ if isinstance(view, gedit.View) and not self.has_controller(view):
+ view._snippet_controller = Document(self, view)
+
+ self.update()
+
+ def stop(self):
+ self.window.remove_accel_group(self.accel_group)
+ self.accel_group = None
+
+ #self.window.remove_accel_group(accel)
+ self.remove_menu()
+ self.unregister_messages()
+
+ # Iterate over all the tabs and remove every controller
+ for view in self.window.get_views():
+ if isinstance(view, gedit.View) and self.has_controller(view):
+ view._snippet_controller.stop()
+ view._snippet_controller = None
+
+ self.window = None
+ self.plugin = None
+
+ def register_messages(self):
+ bus = self.window.get_message_bus()
+
+ self.messages = {
+ 'activate': bus.register('/plugins/snippets', 'activate', ('view', 'iter'), trigger=str, view=gedit.View, iter=gtk.TextIter),
+ 'parse-and-activate': bus.register('/plugins/snippets', 'parse-and-activate', ('view', 'iter'), snippet=str, view=gedit.View, iter=gtk.TextIter)
+ }
+
+ bus.connect('/plugins/snippets', 'activate', self.on_message_activate)
+ bus.connect('/plugins/snippets', 'parse-and-activate', self.on_message_parse_and_activate)
+
+ def unregister_messages(self):
+ bus = self.window.get_message_bus()
+
+ for name in self.messages:
+ bus.unregister(self.messages[name])
+
+ self.messages = {}
+
+ def on_message_activate(self, bus, message):
+ if message.has_key('view'):
+ view = message.view
+ else:
+ view = self.window.get_active_view()
+
+ if not self.has_controller(view):
+ return
+
+ if message.has_key('iter'):
+ iter = message.iter
+ else:
+ iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
+
+ controller = view._snippet_controller
+ controller.run_snippet_trigger(message.trigger, (iter, iter))
+
+ def on_message_parse_and_activate(self, bus, message):
+ if message.has_key('view'):
+ view = message.view
+ else:
+ view = self.window.get_active_view()
+
+ if not self.has_controller(view):
+ return
+
+ if message.has_key('iter'):
+ iter = message.iter
+ else:
+ iter = view.get_buffer().get_iter_at_mark(view.get_buffer().get_insert())
+
+ controller = view._snippet_controller
+ controller.parse_and_run_snippet(message.snippet, iter)
+
+ def insert_menu(self):
+ manager = self.window.get_ui_manager()
+
+ self.action_group = gtk.ActionGroup("GeditSnippetPluginActions")
+ self.action_group.set_translation_domain('gedit')
+ self.action_group.add_actions([('ManageSnippets', None,
+ _('Manage _Snippets...'), \
+ None, _('Manage snippets'), \
+ self.on_action_snippets_activate)])
+
+ self.merge_id = manager.new_merge_id()
+ manager.insert_action_group(self.action_group, -1)
+ manager.add_ui(self.merge_id, '/MenuBar/ToolsMenu/ToolsOps_5', \
+ 'ManageSnippets', 'ManageSnippets', gtk.UI_MANAGER_MENUITEM, False)
+
+ def remove_menu(self):
+ manager = self.window.get_ui_manager()
+ manager.remove_ui(self.merge_id)
+ manager.remove_action_group(self.action_group)
+ self.action_group = None
+
+ def find_snippet(self, snippets, tag):
+ result = []
+
+ for snippet in snippets:
+ if Snippet(snippet)['tag'] == tag:
+ result.append(snippet)
+
+ return result
+
+ def has_controller(self, view):
+ return hasattr(view, '_snippet_controller') and view._snippet_controller
+
+ def update_language(self):
+ if not self.window:
+ return
+
+ if self.current_language:
+ accel_group = Library().get_accel_group( \
+ self.current_language)
+ self.window.remove_accel_group(accel_group)
+
+ if self.current_controller:
+ self.current_language = self.current_controller.language_id
+
+ if self.current_language != None:
+ accel_group = Library().get_accel_group( \
+ self.current_language)
+ self.window.add_accel_group(accel_group)
+ else:
+ self.current_language = None
+
+ def language_changed(self, controller):
+ if controller == self.current_controller:
+ self.update_language()
+
+ def update(self):
+ view = self.window.get_active_view()
+
+ if not view or not self.has_controller(view):
+ return
+
+ controller = view._snippet_controller
+
+ if controller != self.current_controller:
+ self.current_controller = controller
+ self.update_language()
+
+ # Callbacks
+
+ def on_tab_added(self, window, tab):
+ # Create a new controller for this tab if it has a standard gedit view
+ view = tab.get_view()
+
+ if isinstance(view, gedit.View) and not self.has_controller(view):
+ view._snippet_controller = Document(self, view)
+
+ self.update()
+
+ def on_action_snippets_activate(self, item):
+ self.plugin.create_configure_dialog()
+
+ def accelerator_activated(self, keyval, mod):
+ return self.current_controller.accelerator_activate(keyval, mod)
+
+# ex:ts=8:et:
diff --git a/plugins/snippets/snippets/__init__.py b/plugins/snippets/snippets/__init__.py
new file mode 100755
index 00000000..b21da508
--- /dev/null
+++ b/plugins/snippets/snippets/__init__.py
@@ -0,0 +1,101 @@
+# Gedit snippets plugin
+# Copyright (C) 2005-2006 Jesse van den Kieboom <[email protected]>
+#
+# 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 sys
+import os
+import shutil
+
+import gtk
+from gtk import gdk
+import gedit
+import platform
+
+from WindowHelper import WindowHelper
+from Library import Library
+from Manager import Manager
+from Snippet import Snippet
+
+class SnippetsPlugin(gedit.Plugin):
+ def __init__(self):
+ gedit.Plugin.__init__(self)
+
+ self.dlg = None
+
+ library = Library()
+ library.set_accelerator_callback(self.accelerator_activated)
+
+ if platform.platform() == 'Windows':
+ snippetsdir = os.path.expanduser('~/gedit/snippets')
+ else:
+ userdir = os.getenv('MATE22_USER_DIR')
+ if userdir:
+ snippetsdir = os.path.join(userdir, 'gedit/snippets')
+ else:
+ snippetsdir = os.path.expanduser('~/.mate2/gedit/snippets')
+
+ library.set_dirs(snippetsdir, self.system_dirs())
+
+ def system_dirs(self):
+ if platform.platform() != 'Windows':
+ if 'XDG_DATA_DIRS' in os.environ:
+ datadirs = os.environ['XDG_DATA_DIRS']
+ else:
+ datadirs = '/usr/local/share' + os.pathsep + '/usr/share'
+
+ dirs = []
+
+ for d in datadirs.split(os.pathsep):
+ d = os.path.join(d, 'gedit-2', 'plugins', 'snippets')
+
+ if os.path.isdir(d):
+ dirs.append(d)
+
+ dirs.append(self.get_data_dir())
+ return dirs
+
+ def activate(self, window):
+ data = WindowHelper(self)
+ window._snippets_plugin_data = data
+ data.run(window)
+
+ def deactivate(self, window):
+ window._snippets_plugin_data.stop()
+ window._snippets_plugin_data = None
+
+ def update_ui(self, window):
+ window._snippets_plugin_data.update()
+
+ def create_configure_dialog(self):
+ if not self.dlg:
+ self.dlg = Manager(self.get_data_dir())
+ else:
+ self.dlg.run()
+
+ window = gedit.app_get_default().get_active_window()
+
+ if window:
+ self.dlg.dlg.set_transient_for(window)
+
+ return self.dlg.dlg
+
+ def accelerator_activated(self, group, obj, keyval, mod):
+ ret = False
+
+ if hasattr(obj, '_snippets_plugin_data'):
+ ret = obj._snippets_plugin_data.accelerator_activated(keyval, mod)
+
+ return ret
diff --git a/plugins/snippets/snippets/snippets.ui b/plugins/snippets/snippets/snippets.ui
new file mode 100755
index 00000000..426df886
--- /dev/null
+++ b/plugins/snippets/snippets/snippets.ui
@@ -0,0 +1,647 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0">text</col>
+ </row>
+ <row>
+ <col id="0">text/plain</col>
+ </row>
+ <row>
+ <col id="0">text/xml</col>
+ </row>
+ <row>
+ <col id="0">image</col>
+ </row>
+ <row>
+ <col id="0">image/png</col>
+ </row>
+ <row>
+ <col id="0">image/jpeg</col>
+ </row>
+ <row>
+ <col id="0">audio</col>
+ </row>
+ <row>
+ <col id="0">video</col>
+ </row>
+ </data>
+ </object>
+ <object class="GeditDocument" id="source_buffer">
+ <property name="highlight-matching-brackets">True</property>
+ </object>
+ <object class="GtkDialog" id="dialog_snippets">
+ <property name="title" translatable="yes">Snippets Manager</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="default_width">750</property>
+ <property name="default_height">500</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+ <signal handler="on_dialog_snippets_response" last_modification_time="Mon, 19 Dec 2005 11:20:00 GMT" name="response"/>
+ <signal handler="on_dialog_snippets_destroy" last_modification_time="Sun, 22 Jun 2008 13:22:00 GMT" name="destroy"/>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="closebutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHPaned" id="hpaned_paned">
+ <property name="border_width">6</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">275</property>
+ <child>
+ <object class="GtkVBox" id="vbox_selection">
+ <property name="width_request">230</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Snippets:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">tree_view_snippets</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window_snippets">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="tree_view_snippets">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ <signal handler="on_tree_view_snippets_row_expanded" last_modification_time="Tue, 03 Jan 2006 22:06:02 GMT" name="row_expanded"/>
+ <signal handler="on_tree_view_snippets_key_press" last_modification_time="Tue, 03 Jan 2006 22:07:00 GMT" name="key_press_event"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox_buttons">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="button_new_snippet">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Create new snippet</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_button_new_snippet_clicked" last_modification_time="Tue, 20 Dec 2005 19:50:58 GMT" name="clicked"/>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">gtk-new</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_import_snippets">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Import snippets</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_button_import_snippets_clicked" last_modification_time="Tue, 10 Jul 2007 18:37:11 GMT" name="clicked"/>
+ <child>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="stock">gtk-open</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_export_snippets">
+ <property name="visible">True</property>
+ <property name="tooltip-text" translatable="yes">Export selected snippets</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_button_export_snippets_clicked" last_modification_time="Tue, 10 Jul 2007 18:37:25 GMT" name="clicked"/>
+ <child>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="stock">gtk-save</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_remove_snippet">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="tooltip-text" translatable="yes">Delete selected snippet</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal handler="on_button_remove_snippet_clicked" last_modification_time="Mon, 19 Dec 2005 13:15:14 GMT" name="clicked"/>
+ <child>
+ <object class="GtkImage" id="image_remove">
+ <property name="visible">True</property>
+ <property name="stock">gtk-delete</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="shrink">False</property>
+ <property name="resize">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox_snippet">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Edit:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window_snippet">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GeditView" id="source_view_snippet">
+ <property name="buffer">source_buffer</property>
+ <property name="visible">True</property>
+ <property name="auto-indent">True</property>
+ <property name="insert-spaces-instead-of-tabs">False</property>
+ <property name="smart-home-end">GTK_SOURCE_SMART_HOME_END_AFTER</property>
+ <property name="tab-width">2</property>
+ <property name="highlight-current-line">True</property>
+ <property name="show-right-margin">False</property>
+ <property name="show-line-numbers">False</property>
+
+ <signal handler="on_source_view_snippet_focus_out" last_modification_time="Sat, 07 Jan 2006 17:13:24 GMT" name="focus_out_event"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Activation</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ <attributes>
+ <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label_tab_trigger">
+ <property name="visible">True</property>
+ <property comments="&quot;tab&quot; here means the tab key, not the notebook tab!" name="label" translatable="yes">_Tab trigger:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_tab_trigger</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox_tab_trigger">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkEntry" id="entry_tab_trigger">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="tooltip-text" translatable="yes">Single word the snippet is activated with after pressing Tab</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <signal handler="on_entry_tab_trigger_focus_out" last_modification_time="Wed, 04 Jan 2006 14:07:29 GMT" name="focus_out_event"/>
+ <signal handler="on_entry_tab_trigger_changed" last_modification_time="Fri, 28 Apr 2006 16:50:34 GMT" name="changed"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image_tab_trigger">
+ <property name="visible">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">1</property>
+ <property name="padding">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_accelerator">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="tooltip-text" translatable="yes">Shortcut key with which the snippet is activated</property>
+ <property name="can_focus">True</property>
+ <property name="editable">False</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <signal handler="on_entry_accelerator_focus_out" last_modification_time="Wed, 04 Jan 2006 14:07:20 GMT" name="focus_out_event"/>
+ <signal handler="on_entry_accelerator_key_press" last_modification_time="Wed, 04 Jan 2006 14:07:23 GMT" name="key_press_event"/>
+ <signal handler="on_entry_accelerator_focus_in" last_modification_time="Wed, 04 Jan 2006 14:09:06 GMT" name="focus_in_event"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_accelerator">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">S_hortcut key:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_accelerator</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_drop_targets">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Drop targets:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">entry_accelerator</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxEntry" id="combo_drop_targets">
+ <property name="visible">True</property>
+ <property name="add_tearoffs">False</property>
+ <property name="has_frame">True</property>
+ <property name="focus_on_click">True</property>
+ <property name="model">model1</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="shrink">True</property>
+ <property name="resize">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-7">closebutton1</action-widget>
+ <action-widget response="-11">button1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/sort/Makefile.am b/plugins/sort/Makefile.am
new file mode 100755
index 00000000..8fa39143
--- /dev/null
+++ b/plugins/sort/Makefile.am
@@ -0,0 +1,34 @@
+# sort plugin
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+plugin_LTLIBRARIES = libsort.la
+
+libsort_la_SOURCES = \
+ gedit-sort-plugin.h \
+ gedit-sort-plugin.c
+
+libsort_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libsort_la_LIBADD = $(GEDIT_LIBS)
+
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/sort
+ui_DATA = sort.ui
+
+plugin_in_files = sort.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(ui_DATA) $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/sort/gedit-sort-plugin.c b/plugins/sort/gedit-sort-plugin.c
new file mode 100755
index 00000000..8fc6c959
--- /dev/null
+++ b/plugins/sort/gedit-sort-plugin.c
@@ -0,0 +1,588 @@
+/*
+ * gedit-sort-plugin.c
+ *
+ * Original author: Carlo Borreo <[email protected]>
+ * Ported to Gedit2 by Lee Mallabone <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gedit-sort-plugin.h"
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-help.h>
+
+#define GEDIT_SORT_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_SORT_PLUGIN, GeditSortPluginPrivate))
+
+/* Key in case the plugin ever needs any settings. */
+#define SORT_BASE_KEY "/apps/gedit-2/plugins/sort"
+
+#define WINDOW_DATA_KEY "GeditSortPluginWindowData"
+#define MENU_PATH "/MenuBar/EditMenu/EditOps_6"
+
+GEDIT_PLUGIN_REGISTER_TYPE(GeditSortPlugin, gedit_sort_plugin)
+
+typedef struct
+{
+ GtkWidget *dialog;
+ GtkWidget *col_num_spinbutton;
+ GtkWidget *reverse_order_checkbutton;
+ GtkWidget *ignore_case_checkbutton;
+ GtkWidget *remove_dups_checkbutton;
+
+ GeditDocument *doc;
+
+ GtkTextIter start, end; /* selection */
+} SortDialog;
+
+typedef struct
+{
+ GtkActionGroup *ui_action_group;
+ guint ui_id;
+} WindowData;
+
+typedef struct
+{
+ GeditPlugin *plugin;
+ GeditWindow *window;
+} ActionData;
+
+typedef struct
+{
+ gboolean ignore_case;
+ gboolean reverse_order;
+ gboolean remove_duplicates;
+ gint starting_column;
+} SortInfo;
+
+static void sort_cb (GtkAction *action, ActionData *action_data);
+static void sort_real (SortDialog *dialog);
+
+static const GtkActionEntry action_entries[] =
+{
+ { "Sort",
+ GTK_STOCK_SORT_ASCENDING,
+ N_("S_ort..."),
+ NULL,
+ N_("Sort the current document or selection"),
+ G_CALLBACK (sort_cb) }
+};
+
+static void
+sort_dialog_destroy (GtkObject *obj,
+ gpointer dialog_pointer)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_slice_free (SortDialog, dialog_pointer);
+}
+
+static void
+sort_dialog_response_handler (GtkDialog *widget,
+ gint res_id,
+ SortDialog *dialog)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ switch (res_id)
+ {
+ case GTK_RESPONSE_OK:
+ sort_real (dialog);
+ gtk_widget_destroy (dialog->dialog);
+ break;
+
+ case GTK_RESPONSE_HELP:
+ gedit_help_display (GTK_WINDOW (widget),
+ NULL,
+ "gedit-sort-plugin");
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ gtk_widget_destroy (dialog->dialog);
+ break;
+ }
+}
+
+/* NOTE: we store the current selection in the dialog since focusing
+ * the text field (like the combo box) looses the documnent selection.
+ * Storing the selection ONLY works because the dialog is modal */
+static void
+get_current_selection (GeditWindow *window, SortDialog *dialog)
+{
+ GeditDocument *doc;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ doc = gedit_window_get_active_document (window);
+
+ if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &dialog->start,
+ &dialog->end))
+ {
+ /* No selection, get the whole document. */
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc),
+ &dialog->start,
+ &dialog->end);
+ }
+}
+
+static SortDialog *
+get_sort_dialog (ActionData *action_data)
+{
+ SortDialog *dialog;
+ GtkWidget *error_widget;
+ gboolean ret;
+ gchar *data_dir;
+ gchar *ui_file;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ dialog = g_slice_new (SortDialog);
+
+ data_dir = gedit_plugin_get_data_dir (action_data->plugin);
+ ui_file = g_build_filename (data_dir, "sort.ui", NULL);
+ g_free (data_dir);
+ ret = gedit_utils_get_ui_objects (ui_file,
+ NULL,
+ &error_widget,
+ "sort_dialog", &dialog->dialog,
+ "reverse_order_checkbutton", &dialog->reverse_order_checkbutton,
+ "col_num_spinbutton", &dialog->col_num_spinbutton,
+ "ignore_case_checkbutton", &dialog->ignore_case_checkbutton,
+ "remove_dups_checkbutton", &dialog->remove_dups_checkbutton,
+ NULL);
+ g_free (ui_file);
+
+ if (!ret)
+ {
+ const gchar *err_message;
+
+ err_message = gtk_label_get_label (GTK_LABEL (error_widget));
+ gedit_warning (GTK_WINDOW (action_data->window),
+ "%s", err_message);
+
+ g_free (dialog);
+ gtk_widget_destroy (error_widget);
+
+ return NULL;
+ }
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog),
+ GTK_RESPONSE_OK);
+
+ g_signal_connect (dialog->dialog,
+ "destroy",
+ G_CALLBACK (sort_dialog_destroy),
+ dialog);
+
+ g_signal_connect (dialog->dialog,
+ "response",
+ G_CALLBACK (sort_dialog_response_handler),
+ dialog);
+
+ get_current_selection (action_data->window, dialog);
+
+ return dialog;
+}
+
+static void
+sort_cb (GtkAction *action,
+ ActionData *action_data)
+{
+ GeditDocument *doc;
+ GtkWindowGroup *wg;
+ SortDialog *dialog;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ doc = gedit_window_get_active_document (action_data->window);
+ g_return_if_fail (doc != NULL);
+
+ dialog = get_sort_dialog (action_data);
+ g_return_if_fail (dialog != NULL);
+
+ wg = gedit_window_get_group (action_data->window);
+ gtk_window_group_add_window (wg,
+ GTK_WINDOW (dialog->dialog));
+
+ dialog->doc = doc;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog),
+ GTK_WINDOW (action_data->window));
+
+ gtk_window_set_modal (GTK_WINDOW (dialog->dialog),
+ TRUE);
+
+ gtk_widget_show (GTK_WIDGET (dialog->dialog));
+}
+
+/* Compares two strings for the sorting algorithm. Uses the UTF-8 processing
+ * functions in GLib to be as correct as possible.*/
+static gint
+compare_algorithm (gconstpointer s1,
+ gconstpointer s2,
+ gpointer data)
+{
+ gint length1, length2;
+ gint ret;
+ gchar *string1, *string2;
+ gchar *substring1, *substring2;
+ gchar *key1, *key2;
+ SortInfo *sort_info;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ sort_info = (SortInfo *) data;
+ g_return_val_if_fail (sort_info != NULL, -1);
+
+ if (!sort_info->ignore_case)
+ {
+ string1 = *((gchar **) s1);
+ string2 = *((gchar **) s2);
+ }
+ else
+ {
+ string1 = g_utf8_casefold (*((gchar **) s1), -1);
+ string2 = g_utf8_casefold (*((gchar **) s2), -1);
+ }
+
+ length1 = g_utf8_strlen (string1, -1);
+ length2 = g_utf8_strlen (string2, -1);
+
+ if ((length1 < sort_info->starting_column) &&
+ (length2 < sort_info->starting_column))
+ {
+ ret = 0;
+ }
+ else if (length1 < sort_info->starting_column)
+ {
+ ret = -1;
+ }
+ else if (length2 < sort_info->starting_column)
+ {
+ ret = 1;
+ }
+ else if (sort_info->starting_column < 1)
+ {
+ key1 = g_utf8_collate_key (string1, -1);
+ key2 = g_utf8_collate_key (string2, -1);
+ ret = strcmp (key1, key2);
+
+ g_free (key1);
+ g_free (key2);
+ }
+ else
+ {
+ /* A character column offset is required, so figure out
+ * the correct offset into the UTF-8 string. */
+ substring1 = g_utf8_offset_to_pointer (string1, sort_info->starting_column);
+ substring2 = g_utf8_offset_to_pointer (string2, sort_info->starting_column);
+
+ key1 = g_utf8_collate_key (substring1, -1);
+ key2 = g_utf8_collate_key (substring2, -1);
+ ret = strcmp (key1, key2);
+
+ g_free (key1);
+ g_free (key2);
+ }
+
+ /* Do the necessary cleanup. */
+ if (sort_info->ignore_case)
+ {
+ g_free (string1);
+ g_free (string2);
+ }
+
+ if (sort_info->reverse_order)
+ {
+ ret = -1 * ret;
+ }
+
+ return ret;
+}
+
+static gchar *
+get_line_slice (GtkTextBuffer *buf,
+ gint line)
+{
+ GtkTextIter start, end;
+ char *ret;
+
+ gtk_text_buffer_get_iter_at_line (buf, &start, line);
+ end = start;
+
+ if (!gtk_text_iter_ends_line (&start))
+ gtk_text_iter_forward_to_line_end (&end);
+
+ ret= gtk_text_buffer_get_slice (buf,
+ &start,
+ &end,
+ TRUE);
+
+ g_assert (ret != NULL);
+
+ return ret;
+}
+
+static void
+sort_real (SortDialog *dialog)
+{
+ GeditDocument *doc;
+ GtkTextIter start, end;
+ gint start_line, end_line;
+ gint i;
+ gchar *last_row = NULL;
+ gint num_lines;
+ gchar **lines;
+ SortInfo *sort_info;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ doc = dialog->doc;
+ g_return_if_fail (doc != NULL);
+
+ sort_info = g_new0 (SortInfo, 1);
+ sort_info->ignore_case = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->ignore_case_checkbutton));
+ sort_info->reverse_order = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->reverse_order_checkbutton));
+ sort_info->remove_duplicates = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->remove_dups_checkbutton));
+ sort_info->starting_column = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (dialog->col_num_spinbutton)) - 1;
+
+ start = dialog->start;
+ end = dialog->end;
+ start_line = gtk_text_iter_get_line (&start);
+ end_line = gtk_text_iter_get_line (&end);
+
+ /* if we are at line start our last line is the previus one.
+ * Otherwise the last line is the current one but we try to
+ * move the iter after the line terminator */
+ if (gtk_text_iter_get_line_offset (&end) == 0)
+ end_line = MAX (start_line, end_line - 1);
+ else
+ gtk_text_iter_forward_line (&end);
+
+ num_lines = end_line - start_line + 1;
+ lines = g_new0 (gchar *, num_lines + 1);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Building list...");
+
+ for (i = 0; i < num_lines; i++)
+ {
+ lines[i] = get_line_slice (GTK_TEXT_BUFFER (doc), start_line + i);
+ }
+
+ lines[num_lines] = NULL;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Sort list...");
+
+ g_qsort_with_data (lines,
+ num_lines,
+ sizeof (gpointer),
+ compare_algorithm,
+ sort_info);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Rebuilding document...");
+
+ gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (doc));
+
+ gtk_text_buffer_delete (GTK_TEXT_BUFFER (doc),
+ &start,
+ &end);
+
+ for (i = 0; i < num_lines; i++)
+ {
+ if (sort_info->remove_duplicates &&
+ last_row != NULL &&
+ (strcmp (last_row, lines[i]) == 0))
+ continue;
+
+ gtk_text_buffer_insert (GTK_TEXT_BUFFER (doc),
+ &start,
+ lines[i],
+ -1);
+ gtk_text_buffer_insert (GTK_TEXT_BUFFER (doc),
+ &start,
+ "\n",
+ -1);
+
+ last_row = lines[i];
+ }
+
+ gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (doc));
+
+ g_strfreev (lines);
+ g_free (sort_info);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Done.");
+}
+
+static void
+free_window_data (WindowData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ g_object_unref (data->ui_action_group);
+ g_slice_free (WindowData, data);
+}
+
+static void
+free_action_data (ActionData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ g_slice_free (ActionData, data);
+}
+
+static void
+update_ui_real (GeditWindow *window,
+ WindowData *data)
+{
+ GeditView *view;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ view = gedit_window_get_active_view (window);
+
+ gtk_action_group_set_sensitive (data->ui_action_group,
+ (view != NULL) &&
+ gtk_text_view_get_editable (GTK_TEXT_VIEW (view)));
+}
+
+static void
+impl_activate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+ ActionData *action_data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = g_slice_new (WindowData);
+ action_data = g_slice_new (ActionData);
+ action_data->window = window;
+ action_data->plugin = plugin;
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data->ui_action_group = gtk_action_group_new ("GeditSortPluginActions");
+ gtk_action_group_set_translation_domain (data->ui_action_group,
+ GETTEXT_PACKAGE);
+ gtk_action_group_add_actions_full (data->ui_action_group,
+ action_entries,
+ G_N_ELEMENTS (action_entries),
+ action_data,
+ (GDestroyNotify) free_action_data);
+
+ gtk_ui_manager_insert_action_group (manager,
+ data->ui_action_group,
+ -1);
+
+ data->ui_id = gtk_ui_manager_new_merge_id (manager);
+
+ g_object_set_data_full (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ data,
+ (GDestroyNotify) free_window_data);
+
+ gtk_ui_manager_add_ui (manager,
+ data->ui_id,
+ MENU_PATH,
+ "Sort",
+ "Sort",
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ update_ui_real (window,
+ data);
+}
+
+static void
+impl_deactivate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ gtk_ui_manager_remove_ui (manager,
+ data->ui_id);
+ gtk_ui_manager_remove_action_group (manager,
+ data->ui_action_group);
+
+ g_object_set_data (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ NULL);
+}
+
+static void
+impl_update_ui (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ update_ui_real (window,
+ data);
+}
+
+static void
+gedit_sort_plugin_init (GeditSortPlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin initializing");
+}
+
+static void
+gedit_sort_plugin_finalize (GObject *object)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSortPlugin finalizing");
+
+ G_OBJECT_CLASS (gedit_sort_plugin_parent_class)->finalize (object);
+}
+
+static void
+gedit_sort_plugin_class_init (GeditSortPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ object_class->finalize = gedit_sort_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+ plugin_class->update_ui = impl_update_ui;
+}
diff --git a/plugins/sort/gedit-sort-plugin.h b/plugins/sort/gedit-sort-plugin.h
new file mode 100755
index 00000000..c10280bf
--- /dev/null
+++ b/plugins/sort/gedit-sort-plugin.h
@@ -0,0 +1,73 @@
+/*
+ * gedit-sort-plugin.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifndef __GEDIT_SORT_PLUGIN_H__
+#define __GEDIT_SORT_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_SORT_PLUGIN (gedit_sort_plugin_get_type ())
+#define GEDIT_SORT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_SORT_PLUGIN, GeditSortPlugin))
+#define GEDIT_SORT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_SORT_PLUGIN, GeditSortPluginClass))
+#define GEDIT_IS_SORT_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_SORT_PLUGIN))
+#define GEDIT_IS_SORT_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_SORT_PLUGIN))
+#define GEDIT_SORT_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_SORT_PLUGIN, GeditSortPluginClass))
+
+/* Private structure type */
+typedef struct _GeditSortPluginPrivate GeditSortPluginPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditSortPlugin GeditSortPlugin;
+
+struct _GeditSortPlugin
+{
+ GeditPlugin parent_instance;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditSortPluginClass GeditSortPluginClass;
+
+struct _GeditSortPluginClass
+{
+ GeditPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_sort_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_SORT_PLUGIN_H__ */
diff --git a/plugins/sort/sort.gedit-plugin.desktop.in b/plugins/sort/sort.gedit-plugin.desktop.in
new file mode 100755
index 00000000..9ee9634a
--- /dev/null
+++ b/plugins/sort/sort.gedit-plugin.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Plugin]
+Module=sort
+IAge=2
+_Name=Sort
+_Description=Sorts a document or selected text.
+Icon=gtk-sort-ascending
+Authors=Carlo Borreo <[email protected]>;Lee Mallabone <[email protected]>;Paolo Maggi <[email protected]>;Jorge Alberto Torres H. <[email protected]>
+Copyright=Copyright © 2001 Carlo Borreo\nCopyright © 2002-2003 Lee Mallabone, Paolo Maggi\nCopyright © 2004-2005 Paolo Maggi
+Website=http://www.gedit.org
diff --git a/plugins/sort/sort.ui b/plugins/sort/sort.ui
new file mode 100755
index 00000000..4d1fe091
--- /dev/null
+++ b/plugins/sort/sort.ui
@@ -0,0 +1,275 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="upper">100</property>
+ <property name="lower">1</property>
+ <property name="page_increment">10</property>
+ <property name="step_increment">1</property>
+ <property name="page_size">0</property>
+ <property name="value">1</property>
+ </object>
+ <object class="GtkImage" id="sort_image">
+ <property name="stock">gtk-sort-ascending</property>
+ <property name="icon_size">4</property>
+ </object>
+ <object class="GtkDialog" id="sort_dialog">
+ <property name="title" translatable="yes">Sort</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog_action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="image">sort_image</property>
+ <property name="label" translatable="yes">_Sort</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkCheckButton" id="reverse_order_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Reverse order</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="remove_dups_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">R_emove duplicates</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="ignore_case_checkbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Ignore case</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">True</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox13">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">S_tart at column:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">col_num_spinbutton</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="col_num_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">0</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">GTK_UPDATE_ALWAYS</property>
+ <property name="snap_to_ticks">False</property>
+ <property name="wrap">False</property>
+ <property name="adjustment">adjustment1</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox14">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage" id="image7">
+ <property name="visible">True</property>
+ <property name="stock">gtk-dialog-warning</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">You cannot undo a sort operation</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button1</action-widget>
+ <action-widget response="-5">button2</action-widget>
+ <action-widget response="-11">button3</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/spell/Makefile.am b/plugins/spell/Makefile.am
new file mode 100755
index 00000000..9d332f95
--- /dev/null
+++ b/plugins/spell/Makefile.am
@@ -0,0 +1,63 @@
+# Spell checker plugin
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(ENCHANT_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+BUILT_SOURCES = \
+ gedit-spell-marshal.c \
+ gedit-spell-marshal.h
+
+plugin_LTLIBRARIES = libspell.la
+
+libspell_la_SOURCES = \
+ gedit-spell-plugin.c \
+ gedit-spell-plugin.h \
+ gedit-spell-checker.c \
+ gedit-spell-checker.h \
+ gedit-spell-checker-dialog.c \
+ gedit-spell-checker-dialog.h \
+ gedit-spell-checker-language.c \
+ gedit-spell-checker-language.h \
+ gedit-spell-language-dialog.c \
+ gedit-spell-language-dialog.h \
+ gedit-automatic-spell-checker.c \
+ gedit-automatic-spell-checker.h \
+ gedit-spell-utils.c \
+ gedit-spell-utils.h \
+ $(BUILT_SOURCES)
+
+libspell_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libspell_la_LIBADD = $(GEDIT_LIBS) $(ENCHANT_LIBS)
+
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/spell
+ui_DATA = spell-checker.ui languages-dialog.ui
+
+gedit-spell-marshal.h: gedit-spell-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN) $(GLIB_GENMARSHAL) $< --header --prefix=gedit_marshal > $@
+
+gedit-spell-marshal.c: gedit-spell-marshal.list $(GLIB_GENMARSHAL)
+ $(AM_V_GEN) echo "#include \"gedit-spell-marshal.h\"" > $@ && \
+ $(GLIB_GENMARSHAL) $< --body --prefix=gedit_marshal >> $@
+
+plugin_in_files = spell.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = \
+ $(ui_DATA) \
+ $(plugin_in_files) \
+ gedit-spell-marshal.list
+
+CLEANFILES = $(BUILT_SOURCES) $(plugin_DATA)
+
+dist-hook:
+ cd $(distdir); rm -f $(BUILT_SOURCES)
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/spell/gedit-automatic-spell-checker.c b/plugins/spell/gedit-automatic-spell-checker.c
new file mode 100755
index 00000000..96b2fae9
--- /dev/null
+++ b/plugins/spell/gedit-automatic-spell-checker.c
@@ -0,0 +1,1015 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-automatic-spell-checker.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+/* This is a modified version of gtkspell 2.0.5 (gtkspell.sf.net) */
+/* gtkspell - a spell-checking addon for GTK's TextView widget
+ * Copyright (c) 2002 Evan Martin.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "gedit-automatic-spell-checker.h"
+#include "gedit-spell-utils.h"
+
+struct _GeditAutomaticSpellChecker {
+ GeditDocument *doc;
+ GSList *views;
+
+ GtkTextMark *mark_insert_start;
+ GtkTextMark *mark_insert_end;
+ gboolean deferred_check;
+
+ GtkTextTag *tag_highlight;
+ GtkTextMark *mark_click;
+
+ GeditSpellChecker *spell_checker;
+};
+
+static GQuark automatic_spell_checker_id = 0;
+static GQuark suggestion_id = 0;
+
+static void gedit_automatic_spell_checker_free_internal (GeditAutomaticSpellChecker *spell);
+
+static void
+view_destroy (GeditView *view, GeditAutomaticSpellChecker *spell)
+{
+ gedit_automatic_spell_checker_detach_view (spell, view);
+}
+
+static void
+check_word (GeditAutomaticSpellChecker *spell, GtkTextIter *start, GtkTextIter *end)
+{
+ gchar *word;
+
+ word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), start, end, FALSE);
+
+ /*
+ g_print ("Check word: %s [%d - %d]\n", word, gtk_text_iter_get_offset (start),
+ gtk_text_iter_get_offset (end));
+ */
+
+ if (!gedit_spell_checker_check_word (spell->spell_checker, word, -1))
+ {
+ /*
+ g_print ("Apply tag: [%d - %d]\n", gtk_text_iter_get_offset (start),
+ gtk_text_iter_get_offset (end));
+ */
+ gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (spell->doc),
+ spell->tag_highlight,
+ start,
+ end);
+ }
+
+ g_free (word);
+}
+
+static void
+check_range (GeditAutomaticSpellChecker *spell,
+ GtkTextIter start,
+ GtkTextIter end,
+ gboolean force_all)
+{
+ /* we need to "split" on word boundaries.
+ * luckily, Pango knows what "words" are
+ * so we don't have to figure it out. */
+
+ GtkTextIter wstart;
+ GtkTextIter wend;
+ GtkTextIter cursor;
+ GtkTextIter precursor;
+ gboolean highlight;
+
+ /*
+ g_print ("Check range: [%d - %d]\n", gtk_text_iter_get_offset (&start),
+ gtk_text_iter_get_offset (&end));
+ */
+
+ if (gtk_text_iter_inside_word (&end))
+ gtk_text_iter_forward_word_end (&end);
+
+ if (!gtk_text_iter_starts_word (&start))
+ {
+ if (gtk_text_iter_inside_word (&start) ||
+ gtk_text_iter_ends_word (&start))
+ {
+ gtk_text_iter_backward_word_start (&start);
+ }
+ else
+ {
+ /* if we're neither at the beginning nor inside a word,
+ * me must be in some spaces.
+ * skip forward to the beginning of the next word. */
+
+ if (gtk_text_iter_forward_word_end (&start))
+ gtk_text_iter_backward_word_start (&start);
+ }
+ }
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc),
+ &cursor,
+ gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (spell->doc)));
+
+ precursor = cursor;
+ gtk_text_iter_backward_char (&precursor);
+
+ highlight = gtk_text_iter_has_tag (&cursor, spell->tag_highlight) ||
+ gtk_text_iter_has_tag (&precursor, spell->tag_highlight);
+
+ gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (spell->doc),
+ spell->tag_highlight,
+ &start,
+ &end);
+
+ /* Fix a corner case when replacement occurs at beginning of buffer:
+ * An iter at offset 0 seems to always be inside a word,
+ * even if it's not. Possibly a pango bug.
+ */
+ if (gtk_text_iter_get_offset (&start) == 0)
+ {
+ gtk_text_iter_forward_word_end(&start);
+ gtk_text_iter_backward_word_start(&start);
+ }
+
+ wstart = start;
+
+ while (gedit_spell_utils_skip_no_spell_check (&wstart, &end) &&
+ gtk_text_iter_compare (&wstart, &end) < 0)
+ {
+ gboolean inword;
+
+ /* move wend to the end of the current word. */
+ wend = wstart;
+
+ gtk_text_iter_forward_word_end (&wend);
+
+ inword = (gtk_text_iter_compare (&wstart, &cursor) < 0) &&
+ (gtk_text_iter_compare (&cursor, &wend) <= 0);
+
+ if (inword && !force_all)
+ {
+ /* this word is being actively edited,
+ * only check if it's already highligted,
+ * otherwise defer this check until later. */
+ if (highlight)
+ check_word (spell, &wstart, &wend);
+ else
+ spell->deferred_check = TRUE;
+ }
+ else
+ {
+ check_word (spell, &wstart, &wend);
+ spell->deferred_check = FALSE;
+ }
+
+ /* now move wend to the beginning of the next word, */
+ gtk_text_iter_forward_word_end (&wend);
+ gtk_text_iter_backward_word_start (&wend);
+
+ /* make sure we've actually advanced
+ * (we don't advance in some corner cases), */
+ if (gtk_text_iter_equal (&wstart, &wend))
+ break; /* we're done in these cases.. */
+
+ /* and then pick this as the new next word beginning. */
+ wstart = wend;
+ }
+}
+
+static void
+check_deferred_range (GeditAutomaticSpellChecker *spell,
+ gboolean force_all)
+{
+ GtkTextIter start, end;
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc),
+ &start,
+ spell->mark_insert_start);
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (spell->doc),
+ &end,
+ spell->mark_insert_end);
+
+ check_range (spell, start, end, force_all);
+}
+
+/* insertion works like this:
+ * - before the text is inserted, we mark the position in the buffer.
+ * - after the text is inserted, we see where our mark is and use that and
+ * the current position to check the entire range of inserted text.
+ *
+ * this may be overkill for the common case (inserting one character). */
+
+static void
+insert_text_before (GtkTextBuffer *buffer, GtkTextIter *iter,
+ gchar *text, gint len, GeditAutomaticSpellChecker *spell)
+{
+ gtk_text_buffer_move_mark (buffer, spell->mark_insert_start, iter);
+}
+
+static void
+insert_text_after (GtkTextBuffer *buffer, GtkTextIter *iter,
+ gchar *text, gint len, GeditAutomaticSpellChecker *spell)
+{
+ GtkTextIter start;
+
+ /* we need to check a range of text. */
+ gtk_text_buffer_get_iter_at_mark (buffer, &start, spell->mark_insert_start);
+
+ check_range (spell, start, *iter, FALSE);
+
+ gtk_text_buffer_move_mark (buffer, spell->mark_insert_end, iter);
+}
+
+/* deleting is more simple: we're given the range of deleted text.
+ * after deletion, the start and end iters should be at the same position
+ * (because all of the text between them was deleted!).
+ * this means we only really check the words immediately bounding the
+ * deletion.
+ */
+
+static void
+delete_range_after (GtkTextBuffer *buffer, GtkTextIter *start, GtkTextIter *end,
+ GeditAutomaticSpellChecker *spell)
+{
+ check_range (spell, *start, *end, FALSE);
+}
+
+static void
+mark_set (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ GtkTextMark *mark,
+ GeditAutomaticSpellChecker *spell)
+{
+ /* if the cursor has moved and there is a deferred check so handle it now */
+ if ((mark == gtk_text_buffer_get_insert (buffer)) && spell->deferred_check)
+ check_deferred_range (spell, FALSE);
+}
+
+static void
+get_word_extents_from_mark (GtkTextBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ GtkTextMark *mark)
+{
+ gtk_text_buffer_get_iter_at_mark(buffer, start, mark);
+
+ if (!gtk_text_iter_starts_word (start))
+ gtk_text_iter_backward_word_start (start);
+
+ *end = *start;
+
+ if (gtk_text_iter_inside_word (end))
+ gtk_text_iter_forward_word_end (end);
+}
+
+static void
+remove_tag_to_word (GeditAutomaticSpellChecker *spell, const gchar *word)
+{
+ GtkTextIter iter;
+ GtkTextIter match_start, match_end;
+
+ gboolean found;
+
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (spell->doc), &iter, 0);
+
+ found = TRUE;
+
+ while (found)
+ {
+ found = gtk_text_iter_forward_search (&iter,
+ word,
+ GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY,
+ &match_start,
+ &match_end,
+ NULL);
+
+ if (found)
+ {
+ if (gtk_text_iter_starts_word (&match_start) &&
+ gtk_text_iter_ends_word (&match_end))
+ {
+ gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (spell->doc),
+ spell->tag_highlight,
+ &match_start,
+ &match_end);
+ }
+
+ iter = match_end;
+ }
+ }
+}
+
+static void
+add_to_dictionary (GtkWidget *menuitem, GeditAutomaticSpellChecker *spell)
+{
+ gchar *word;
+
+ GtkTextIter start, end;
+
+ get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click);
+
+ word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc),
+ &start,
+ &end,
+ FALSE);
+
+ gedit_spell_checker_add_word_to_personal (spell->spell_checker, word, -1);
+
+ g_free (word);
+}
+
+static void
+ignore_all (GtkWidget *menuitem, GeditAutomaticSpellChecker *spell)
+{
+ gchar *word;
+
+ GtkTextIter start, end;
+
+ get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click);
+
+ word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc),
+ &start,
+ &end,
+ FALSE);
+
+ gedit_spell_checker_add_word_to_session (spell->spell_checker, word, -1);
+
+ g_free (word);
+}
+
+static void
+replace_word (GtkWidget *menuitem, GeditAutomaticSpellChecker *spell)
+{
+ gchar *oldword;
+ const gchar *newword;
+
+ GtkTextIter start, end;
+
+ get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click);
+
+ oldword = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), &start, &end, FALSE);
+
+ newword = g_object_get_qdata (G_OBJECT (menuitem), suggestion_id);
+ g_return_if_fail (newword != NULL);
+
+ gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (spell->doc));
+
+ gtk_text_buffer_delete (GTK_TEXT_BUFFER (spell->doc), &start, &end);
+ gtk_text_buffer_insert (GTK_TEXT_BUFFER (spell->doc), &start, newword, -1);
+
+ gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (spell->doc));
+
+ gedit_spell_checker_set_correction (spell->spell_checker,
+ oldword, strlen (oldword),
+ newword, strlen (newword));
+
+ g_free (oldword);
+}
+
+static GtkWidget *
+build_suggestion_menu (GeditAutomaticSpellChecker *spell, const gchar *word)
+{
+ GtkWidget *topmenu, *menu;
+ GtkWidget *mi;
+ GSList *suggestions;
+ GSList *list;
+ gchar *label_text;
+
+ topmenu = menu = gtk_menu_new();
+
+ suggestions = gedit_spell_checker_get_suggestions (spell->spell_checker, word, -1);
+
+ list = suggestions;
+
+ if (suggestions == NULL)
+ {
+ /* no suggestions. put something in the menu anyway... */
+ GtkWidget *label;
+ /* Translators: Displayed in the "Check Spelling" dialog if there are no suggestions for the current misspelled word */
+ label = gtk_label_new (_("(no suggested words)"));
+
+ mi = gtk_menu_item_new ();
+ gtk_widget_set_sensitive (mi, FALSE);
+ gtk_container_add (GTK_CONTAINER(mi), label);
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+ }
+ else
+ {
+ gint count = 0;
+
+ /* build a set of menus with suggestions. */
+ while (suggestions != NULL)
+ {
+ GtkWidget *label;
+
+ if (count == 10)
+ {
+ /* Separator */
+ mi = gtk_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+
+ mi = gtk_menu_item_new_with_mnemonic (_("_More..."));
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+
+ menu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
+ count = 0;
+ }
+
+ label_text = g_strdup_printf ("<b>%s</b>", (gchar*) suggestions->data);
+
+ label = gtk_label_new (label_text);
+ gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+ mi = gtk_menu_item_new ();
+ gtk_container_add (GTK_CONTAINER(mi), label);
+
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
+
+ g_object_set_qdata_full (G_OBJECT (mi),
+ suggestion_id,
+ g_strdup (suggestions->data),
+ (GDestroyNotify)g_free);
+
+ g_free (label_text);
+ g_signal_connect (mi,
+ "activate",
+ G_CALLBACK (replace_word),
+ spell);
+
+ count++;
+
+ suggestions = g_slist_next (suggestions);
+ }
+ }
+
+ /* free the suggestion list */
+ suggestions = list;
+
+ while (list)
+ {
+ g_free (list->data);
+ list = g_slist_next (list);
+ }
+
+ g_slist_free (suggestions);
+
+ /* Separator */
+ mi = gtk_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ /* Ignore all */
+ mi = gtk_image_menu_item_new_with_mnemonic (_("_Ignore All"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi),
+ gtk_image_new_from_stock (GTK_STOCK_GOTO_BOTTOM,
+ GTK_ICON_SIZE_MENU));
+
+ g_signal_connect (mi,
+ "activate",
+ G_CALLBACK(ignore_all),
+ spell);
+
+ gtk_widget_show_all (mi);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ /* + Add to Dictionary */
+ mi = gtk_image_menu_item_new_with_mnemonic (_("_Add"));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi),
+ gtk_image_new_from_stock (GTK_STOCK_ADD,
+ GTK_ICON_SIZE_MENU));
+
+ g_signal_connect (mi,
+ "activate",
+ G_CALLBACK (add_to_dictionary),
+ spell);
+
+ gtk_widget_show_all (mi);
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi);
+
+ return topmenu;
+}
+
+static void
+populate_popup (GtkTextView *textview, GtkMenu *menu, GeditAutomaticSpellChecker *spell)
+{
+ GtkWidget *img, *mi;
+ GtkTextIter start, end;
+ char *word;
+
+ /* we need to figure out if they picked a misspelled word. */
+ get_word_extents_from_mark (GTK_TEXT_BUFFER (spell->doc), &start, &end, spell->mark_click);
+
+ /* if our highlight algorithm ever messes up,
+ * this isn't correct, either. */
+ if (!gtk_text_iter_has_tag (&start, spell->tag_highlight))
+ return; /* word wasn't misspelled. */
+
+ /* menu separator comes first. */
+ mi = gtk_menu_item_new ();
+ gtk_widget_show (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+
+ /* then, on top of it, the suggestions menu. */
+ img = gtk_image_new_from_stock (GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU);
+ mi = gtk_image_menu_item_new_with_mnemonic (_("_Spelling Suggestions..."));
+ gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), img);
+
+ word = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (spell->doc), &start, &end, FALSE);
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi),
+ build_suggestion_menu (spell, word));
+ g_free(word);
+
+ gtk_widget_show_all (mi);
+ gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi);
+}
+
+void
+gedit_automatic_spell_checker_recheck_all (GeditAutomaticSpellChecker *spell)
+{
+ GtkTextIter start, end;
+
+ g_return_if_fail (spell != NULL);
+
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (spell->doc), &start, &end);
+
+ check_range (spell, start, end, TRUE);
+}
+
+static void
+add_word_signal_cb (GeditSpellChecker *checker,
+ const gchar *word,
+ gint len,
+ GeditAutomaticSpellChecker *spell)
+{
+ gchar *w;
+
+ if (len < 0)
+ w = g_strdup (word);
+ else
+ w = g_strndup (word, len);
+
+ remove_tag_to_word (spell, w);
+
+ g_free (w);
+}
+
+static void
+set_language_cb (GeditSpellChecker *checker,
+ const GeditSpellCheckerLanguage *lang,
+ GeditAutomaticSpellChecker *spell)
+{
+ gedit_automatic_spell_checker_recheck_all (spell);
+}
+
+static void
+clear_session_cb (GeditSpellChecker *checker,
+ GeditAutomaticSpellChecker *spell)
+{
+ gedit_automatic_spell_checker_recheck_all (spell);
+}
+
+/* When the user right-clicks on a word, they want to check that word.
+ * Here, we do NOT move the cursor to the location of the clicked-upon word
+ * since that prevents the use of edit functions on the context menu.
+ */
+static gboolean
+button_press_event (GtkTextView *view,
+ GdkEventButton *event,
+ GeditAutomaticSpellChecker *spell)
+{
+ if (event->button == 3)
+ {
+ gint x, y;
+ GtkTextIter iter;
+
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (view);
+
+ /* handle deferred check if it exists */
+ if (spell->deferred_check)
+ check_deferred_range (spell, TRUE);
+
+ gtk_text_view_window_to_buffer_coords (view,
+ GTK_TEXT_WINDOW_TEXT,
+ event->x, event->y,
+ &x, &y);
+
+ gtk_text_view_get_iter_at_location (view, &iter, x, y);
+
+ gtk_text_buffer_move_mark (buffer, spell->mark_click, &iter);
+ }
+
+ return FALSE; /* false: let gtk process this event, too.
+ we don't want to eat any events. */
+}
+
+/* Move the insert mark before popping up the menu, otherwise it
+ * will contain the wrong set of suggestions.
+ */
+static gboolean
+popup_menu_event (GtkTextView *view, GeditAutomaticSpellChecker *spell)
+{
+ GtkTextIter iter;
+ GtkTextBuffer *buffer;
+
+ buffer = gtk_text_view_get_buffer (view);
+
+ /* handle deferred check if it exists */
+ if (spell->deferred_check)
+ check_deferred_range (spell, TRUE);
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+ gtk_text_buffer_get_insert (buffer));
+ gtk_text_buffer_move_mark (buffer, spell->mark_click, &iter);
+
+ return FALSE;
+}
+
+static void
+tag_table_changed (GtkTextTagTable *table,
+ GeditAutomaticSpellChecker *spell)
+{
+ g_return_if_fail (spell->tag_highlight != NULL);
+
+ gtk_text_tag_set_priority (spell->tag_highlight,
+ gtk_text_tag_table_get_size (table) - 1);
+}
+
+static void
+tag_added_or_removed (GtkTextTagTable *table,
+ GtkTextTag *tag,
+ GeditAutomaticSpellChecker *spell)
+{
+ tag_table_changed (table, spell);
+}
+
+static void
+tag_changed (GtkTextTagTable *table,
+ GtkTextTag *tag,
+ gboolean size_changed,
+ GeditAutomaticSpellChecker *spell)
+{
+ tag_table_changed (table, spell);
+}
+
+static void
+highlight_updated (GtkSourceBuffer *buffer,
+ GtkTextIter *start,
+ GtkTextIter *end,
+ GeditAutomaticSpellChecker *spell)
+{
+ check_range (spell, *start, *end, FALSE);
+}
+
+static void
+spell_tag_destroyed (GeditAutomaticSpellChecker *spell,
+ GObject *where_the_object_was)
+{
+ spell->tag_highlight = NULL;
+}
+
+GeditAutomaticSpellChecker *
+gedit_automatic_spell_checker_new (GeditDocument *doc,
+ GeditSpellChecker *checker)
+{
+ GeditAutomaticSpellChecker *spell;
+ GtkTextTagTable *tag_table;
+ GtkTextIter start, end;
+
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (checker), NULL);
+ g_return_val_if_fail ((spell = gedit_automatic_spell_checker_get_from_document (doc)) == NULL,
+ spell);
+
+ /* attach to the widget */
+ spell = g_new0 (GeditAutomaticSpellChecker, 1);
+
+ spell->doc = doc;
+ spell->spell_checker = g_object_ref (checker);
+
+ if (automatic_spell_checker_id == 0)
+ {
+ automatic_spell_checker_id =
+ g_quark_from_string ("GeditAutomaticSpellCheckerID");
+ }
+ if (suggestion_id == 0)
+ {
+ suggestion_id = g_quark_from_string ("GeditAutoSuggestionID");
+ }
+
+ g_object_set_qdata_full (G_OBJECT (doc),
+ automatic_spell_checker_id,
+ spell,
+ (GDestroyNotify)gedit_automatic_spell_checker_free_internal);
+
+ g_signal_connect (doc,
+ "insert-text",
+ G_CALLBACK (insert_text_before),
+ spell);
+ g_signal_connect_after (doc,
+ "insert-text",
+ G_CALLBACK (insert_text_after),
+ spell);
+ g_signal_connect_after (doc,
+ "delete-range",
+ G_CALLBACK (delete_range_after),
+ spell);
+ g_signal_connect (doc,
+ "mark-set",
+ G_CALLBACK (mark_set),
+ spell);
+
+ g_signal_connect (doc,
+ "highlight-updated",
+ G_CALLBACK (highlight_updated),
+ spell);
+
+ g_signal_connect (spell->spell_checker,
+ "add_word_to_session",
+ G_CALLBACK (add_word_signal_cb),
+ spell);
+ g_signal_connect (spell->spell_checker,
+ "add_word_to_personal",
+ G_CALLBACK (add_word_signal_cb),
+ spell);
+ g_signal_connect (spell->spell_checker,
+ "clear_session",
+ G_CALLBACK (clear_session_cb),
+ spell);
+ g_signal_connect (spell->spell_checker,
+ "set_language",
+ G_CALLBACK (set_language_cb),
+ spell);
+
+ spell->tag_highlight = gtk_text_buffer_create_tag (
+ GTK_TEXT_BUFFER (doc),
+ "gtkspell-misspelled",
+ "underline", PANGO_UNDERLINE_ERROR,
+ NULL);
+
+ g_object_weak_ref (G_OBJECT (spell->tag_highlight),
+ (GWeakNotify)spell_tag_destroyed,
+ spell);
+
+ tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (doc));
+
+ gtk_text_tag_set_priority (spell->tag_highlight,
+ gtk_text_tag_table_get_size (tag_table) - 1);
+
+ g_signal_connect (tag_table,
+ "tag-added",
+ G_CALLBACK (tag_added_or_removed),
+ spell);
+ g_signal_connect (tag_table,
+ "tag-removed",
+ G_CALLBACK (tag_added_or_removed),
+ spell);
+ g_signal_connect (tag_table,
+ "tag-changed",
+ G_CALLBACK (tag_changed),
+ spell);
+
+ /* we create the mark here, but we don't use it until text is
+ * inserted, so we don't really care where iter points. */
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc), &start, &end);
+
+ spell->mark_insert_start = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc),
+ "gedit-automatic-spell-checker-insert-start");
+
+ if (spell->mark_insert_start == NULL)
+ {
+ spell->mark_insert_start =
+ gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc),
+ "gedit-automatic-spell-checker-insert-start",
+ &start,
+ TRUE);
+ }
+ else
+ {
+ gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc),
+ spell->mark_insert_start,
+ &start);
+ }
+
+ spell->mark_insert_end = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc),
+ "gedit-automatic-spell-checker-insert-end");
+
+ if (spell->mark_insert_end == NULL)
+ {
+ spell->mark_insert_end =
+ gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc),
+ "gedit-automatic-spell-checker-insert-end",
+ &start,
+ TRUE);
+ }
+ else
+ {
+ gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc),
+ spell->mark_insert_end,
+ &start);
+ }
+
+ spell->mark_click = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (doc),
+ "gedit-automatic-spell-checker-click");
+
+ if (spell->mark_click == NULL)
+ {
+ spell->mark_click =
+ gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc),
+ "gedit-automatic-spell-checker-click",
+ &start,
+ TRUE);
+ }
+ else
+ {
+ gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc),
+ spell->mark_click,
+ &start);
+ }
+
+ spell->deferred_check = FALSE;
+
+ return spell;
+}
+
+GeditAutomaticSpellChecker *
+gedit_automatic_spell_checker_get_from_document (const GeditDocument *doc)
+{
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+
+ if (automatic_spell_checker_id == 0)
+ return NULL;
+
+ return g_object_get_qdata (G_OBJECT (doc), automatic_spell_checker_id);
+}
+
+void
+gedit_automatic_spell_checker_free (GeditAutomaticSpellChecker *spell)
+{
+ g_return_if_fail (spell != NULL);
+ g_return_if_fail (gedit_automatic_spell_checker_get_from_document (spell->doc) == spell);
+
+ if (automatic_spell_checker_id == 0)
+ return;
+
+ g_object_set_qdata (G_OBJECT (spell->doc), automatic_spell_checker_id, NULL);
+}
+
+static void
+gedit_automatic_spell_checker_free_internal (GeditAutomaticSpellChecker *spell)
+{
+ GtkTextTagTable *table;
+ GtkTextIter start, end;
+ GSList *list;
+
+ g_return_if_fail (spell != NULL);
+
+ table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (spell->doc));
+
+ if (table != NULL && spell->tag_highlight != NULL)
+ {
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (spell->doc),
+ &start,
+ &end);
+ gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (spell->doc),
+ spell->tag_highlight,
+ &start,
+ &end);
+
+ g_signal_handlers_disconnect_matched (G_OBJECT (table),
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+
+ gtk_text_tag_table_remove (table, spell->tag_highlight);
+ }
+
+ g_signal_handlers_disconnect_matched (G_OBJECT (spell->doc),
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+
+ g_signal_handlers_disconnect_matched (G_OBJECT (spell->spell_checker),
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+
+ g_object_unref (spell->spell_checker);
+
+ list = spell->views;
+ while (list != NULL)
+ {
+ GeditView *view = GEDIT_VIEW (list->data);
+
+ g_signal_handlers_disconnect_matched (G_OBJECT (view),
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+
+ g_signal_handlers_disconnect_matched (G_OBJECT (view),
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+
+ list = g_slist_next (list);
+ }
+
+ g_slist_free (spell->views);
+
+ g_free (spell);
+}
+
+void
+gedit_automatic_spell_checker_attach_view (
+ GeditAutomaticSpellChecker *spell,
+ GeditView *view)
+{
+ g_return_if_fail (spell != NULL);
+ g_return_if_fail (GEDIT_IS_VIEW (view));
+
+ g_return_if_fail (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)) ==
+ GTK_TEXT_BUFFER (spell->doc));
+
+ g_signal_connect (view,
+ "button-press-event",
+ G_CALLBACK (button_press_event),
+ spell);
+ g_signal_connect (view,
+ "popup-menu",
+ G_CALLBACK (popup_menu_event),
+ spell);
+ g_signal_connect (view,
+ "populate-popup",
+ G_CALLBACK (populate_popup),
+ spell);
+ g_signal_connect (view,
+ "destroy",
+ G_CALLBACK (view_destroy),
+ spell);
+
+ spell->views = g_slist_prepend (spell->views, view);
+}
+
+void
+gedit_automatic_spell_checker_detach_view (
+ GeditAutomaticSpellChecker *spell,
+ GeditView *view)
+{
+ g_return_if_fail (spell != NULL);
+ g_return_if_fail (GEDIT_IS_VIEW (view));
+
+ g_return_if_fail (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)) ==
+ GTK_TEXT_BUFFER (spell->doc));
+ g_return_if_fail (spell->views != NULL);
+
+ g_signal_handlers_disconnect_matched (G_OBJECT (view),
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+
+ g_signal_handlers_disconnect_matched (G_OBJECT (view),
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ spell);
+
+ spell->views = g_slist_remove (spell->views, view);
+}
+
diff --git a/plugins/spell/gedit-automatic-spell-checker.h b/plugins/spell/gedit-automatic-spell-checker.h
new file mode 100755
index 00000000..cc634424
--- /dev/null
+++ b/plugins/spell/gedit-automatic-spell-checker.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-automatic-spell-checker.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+/* This is a modified version of gtkspell 2.0.2 (gtkspell.sf.net) */
+/* gtkspell - a spell-checking addon for GTK's TextView widget
+ * Copyright (c) 2002 Evan Martin.
+ */
+
+#ifndef __GEDIT_AUTOMATIC_SPELL_CHECKER_H__
+#define __GEDIT_AUTOMATIC_SPELL_CHECKER_H__
+
+#include <gedit/gedit-document.h>
+#include <gedit/gedit-view.h>
+
+#include "gedit-spell-checker.h"
+
+typedef struct _GeditAutomaticSpellChecker GeditAutomaticSpellChecker;
+
+GeditAutomaticSpellChecker *gedit_automatic_spell_checker_new (
+ GeditDocument *doc,
+ GeditSpellChecker *checker);
+
+GeditAutomaticSpellChecker *gedit_automatic_spell_checker_get_from_document (
+ const GeditDocument *doc);
+
+void gedit_automatic_spell_checker_free (
+ GeditAutomaticSpellChecker *spell);
+
+void gedit_automatic_spell_checker_attach_view (
+ GeditAutomaticSpellChecker *spell,
+ GeditView *view);
+
+void gedit_automatic_spell_checker_detach_view (
+ GeditAutomaticSpellChecker *spell,
+ GeditView *view);
+
+void gedit_automatic_spell_checker_recheck_all (
+ GeditAutomaticSpellChecker *spell);
+
+#endif /* __GEDIT_AUTOMATIC_SPELL_CHECKER_H__ */
+
diff --git a/plugins/spell/gedit-spell-checker-dialog.c b/plugins/spell/gedit-spell-checker-dialog.c
new file mode 100755
index 00000000..0488d160
--- /dev/null
+++ b/plugins/spell/gedit-spell-checker-dialog.c
@@ -0,0 +1,722 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-spell-checker-dialog.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gedit/gedit-utils.h>
+#include "gedit-spell-checker-dialog.h"
+#include "gedit-spell-marshal.h"
+
+struct _GeditSpellCheckerDialog
+{
+ GtkWindow parent_instance;
+
+ GeditSpellChecker *spell_checker;
+
+ gchar *misspelled_word;
+
+ GtkWidget *misspelled_word_label;
+ GtkWidget *word_entry;
+ GtkWidget *check_word_button;
+ GtkWidget *ignore_button;
+ GtkWidget *ignore_all_button;
+ GtkWidget *change_button;
+ GtkWidget *change_all_button;
+ GtkWidget *add_word_button;
+ GtkWidget *close_button;
+ GtkWidget *suggestions_list;
+ GtkWidget *language_label;
+
+ GtkTreeModel *suggestions_list_model;
+};
+
+enum
+{
+ IGNORE,
+ IGNORE_ALL,
+ CHANGE,
+ CHANGE_ALL,
+ ADD_WORD_TO_PERSONAL,
+ LAST_SIGNAL
+};
+
+enum
+{
+ COLUMN_SUGGESTIONS,
+ NUM_COLUMNS
+};
+
+static void update_suggestions_list_model (GeditSpellCheckerDialog *dlg,
+ GSList *suggestions);
+
+static void word_entry_changed_handler (GtkEditable *editable,
+ GeditSpellCheckerDialog *dlg);
+static void close_button_clicked_handler (GtkButton *button,
+ GeditSpellCheckerDialog *dlg);
+static void suggestions_list_selection_changed_handler (GtkTreeSelection *selection,
+ GeditSpellCheckerDialog *dlg);
+static void check_word_button_clicked_handler (GtkButton *button,
+ GeditSpellCheckerDialog *dlg);
+static void add_word_button_clicked_handler (GtkButton *button,
+ GeditSpellCheckerDialog *dlg);
+static void ignore_button_clicked_handler (GtkButton *button,
+ GeditSpellCheckerDialog *dlg);
+static void ignore_all_button_clicked_handler (GtkButton *button,
+ GeditSpellCheckerDialog *dlg);
+static void change_button_clicked_handler (GtkButton *button,
+ GeditSpellCheckerDialog *dlg);
+static void change_all_button_clicked_handler (GtkButton *button,
+ GeditSpellCheckerDialog *dlg);
+static void suggestions_list_row_activated_handler (GtkTreeView *view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GeditSpellCheckerDialog *dlg);
+
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE(GeditSpellCheckerDialog, gedit_spell_checker_dialog, GTK_TYPE_WINDOW)
+
+static void
+gedit_spell_checker_dialog_destroy (GtkObject *object)
+{
+ GeditSpellCheckerDialog *dlg = GEDIT_SPELL_CHECKER_DIALOG (object);
+
+ if (dlg->spell_checker != NULL)
+ {
+ g_object_unref (dlg->spell_checker);
+ dlg->spell_checker = NULL;
+ }
+
+ if (dlg->misspelled_word != NULL)
+ {
+ g_free (dlg->misspelled_word);
+ dlg->misspelled_word = NULL;
+ }
+
+ GTK_OBJECT_CLASS (gedit_spell_checker_dialog_parent_class)->destroy (object);
+}
+
+static void
+gedit_spell_checker_dialog_class_init (GeditSpellCheckerDialogClass * klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ GTK_OBJECT_CLASS (object_class)->destroy = gedit_spell_checker_dialog_destroy;
+
+ signals[IGNORE] =
+ g_signal_new ("ignore",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, ignore),
+ NULL, NULL,
+ gedit_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+
+ signals[IGNORE_ALL] =
+ g_signal_new ("ignore_all",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, ignore_all),
+ NULL, NULL,
+ gedit_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+
+ signals[CHANGE] =
+ g_signal_new ("change",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, change),
+ NULL, NULL,
+ gedit_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ signals[CHANGE_ALL] =
+ g_signal_new ("change_all",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, change_all),
+ NULL, NULL,
+ gedit_marshal_VOID__STRING_STRING,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ signals[ADD_WORD_TO_PERSONAL] =
+ g_signal_new ("add_word_to_personal",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerDialogClass, add_word_to_personal),
+ NULL, NULL,
+ gedit_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+}
+
+static void
+create_dialog (GeditSpellCheckerDialog *dlg,
+ const gchar *data_dir)
+{
+ GtkWidget *error_widget;
+ GtkWidget *content;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GtkTreeSelection *selection;
+ gchar *root_objects[] = {
+ "content",
+ "check_word_image",
+ "add_word_image",
+ "ignore_image",
+ "change_image",
+ "ignore_all_image",
+ "change_all_image",
+ NULL
+ };
+ gboolean ret;
+ gchar *ui_file;
+
+ g_return_if_fail (dlg != NULL);
+
+ dlg->spell_checker = NULL;
+ dlg->misspelled_word = NULL;
+
+ ui_file = g_build_filename (data_dir, "spell-checker.ui", NULL);
+ ret = gedit_utils_get_ui_objects (ui_file,
+ root_objects,
+ &error_widget,
+
+ "content", &content,
+ "misspelled_word_label", &dlg->misspelled_word_label,
+ "word_entry", &dlg->word_entry,
+ "check_word_button", &dlg->check_word_button,
+ "ignore_button", &dlg->ignore_button,
+ "ignore_all_button", &dlg->ignore_all_button,
+ "change_button", &dlg->change_button,
+ "change_all_button", &dlg->change_all_button,
+ "add_word_button", &dlg->add_word_button,
+ "close_button", &dlg->close_button,
+ "suggestions_list", &dlg->suggestions_list,
+ "language_label", &dlg->language_label,
+ NULL);
+ g_free (ui_file);
+
+ if (!ret)
+ {
+ gtk_widget_show (error_widget);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ error_widget, TRUE, TRUE, 0);
+
+ return;
+ }
+
+ gtk_label_set_label (GTK_LABEL (dlg->misspelled_word_label), "");
+ gtk_widget_set_sensitive (dlg->word_entry, FALSE);
+ gtk_widget_set_sensitive (dlg->check_word_button, FALSE);
+ gtk_widget_set_sensitive (dlg->ignore_button, FALSE);
+ gtk_widget_set_sensitive (dlg->ignore_all_button, FALSE);
+ gtk_widget_set_sensitive (dlg->change_button, FALSE);
+ gtk_widget_set_sensitive (dlg->change_all_button, FALSE);
+ gtk_widget_set_sensitive (dlg->add_word_button, FALSE);
+
+ gtk_label_set_label (GTK_LABEL (dlg->language_label), "");
+
+ gtk_container_add (GTK_CONTAINER (dlg), content);
+ g_object_unref (content);
+
+ gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE);
+ gtk_window_set_title (GTK_WINDOW (dlg), _("Check Spelling"));
+
+ /* Suggestion list */
+ dlg->suggestions_list_model = GTK_TREE_MODEL (
+ gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING));
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dlg->suggestions_list),
+ dlg->suggestions_list_model);
+
+ /* Add the suggestions column */
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Suggestions"), cell,
+ "text", COLUMN_SUGGESTIONS, NULL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->suggestions_list), column);
+
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (dlg->suggestions_list),
+ COLUMN_SUGGESTIONS);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->suggestions_list));
+
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
+
+ /* Set default button */
+ GTK_WIDGET_SET_FLAGS (dlg->change_button, GTK_CAN_DEFAULT);
+ gtk_widget_grab_default (dlg->change_button);
+
+ gtk_entry_set_activates_default (GTK_ENTRY (dlg->word_entry), TRUE);
+
+ /* Connect signals */
+ g_signal_connect (dlg->word_entry, "changed",
+ G_CALLBACK (word_entry_changed_handler), dlg);
+ g_signal_connect (dlg->close_button, "clicked",
+ G_CALLBACK (close_button_clicked_handler), dlg);
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (suggestions_list_selection_changed_handler),
+ dlg);
+ g_signal_connect (dlg->check_word_button, "clicked",
+ G_CALLBACK (check_word_button_clicked_handler), dlg);
+ g_signal_connect (dlg->add_word_button, "clicked",
+ G_CALLBACK (add_word_button_clicked_handler), dlg);
+ g_signal_connect (dlg->ignore_button, "clicked",
+ G_CALLBACK (ignore_button_clicked_handler), dlg);
+ g_signal_connect (dlg->ignore_all_button, "clicked",
+ G_CALLBACK (ignore_all_button_clicked_handler), dlg);
+ g_signal_connect (dlg->change_button, "clicked",
+ G_CALLBACK (change_button_clicked_handler), dlg);
+ g_signal_connect (dlg->change_all_button, "clicked",
+ G_CALLBACK (change_all_button_clicked_handler), dlg);
+ g_signal_connect (dlg->suggestions_list, "row-activated",
+ G_CALLBACK (suggestions_list_row_activated_handler), dlg);
+}
+
+static void
+gedit_spell_checker_dialog_init (GeditSpellCheckerDialog *dlg)
+{
+}
+
+GtkWidget *
+gedit_spell_checker_dialog_new (const gchar *data_dir)
+{
+ GeditSpellCheckerDialog *dlg;
+
+ dlg = GEDIT_SPELL_CHECKER_DIALOG (
+ g_object_new (GEDIT_TYPE_SPELL_CHECKER_DIALOG, NULL));
+
+ g_return_val_if_fail (dlg != NULL, NULL);
+
+ create_dialog (dlg, data_dir);
+
+ return GTK_WIDGET (dlg);
+}
+
+GtkWidget *
+gedit_spell_checker_dialog_new_from_spell_checker (GeditSpellChecker *spell,
+ const gchar *data_dir)
+{
+ GeditSpellCheckerDialog *dlg;
+
+ g_return_val_if_fail (spell != NULL, NULL);
+
+ dlg = GEDIT_SPELL_CHECKER_DIALOG (
+ g_object_new (GEDIT_TYPE_SPELL_CHECKER_DIALOG, NULL));
+
+ g_return_val_if_fail (dlg != NULL, NULL);
+
+ create_dialog (dlg, data_dir);
+
+ gedit_spell_checker_dialog_set_spell_checker (dlg, spell);
+
+ return GTK_WIDGET (dlg);
+}
+
+void
+gedit_spell_checker_dialog_set_spell_checker (GeditSpellCheckerDialog *dlg, GeditSpellChecker *spell)
+{
+ const GeditSpellCheckerLanguage* language;
+ const gchar *lang;
+ gchar *tmp;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+ g_return_if_fail (spell != NULL);
+
+ if (dlg->spell_checker != NULL)
+ g_object_unref (dlg->spell_checker);
+
+ dlg->spell_checker = spell;
+ g_object_ref (dlg->spell_checker);
+
+ language = gedit_spell_checker_get_language (dlg->spell_checker);
+
+ lang = gedit_spell_checker_language_to_string (language);
+ tmp = g_strdup_printf("<b>%s</b>", lang);
+
+ gtk_label_set_label (GTK_LABEL (dlg->language_label), tmp);
+ g_free (tmp);
+
+ if (dlg->misspelled_word != NULL)
+ gedit_spell_checker_dialog_set_misspelled_word (dlg, dlg->misspelled_word, -1);
+ else
+ gtk_list_store_clear (GTK_LIST_STORE (dlg->suggestions_list_model));
+
+ /* TODO: reset all widgets */
+}
+
+void
+gedit_spell_checker_dialog_set_misspelled_word (GeditSpellCheckerDialog *dlg,
+ const gchar *word,
+ gint len)
+{
+ gchar *tmp;
+ GSList *sug;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+ g_return_if_fail (word != NULL);
+
+ g_return_if_fail (dlg->spell_checker != NULL);
+ g_return_if_fail (!gedit_spell_checker_check_word (dlg->spell_checker, word, -1));
+
+ /* build_suggestions_list */
+ if (dlg->misspelled_word != NULL)
+ g_free (dlg->misspelled_word);
+
+ dlg->misspelled_word = g_strdup (word);
+
+ tmp = g_strdup_printf("<b>%s</b>", word);
+ gtk_label_set_label (GTK_LABEL (dlg->misspelled_word_label), tmp);
+ g_free (tmp);
+
+ sug = gedit_spell_checker_get_suggestions (dlg->spell_checker,
+ dlg->misspelled_word,
+ -1);
+
+ update_suggestions_list_model (dlg, sug);
+
+ /* free the suggestion list */
+ g_slist_foreach (sug, (GFunc)g_free, NULL);
+ g_slist_free (sug);
+
+ gtk_widget_set_sensitive (dlg->ignore_button, TRUE);
+ gtk_widget_set_sensitive (dlg->ignore_all_button, TRUE);
+ gtk_widget_set_sensitive (dlg->add_word_button, TRUE);
+}
+
+static void
+update_suggestions_list_model (GeditSpellCheckerDialog *dlg, GSList *suggestions)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GtkTreeSelection *sel;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+ g_return_if_fail (GTK_IS_LIST_STORE (dlg->suggestions_list_model));
+
+ store = GTK_LIST_STORE (dlg->suggestions_list_model);
+ gtk_list_store_clear (store);
+
+ gtk_widget_set_sensitive (dlg->word_entry, TRUE);
+
+ if (suggestions == NULL)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ /* Translators: Displayed in the "Check Spelling" dialog if there are no suggestions
+ * for the current misspelled word */
+ COLUMN_SUGGESTIONS, _("(no suggested words)"),
+ -1);
+
+ gtk_entry_set_text (GTK_ENTRY (dlg->word_entry), "");
+
+ gtk_widget_set_sensitive (dlg->suggestions_list, FALSE);
+
+ return;
+ }
+
+ gtk_widget_set_sensitive (dlg->suggestions_list, TRUE);
+
+ gtk_entry_set_text (GTK_ENTRY (dlg->word_entry), (gchar*)suggestions->data);
+
+ while (suggestions != NULL)
+ {
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_SUGGESTIONS, (gchar*)suggestions->data,
+ -1);
+
+ suggestions = g_slist_next (suggestions);
+ }
+
+ sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->suggestions_list));
+ gtk_tree_model_get_iter_first (dlg->suggestions_list_model, &iter);
+ gtk_tree_selection_select_iter (sel, &iter);
+}
+
+static void
+word_entry_changed_handler (GtkEditable *editable, GeditSpellCheckerDialog *dlg)
+{
+ const gchar *text;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+
+ text = gtk_entry_get_text (GTK_ENTRY (dlg->word_entry));
+
+ if (g_utf8_strlen (text, -1) > 0)
+ {
+ gtk_widget_set_sensitive (dlg->check_word_button, TRUE);
+ gtk_widget_set_sensitive (dlg->change_button, TRUE);
+ gtk_widget_set_sensitive (dlg->change_all_button, TRUE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (dlg->check_word_button, FALSE);
+ gtk_widget_set_sensitive (dlg->change_button, FALSE);
+ gtk_widget_set_sensitive (dlg->change_all_button, FALSE);
+ }
+}
+
+static void
+close_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg)
+{
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+}
+
+static void
+suggestions_list_selection_changed_handler (GtkTreeSelection *selection,
+ GeditSpellCheckerDialog *dlg)
+{
+ GtkTreeIter iter;
+ GValue value = {0, };
+ const gchar *text;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+
+ if (! gtk_tree_selection_get_selected (selection, NULL, &iter))
+ return;
+
+ gtk_tree_model_get_value (dlg->suggestions_list_model, &iter,
+ COLUMN_SUGGESTIONS,
+ &value);
+
+ text = g_value_get_string (&value);
+
+ gtk_entry_set_text (GTK_ENTRY (dlg->word_entry), text);
+
+ g_value_unset (&value);
+}
+
+static void
+check_word_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg)
+{
+ const gchar *word;
+ gssize len;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+
+ word = gtk_entry_get_text (GTK_ENTRY (dlg->word_entry));
+ len = strlen (word);
+ g_return_if_fail (len > 0);
+
+ if (gedit_spell_checker_check_word (dlg->spell_checker, word, len))
+ {
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ store = GTK_LIST_STORE (dlg->suggestions_list_model);
+ gtk_list_store_clear (store);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ /* Translators: Displayed in the "Check Spelling" dialog if the current word isn't misspelled */
+ COLUMN_SUGGESTIONS, _("(correct spelling)"),
+ -1);
+
+ gtk_widget_set_sensitive (dlg->suggestions_list, FALSE);
+ }
+ else
+ {
+ GSList *sug;
+
+ sug = gedit_spell_checker_get_suggestions (dlg->spell_checker,
+ word,
+ len);
+
+ update_suggestions_list_model (dlg, sug);
+
+ /* free the suggestion list */
+ g_slist_foreach (sug, (GFunc)g_free, NULL);
+ g_slist_free (sug);
+ }
+}
+
+static void
+add_word_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg)
+{
+ gchar *word;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+ g_return_if_fail (dlg->misspelled_word != NULL);
+
+ gedit_spell_checker_add_word_to_personal (dlg->spell_checker,
+ dlg->misspelled_word,
+ -1);
+
+ word = g_strdup (dlg->misspelled_word);
+
+ g_signal_emit (G_OBJECT (dlg), signals [ADD_WORD_TO_PERSONAL], 0, word);
+
+ g_free (word);
+}
+
+static void
+ignore_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg)
+{
+ gchar *word;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+ g_return_if_fail (dlg->misspelled_word != NULL);
+
+ word = g_strdup (dlg->misspelled_word);
+
+ g_signal_emit (G_OBJECT (dlg), signals [IGNORE], 0, word);
+
+ g_free (word);
+}
+
+static void
+ignore_all_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg)
+{
+ gchar *word;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+ g_return_if_fail (dlg->misspelled_word != NULL);
+
+ gedit_spell_checker_add_word_to_session (dlg->spell_checker,
+ dlg->misspelled_word,
+ -1);
+
+ word = g_strdup (dlg->misspelled_word);
+
+ g_signal_emit (G_OBJECT (dlg), signals [IGNORE_ALL], 0, word);
+
+ g_free (word);
+}
+
+static void
+change_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg)
+{
+ gchar *word;
+ gchar *change;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+ g_return_if_fail (dlg->misspelled_word != NULL);
+
+ change = g_strdup (gtk_entry_get_text (GTK_ENTRY (dlg->word_entry)));
+ g_return_if_fail (change != NULL);
+ g_return_if_fail (*change != '\0');
+
+ gedit_spell_checker_set_correction (dlg->spell_checker,
+ dlg->misspelled_word, -1,
+ change, -1);
+
+ word = g_strdup (dlg->misspelled_word);
+
+ g_signal_emit (G_OBJECT (dlg), signals [CHANGE], 0, word, change);
+
+ g_free (word);
+ g_free (change);
+}
+
+/* double click on one of the suggestions is like clicking on "change" */
+static void
+suggestions_list_row_activated_handler (GtkTreeView *view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GeditSpellCheckerDialog *dlg)
+{
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+
+ change_button_clicked_handler (GTK_BUTTON (dlg->change_button), dlg);
+}
+
+static void
+change_all_button_clicked_handler (GtkButton *button, GeditSpellCheckerDialog *dlg)
+{
+ gchar *word;
+ gchar *change;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+ g_return_if_fail (dlg->misspelled_word != NULL);
+
+ change = g_strdup (gtk_entry_get_text (GTK_ENTRY (dlg->word_entry)));
+ g_return_if_fail (change != NULL);
+ g_return_if_fail (*change != '\0');
+
+ gedit_spell_checker_set_correction (dlg->spell_checker,
+ dlg->misspelled_word, -1,
+ change, -1);
+
+ word = g_strdup (dlg->misspelled_word);
+
+ g_signal_emit (G_OBJECT (dlg), signals [CHANGE_ALL], 0, word, change);
+
+ g_free (word);
+ g_free (change);
+}
+
+void
+gedit_spell_checker_dialog_set_completed (GeditSpellCheckerDialog *dlg)
+{
+ gchar *tmp;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER_DIALOG (dlg));
+
+ tmp = g_strdup_printf("<b>%s</b>", _("Completed spell checking"));
+ gtk_label_set_label (GTK_LABEL (dlg->misspelled_word_label),
+ tmp);
+ g_free (tmp);
+
+ gtk_list_store_clear (GTK_LIST_STORE (dlg->suggestions_list_model));
+ gtk_entry_set_text (GTK_ENTRY (dlg->word_entry), "");
+
+ gtk_widget_set_sensitive (dlg->word_entry, FALSE);
+ gtk_widget_set_sensitive (dlg->check_word_button, FALSE);
+ gtk_widget_set_sensitive (dlg->ignore_button, FALSE);
+ gtk_widget_set_sensitive (dlg->ignore_all_button, FALSE);
+ gtk_widget_set_sensitive (dlg->change_button, FALSE);
+ gtk_widget_set_sensitive (dlg->change_all_button, FALSE);
+ gtk_widget_set_sensitive (dlg->add_word_button, FALSE);
+ gtk_widget_set_sensitive (dlg->suggestions_list, FALSE);
+}
+
diff --git a/plugins/spell/gedit-spell-checker-dialog.h b/plugins/spell/gedit-spell-checker-dialog.h
new file mode 100755
index 00000000..257c2b75
--- /dev/null
+++ b/plugins/spell/gedit-spell-checker-dialog.h
@@ -0,0 +1,92 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-spell-checker-dialog.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __GEDIT_SPELL_CHECKER_DIALOG_H__
+#define __GEDIT_SPELL_CHECKER_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include "gedit-spell-checker.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_SPELL_CHECKER_DIALOG (gedit_spell_checker_dialog_get_type ())
+#define GEDIT_SPELL_CHECKER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_SPELL_CHECKER_DIALOG, GeditSpellCheckerDialog))
+#define GEDIT_SPELL_CHECKER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_SPELL_CHECKER_DIALOG, GeditSpellCheckerDialog))
+#define GEDIT_IS_SPELL_CHECKER_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_SPELL_CHECKER_DIALOG))
+#define GEDIT_IS_SPELL_CHECKER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_SPELL_CHECKER_DIALOG))
+#define GEDIT_SPELL_CHECKER_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_SPELL_CHECKER_DIALOG, GeditSpellCheckerDialog))
+
+
+typedef struct _GeditSpellCheckerDialog GeditSpellCheckerDialog;
+
+typedef struct _GeditSpellCheckerDialogClass GeditSpellCheckerDialogClass;
+
+struct _GeditSpellCheckerDialogClass
+{
+ GtkWindowClass parent_class;
+
+ /* Signals */
+ void (*ignore) (GeditSpellCheckerDialog *dlg,
+ const gchar *word);
+ void (*ignore_all) (GeditSpellCheckerDialog *dlg,
+ const gchar *word);
+ void (*change) (GeditSpellCheckerDialog *dlg,
+ const gchar *word,
+ const gchar *change_to);
+ void (*change_all) (GeditSpellCheckerDialog *dlg,
+ const gchar *word,
+ const gchar *change_to);
+ void (*add_word_to_personal) (GeditSpellCheckerDialog *dlg,
+ const gchar *word);
+
+};
+
+GType gedit_spell_checker_dialog_get_type (void) G_GNUC_CONST;
+
+/* Constructors */
+GtkWidget *gedit_spell_checker_dialog_new (const gchar *data_dir);
+GtkWidget *gedit_spell_checker_dialog_new_from_spell_checker
+ (GeditSpellChecker *spell,
+ const gchar *data_dir);
+
+void gedit_spell_checker_dialog_set_spell_checker
+ (GeditSpellCheckerDialog *dlg,
+ GeditSpellChecker *spell);
+void gedit_spell_checker_dialog_set_misspelled_word
+ (GeditSpellCheckerDialog *dlg,
+ const gchar* word,
+ gint len);
+
+void gedit_spell_checker_dialog_set_completed
+ (GeditSpellCheckerDialog *dlg);
+
+G_END_DECLS
+
+#endif /* __GEDIT_SPELL_CHECKER_DIALOG_H__ */
+
diff --git a/plugins/spell/gedit-spell-checker-language.c b/plugins/spell/gedit-spell-checker-language.c
new file mode 100755
index 00000000..ae7e2422
--- /dev/null
+++ b/plugins/spell/gedit-spell-checker-language.c
@@ -0,0 +1,439 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-spell-checker-language.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2006 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2006. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+/* Part of the code taked from Epiphany.
+ *
+ * Copyright (C) 2003, 2004 Christian Persch
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <enchant.h>
+
+#include <glib/gi18n.h>
+#include <libxml/xmlreader.h>
+
+#include "gedit-spell-checker-language.h"
+
+#include <gedit/gedit-debug.h>
+
+#define ISO_639_DOMAIN "iso_639"
+#define ISO_3166_DOMAIN "iso_3166"
+
+#define ISOCODESLOCALEDIR ISO_CODES_PREFIX "/share/locale"
+
+struct _GeditSpellCheckerLanguage
+{
+ gchar *abrev;
+ gchar *name;
+};
+
+static gboolean available_languages_initialized = FALSE;
+static GSList *available_languages = NULL;
+
+static GHashTable *iso_639_table = NULL;
+static GHashTable *iso_3166_table = NULL;
+
+static void
+bind_iso_domains (void)
+{
+ static gboolean bound = FALSE;
+
+ if (bound == FALSE)
+ {
+ bindtextdomain (ISO_639_DOMAIN, ISOCODESLOCALEDIR);
+ bind_textdomain_codeset (ISO_639_DOMAIN, "UTF-8");
+
+ bindtextdomain(ISO_3166_DOMAIN, ISOCODESLOCALEDIR);
+ bind_textdomain_codeset (ISO_3166_DOMAIN, "UTF-8");
+
+ bound = TRUE;
+ }
+}
+
+static void
+read_iso_639_entry (xmlTextReaderPtr reader,
+ GHashTable *table)
+{
+ xmlChar *code, *name;
+
+ code = xmlTextReaderGetAttribute (reader, (const xmlChar *) "iso_639_1_code");
+ name = xmlTextReaderGetAttribute (reader, (const xmlChar *) "name");
+
+ /* Get iso-639-2 code */
+ if (code == NULL || code[0] == '\0')
+ {
+ xmlFree (code);
+ /* FIXME: use the 2T or 2B code? */
+ code = xmlTextReaderGetAttribute (reader, (const xmlChar *) "iso_639_2T_code");
+ }
+
+ if (code != NULL && code[0] != '\0' && name != NULL && name[0] != '\0')
+ {
+ g_hash_table_insert (table, code, name);
+ }
+ else
+ {
+ xmlFree (code);
+ xmlFree (name);
+ }
+}
+
+static void
+read_iso_3166_entry (xmlTextReaderPtr reader,
+ GHashTable *table)
+{
+ xmlChar *code, *name;
+
+ code = xmlTextReaderGetAttribute (reader, (const xmlChar *) "alpha_2_code");
+ name = xmlTextReaderGetAttribute (reader, (const xmlChar *) "name");
+
+ if (code != NULL && code[0] != '\0' && name != NULL && name[0] != '\0')
+ {
+ char *lcode;
+
+ lcode = g_ascii_strdown ((char *) code, -1);
+ xmlFree (code);
+
+ /* g_print ("%s -> %s\n", lcode, name); */
+
+ g_hash_table_insert (table, lcode, name);
+ }
+ else
+ {
+ xmlFree (code);
+ xmlFree (name);
+ }
+}
+
+typedef enum
+{
+ STATE_START,
+ STATE_STOP,
+ STATE_ENTRIES,
+} ParserState;
+
+static void
+load_iso_entries (int iso,
+ GFunc read_entry_func,
+ gpointer user_data)
+{
+ xmlTextReaderPtr reader;
+ ParserState state = STATE_START;
+ xmlChar iso_entries[32], iso_entry[32];
+ char *filename;
+ int ret = -1;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Loading ISO-%d codes", iso);
+
+ filename = g_strdup_printf (ISO_CODES_PREFIX "/share/xml/iso-codes/iso_%d.xml", iso);
+ reader = xmlNewTextReaderFilename (filename);
+ if (reader == NULL) goto out;
+
+ xmlStrPrintf (iso_entries, sizeof (iso_entries), (const xmlChar *)"iso_%d_entries", iso);
+ xmlStrPrintf (iso_entry, sizeof (iso_entry), (const xmlChar *)"iso_%d_entry", iso);
+
+ ret = xmlTextReaderRead (reader);
+
+ while (ret == 1)
+ {
+ const xmlChar *tag;
+ xmlReaderTypes type;
+
+ tag = xmlTextReaderConstName (reader);
+ type = xmlTextReaderNodeType (reader);
+
+ if (state == STATE_ENTRIES &&
+ type == XML_READER_TYPE_ELEMENT &&
+ xmlStrEqual (tag, iso_entry))
+ {
+ read_entry_func (reader, user_data);
+ }
+ else if (state == STATE_START &&
+ type == XML_READER_TYPE_ELEMENT &&
+ xmlStrEqual (tag, iso_entries))
+ {
+ state = STATE_ENTRIES;
+ }
+ else if (state == STATE_ENTRIES &&
+ type == XML_READER_TYPE_END_ELEMENT &&
+ xmlStrEqual (tag, iso_entries))
+ {
+ state = STATE_STOP;
+ }
+ else if (type == XML_READER_TYPE_SIGNIFICANT_WHITESPACE ||
+ type == XML_READER_TYPE_WHITESPACE ||
+ type == XML_READER_TYPE_TEXT ||
+ type == XML_READER_TYPE_COMMENT)
+ {
+ /* eat it */
+ }
+ else
+ {
+ /* ignore it */
+ }
+
+ ret = xmlTextReaderRead (reader);
+ }
+
+ xmlFreeTextReader (reader);
+
+out:
+ if (ret < 0 || state != STATE_STOP)
+ {
+ g_warning ("Failed to load ISO-%d codes from %s!\n",
+ iso, filename);
+ }
+
+ g_free (filename);
+}
+
+static GHashTable *
+create_iso_639_table (void)
+{
+ GHashTable *table;
+
+ bind_iso_domains ();
+ table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) xmlFree,
+ (GDestroyNotify) xmlFree);
+
+ load_iso_entries (639, (GFunc) read_iso_639_entry, table);
+
+ return table;
+}
+
+static GHashTable *
+create_iso_3166_table (void)
+{
+ GHashTable *table;
+
+ bind_iso_domains ();
+ table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) xmlFree);
+
+ load_iso_entries (3166, (GFunc) read_iso_3166_entry, table);
+
+ return table;
+}
+
+static char *
+create_name_for_language (const char *code)
+{
+ char **str;
+ char *name = NULL;
+ const char *langname, *localename;
+ int len;
+
+ g_return_val_if_fail (iso_639_table != NULL, NULL);
+ g_return_val_if_fail (iso_3166_table != NULL, NULL);
+
+ str = g_strsplit (code, "_", -1);
+ len = g_strv_length (str);
+ g_return_val_if_fail (len != 0, NULL);
+
+ langname = (const char *) g_hash_table_lookup (iso_639_table, str[0]);
+
+ if (len == 1 && langname != NULL)
+ {
+ name = g_strdup (dgettext (ISO_639_DOMAIN, langname));
+ }
+ else if (len == 2 && langname != NULL)
+ {
+ gchar *locale_code = g_ascii_strdown (str[1], -1);
+
+ localename = (const char *) g_hash_table_lookup (iso_3166_table, locale_code);
+ g_free (locale_code);
+
+ if (localename != NULL)
+ {
+ /* Translators: the first %s is the language name, and
+ * the second %s is the locale name. Example:
+ * "French (France)"
+ */
+ name = g_strdup_printf (C_("language", "%s (%s)"),
+ dgettext (ISO_639_DOMAIN, langname),
+ dgettext (ISO_3166_DOMAIN, localename));
+ }
+ else
+ {
+ name = g_strdup_printf (C_("language", "%s (%s)"),
+ dgettext (ISO_639_DOMAIN, langname), str[1]);
+ }
+ }
+ else
+ {
+ /* Translators: this refers to an unknown language code
+ * (one which isn't in our built-in list).
+ */
+ name = g_strdup_printf (C_("language", "Unknown (%s)"), code);
+ }
+
+ g_strfreev (str);
+
+ return name;
+}
+
+static void
+enumerate_dicts (const char * const lang_tag,
+ const char * const provider_name,
+ const char * const provider_desc,
+ const char * const provider_file,
+ void * user_data)
+{
+ gchar *lang_name;
+
+ GTree *dicts = (GTree *)user_data;
+
+ lang_name = create_name_for_language (lang_tag);
+ g_return_if_fail (lang_name != NULL);
+
+ /* g_print ("%s - %s\n", lang_tag, lang_name); */
+
+ g_tree_replace (dicts, g_strdup (lang_tag), lang_name);
+}
+
+static gint
+key_cmp (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+ return strcmp (a, b);
+}
+
+static gint
+lang_cmp (const GeditSpellCheckerLanguage *a,
+ const GeditSpellCheckerLanguage *b)
+{
+ return g_utf8_collate (a->name, b->name);
+}
+
+static gboolean
+build_langs_list (const gchar *key,
+ const gchar *value,
+ gpointer data)
+{
+ GeditSpellCheckerLanguage *lang = g_new (GeditSpellCheckerLanguage, 1);
+
+ lang->abrev = g_strdup (key);
+ lang->name = g_strdup (value);
+
+ available_languages = g_slist_insert_sorted (available_languages,
+ lang,
+ (GCompareFunc)lang_cmp);
+
+ return FALSE;
+}
+
+const GSList *
+gedit_spell_checker_get_available_languages (void)
+{
+ EnchantBroker *broker;
+ GTree *dicts;
+
+ if (available_languages_initialized)
+ return available_languages;
+
+ g_return_val_if_fail (available_languages == NULL, NULL);
+
+ available_languages_initialized = TRUE;
+
+ broker = enchant_broker_init ();
+ g_return_val_if_fail (broker != NULL, NULL);
+
+ /* Use a GTree to efficiently remove duplicates while building the list */
+ dicts = g_tree_new_full (key_cmp,
+ NULL,
+ (GDestroyNotify)g_free,
+ (GDestroyNotify)g_free);
+
+ iso_639_table = create_iso_639_table ();
+ iso_3166_table = create_iso_3166_table ();
+
+ enchant_broker_list_dicts (broker, enumerate_dicts, dicts);
+
+ enchant_broker_free (broker);
+
+ g_hash_table_destroy (iso_639_table);
+ g_hash_table_destroy (iso_3166_table);
+
+ iso_639_table = NULL;
+ iso_3166_table = NULL;
+
+ g_tree_foreach (dicts, (GTraverseFunc)build_langs_list, NULL);
+
+ g_tree_destroy (dicts);
+
+ return available_languages;
+}
+
+const gchar *
+gedit_spell_checker_language_to_string (const GeditSpellCheckerLanguage *lang)
+{
+ if (lang == NULL)
+ /* Translators: this refers the Default language used by the
+ * spell checker
+ */
+ return C_("language", "Default");
+
+ return lang->name;
+}
+
+const gchar *
+gedit_spell_checker_language_to_key (const GeditSpellCheckerLanguage *lang)
+{
+ g_return_val_if_fail (lang != NULL, NULL);
+
+ return lang->abrev;
+}
+
+const GeditSpellCheckerLanguage *
+gedit_spell_checker_language_from_key (const gchar *key)
+{
+ const GSList *langs;
+
+ g_return_val_if_fail (key != NULL, NULL);
+
+ langs = gedit_spell_checker_get_available_languages ();
+
+ while (langs != NULL)
+ {
+ const GeditSpellCheckerLanguage *l = (const GeditSpellCheckerLanguage *)langs->data;
+
+ if (g_ascii_strcasecmp (key, l->abrev) == 0)
+ return l;
+
+ langs = g_slist_next (langs);
+ }
+
+ return NULL;
+}
diff --git a/plugins/spell/gedit-spell-checker-language.h b/plugins/spell/gedit-spell-checker-language.h
new file mode 100755
index 00000000..7e62de65
--- /dev/null
+++ b/plugins/spell/gedit-spell-checker-language.h
@@ -0,0 +1,51 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-spell-checker-language.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2006 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2006. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __GEDIT_SPELL_CHECKER_LANGUAGE_H__
+#define __GEDIT_SPELL_CHECKER_LANGUAGE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GeditSpellCheckerLanguage GeditSpellCheckerLanguage;
+
+const gchar *gedit_spell_checker_language_to_string (const GeditSpellCheckerLanguage *lang);
+
+const gchar *gedit_spell_checker_language_to_key (const GeditSpellCheckerLanguage *lang);
+
+const GeditSpellCheckerLanguage *gedit_spell_checker_language_from_key (const gchar *key);
+
+/* GSList contains "GeditSpellCheckerLanguage*" items */
+const GSList *gedit_spell_checker_get_available_languages
+ (void);
+
+G_END_DECLS
+
+#endif /* __GEDIT_SPELL_CHECKER_LANGUAGE_H__ */
diff --git a/plugins/spell/gedit-spell-checker.c b/plugins/spell/gedit-spell-checker.c
new file mode 100755
index 00000000..51b8d8a8
--- /dev/null
+++ b/plugins/spell/gedit-spell-checker.c
@@ -0,0 +1,520 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-spell-checker.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002-2006 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002-2006. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <enchant.h>
+
+#include <glib/gi18n.h>
+#include <glib.h>
+
+#include "gedit-spell-checker.h"
+#include "gedit-spell-utils.h"
+#include "gedit-spell-marshal.h"
+
+struct _GeditSpellChecker
+{
+ GObject parent_instance;
+
+ EnchantDict *dict;
+ EnchantBroker *broker;
+ const GeditSpellCheckerLanguage *active_lang;
+};
+
+/* GObject properties */
+enum {
+ PROP_0 = 0,
+ PROP_LANGUAGE,
+ LAST_PROP
+};
+
+/* Signals */
+enum {
+ ADD_WORD_TO_PERSONAL = 0,
+ ADD_WORD_TO_SESSION,
+ SET_LANGUAGE,
+ CLEAR_SESSION,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE(GeditSpellChecker, gedit_spell_checker, G_TYPE_OBJECT)
+
+static void
+gedit_spell_checker_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ /*
+ GeditSpellChecker *spell = GEDIT_SPELL_CHECKER (object);
+ */
+
+ switch (prop_id)
+ {
+ case PROP_LANGUAGE:
+ /* TODO */
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gedit_spell_checker_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ /*
+ GeditSpellChecker *spell = GEDIT_SPELL_CHECKER (object);
+ */
+
+ switch (prop_id)
+ {
+ case PROP_LANGUAGE:
+ /* TODO */
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+gedit_spell_checker_finalize (GObject *object)
+{
+ GeditSpellChecker *spell_checker;
+
+ g_return_if_fail (GEDIT_IS_SPELL_CHECKER (object));
+
+ spell_checker = GEDIT_SPELL_CHECKER (object);
+
+ if (spell_checker->dict != NULL)
+ enchant_broker_free_dict (spell_checker->broker, spell_checker->dict);
+
+ if (spell_checker->broker != NULL)
+ enchant_broker_free (spell_checker->broker);
+
+ G_OBJECT_CLASS (gedit_spell_checker_parent_class)->finalize (object);
+}
+
+static void
+gedit_spell_checker_class_init (GeditSpellCheckerClass * klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gedit_spell_checker_set_property;
+ object_class->get_property = gedit_spell_checker_get_property;
+
+ object_class->finalize = gedit_spell_checker_finalize;
+
+ g_object_class_install_property (object_class,
+ PROP_LANGUAGE,
+ g_param_spec_pointer ("language",
+ "Language",
+ "The language used by the spell checker",
+ G_PARAM_READWRITE));
+
+ signals[ADD_WORD_TO_PERSONAL] =
+ g_signal_new ("add_word_to_personal",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerClass, add_word_to_personal),
+ NULL, NULL,
+ gedit_marshal_VOID__STRING_INT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING,
+ G_TYPE_INT);
+
+ signals[ADD_WORD_TO_SESSION] =
+ g_signal_new ("add_word_to_session",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerClass, add_word_to_session),
+ NULL, NULL,
+ gedit_marshal_VOID__STRING_INT,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_STRING,
+ G_TYPE_INT);
+
+ signals[SET_LANGUAGE] =
+ g_signal_new ("set_language",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerClass, set_language),
+ NULL, NULL,
+ gedit_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER);
+
+ signals[CLEAR_SESSION] =
+ g_signal_new ("clear_session",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GeditSpellCheckerClass, clear_session),
+ NULL, NULL,
+ gedit_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+gedit_spell_checker_init (GeditSpellChecker *spell_checker)
+{
+ spell_checker->broker = enchant_broker_init ();
+ spell_checker->dict = NULL;
+ spell_checker->active_lang = NULL;
+}
+
+GeditSpellChecker *
+gedit_spell_checker_new (void)
+{
+ GeditSpellChecker *spell;
+
+ spell = GEDIT_SPELL_CHECKER (
+ g_object_new (GEDIT_TYPE_SPELL_CHECKER, NULL));
+
+ g_return_val_if_fail (spell != NULL, NULL);
+
+ return spell;
+}
+
+static gboolean
+lazy_init (GeditSpellChecker *spell,
+ const GeditSpellCheckerLanguage *language)
+{
+ if (spell->dict != NULL)
+ return TRUE;
+
+ g_return_val_if_fail (spell->broker != NULL, FALSE);
+
+ spell->active_lang = NULL;
+
+ if (language != NULL)
+ {
+ spell->active_lang = language;
+ }
+ else
+ {
+ /* First try to get a default language */
+ const GeditSpellCheckerLanguage *l;
+ gint i = 0;
+ const gchar * const *lang_tags = g_get_language_names ();
+
+ while (lang_tags [i])
+ {
+ l = gedit_spell_checker_language_from_key (lang_tags [i]);
+
+ if (l != NULL)
+ {
+ spell->active_lang = l;
+ break;
+ }
+
+ i++;
+ }
+ }
+
+ /* Second try to get a default language */
+ if (spell->active_lang == NULL)
+ spell->active_lang = gedit_spell_checker_language_from_key ("en_US");
+
+ /* Last try to get a default language */
+ if (spell->active_lang == NULL)
+ {
+ const GSList *langs;
+ langs = gedit_spell_checker_get_available_languages ();
+ if (langs != NULL)
+ spell->active_lang = (const GeditSpellCheckerLanguage *)langs->data;
+ }
+
+ if (spell->active_lang != NULL)
+ {
+ const gchar *key;
+
+ key = gedit_spell_checker_language_to_key (spell->active_lang);
+
+ spell->dict = enchant_broker_request_dict (spell->broker,
+ key);
+ }
+
+ if (spell->dict == NULL)
+ {
+ spell->active_lang = NULL;
+
+ if (language != NULL)
+ g_warning ("Spell checker plugin: cannot select a default language.");
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gedit_spell_checker_set_language (GeditSpellChecker *spell,
+ const GeditSpellCheckerLanguage *language)
+{
+ gboolean ret;
+
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE);
+
+ if (spell->dict != NULL)
+ {
+ enchant_broker_free_dict (spell->broker, spell->dict);
+ spell->dict = NULL;
+ }
+
+ ret = lazy_init (spell, language);
+
+ if (ret)
+ g_signal_emit (G_OBJECT (spell), signals[SET_LANGUAGE], 0, language);
+ else
+ g_warning ("Spell checker plugin: cannot use language %s.",
+ gedit_spell_checker_language_to_string (language));
+
+ return ret;
+}
+
+const GeditSpellCheckerLanguage *
+gedit_spell_checker_get_language (GeditSpellChecker *spell)
+{
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), NULL);
+
+ if (!lazy_init (spell, spell->active_lang))
+ return NULL;
+
+ return spell->active_lang;
+}
+
+gboolean
+gedit_spell_checker_check_word (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize len)
+{
+ gint enchant_result;
+ gboolean res = FALSE;
+
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE);
+ g_return_val_if_fail (word != NULL, FALSE);
+
+ if (!lazy_init (spell, spell->active_lang))
+ return FALSE;
+
+ if (len < 0)
+ len = strlen (word);
+
+ if (strcmp (word, "gedit") == 0)
+ return TRUE;
+
+ if (gedit_spell_utils_is_digit (word, len))
+ return TRUE;
+
+ g_return_val_if_fail (spell->dict != NULL, FALSE);
+ enchant_result = enchant_dict_check (spell->dict, word, len);
+
+ switch (enchant_result)
+ {
+ case -1:
+ /* error */
+ res = FALSE;
+
+ g_warning ("Spell checker plugin: error checking word '%s' (%s).",
+ word, enchant_dict_get_error (spell->dict));
+
+ break;
+ case 1:
+ /* it is not in the directory */
+ res = FALSE;
+ break;
+ case 0:
+ /* is is in the directory */
+ res = TRUE;
+ break;
+ default:
+ g_return_val_if_reached (FALSE);
+ }
+
+ return res;
+}
+
+
+/* return NULL on error or if no suggestions are found */
+GSList *
+gedit_spell_checker_get_suggestions (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize len)
+{
+ gchar **suggestions;
+ size_t n_suggestions = 0;
+ GSList *suggestions_list = NULL;
+ gint i;
+
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), NULL);
+ g_return_val_if_fail (word != NULL, NULL);
+
+ if (!lazy_init (spell, spell->active_lang))
+ return NULL;
+
+ g_return_val_if_fail (spell->dict != NULL, NULL);
+
+ if (len < 0)
+ len = strlen (word);
+
+ suggestions = enchant_dict_suggest (spell->dict, word, len, &n_suggestions);
+
+ if (n_suggestions == 0)
+ return NULL;
+
+ g_return_val_if_fail (suggestions != NULL, NULL);
+
+ for (i = 0; i < (gint)n_suggestions; i++)
+ {
+ suggestions_list = g_slist_prepend (suggestions_list,
+ suggestions[i]);
+ }
+
+ /* The single suggestions will be freed by the caller */
+ g_free (suggestions);
+
+ suggestions_list = g_slist_reverse (suggestions_list);
+
+ return suggestions_list;
+}
+
+gboolean
+gedit_spell_checker_add_word_to_personal (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize len)
+{
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE);
+ g_return_val_if_fail (word != NULL, FALSE);
+
+ if (!lazy_init (spell, spell->active_lang))
+ return FALSE;
+
+ g_return_val_if_fail (spell->dict != NULL, FALSE);
+
+ if (len < 0)
+ len = strlen (word);
+
+ enchant_dict_add_to_pwl (spell->dict, word, len);
+
+ g_signal_emit (G_OBJECT (spell), signals[ADD_WORD_TO_PERSONAL], 0, word, len);
+
+ return TRUE;
+}
+
+gboolean
+gedit_spell_checker_add_word_to_session (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize len)
+{
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE);
+ g_return_val_if_fail (word != NULL, FALSE);
+
+ if (!lazy_init (spell, spell->active_lang))
+ return FALSE;
+
+ g_return_val_if_fail (spell->dict != NULL, FALSE);
+
+ if (len < 0)
+ len = strlen (word);
+
+ enchant_dict_add_to_session (spell->dict, word, len);
+
+ g_signal_emit (G_OBJECT (spell), signals[ADD_WORD_TO_SESSION], 0, word, len);
+
+ return TRUE;
+}
+
+gboolean
+gedit_spell_checker_clear_session (GeditSpellChecker *spell)
+{
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE);
+
+ /* free and re-request dictionary */
+ if (spell->dict != NULL)
+ {
+ enchant_broker_free_dict (spell->broker, spell->dict);
+ spell->dict = NULL;
+ }
+
+ if (!lazy_init (spell, spell->active_lang))
+ return FALSE;
+
+ g_signal_emit (G_OBJECT (spell), signals[CLEAR_SESSION], 0);
+
+ return TRUE;
+}
+
+/*
+ * Informs dictionary, that word 'word' will be replaced/corrected by word
+ * 'replacement'
+ */
+gboolean
+gedit_spell_checker_set_correction (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize w_len,
+ const gchar *replacement,
+ gssize r_len)
+{
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (spell), FALSE);
+ g_return_val_if_fail (word != NULL, FALSE);
+ g_return_val_if_fail (replacement != NULL, FALSE);
+
+ if (!lazy_init (spell, spell->active_lang))
+ return FALSE;
+
+ g_return_val_if_fail (spell->dict != NULL, FALSE);
+
+ if (w_len < 0)
+ w_len = strlen (word);
+
+ if (r_len < 0)
+ r_len = strlen (replacement);
+
+ enchant_dict_store_replacement (spell->dict,
+ word,
+ w_len,
+ replacement,
+ r_len);
+
+ return TRUE;
+}
+
diff --git a/plugins/spell/gedit-spell-checker.h b/plugins/spell/gedit-spell-checker.h
new file mode 100755
index 00000000..0cc69d31
--- /dev/null
+++ b/plugins/spell/gedit-spell-checker.h
@@ -0,0 +1,109 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-spell-checker.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002-2006 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __GEDIT_SPELL_CHECKER_H__
+#define __GEDIT_SPELL_CHECKER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "gedit-spell-checker-language.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_SPELL_CHECKER (gedit_spell_checker_get_type ())
+#define GEDIT_SPELL_CHECKER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_SPELL_CHECKER, GeditSpellChecker))
+#define GEDIT_SPELL_CHECKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_SPELL_CHECKER, GeditSpellChecker))
+#define GEDIT_IS_SPELL_CHECKER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_SPELL_CHECKER))
+#define GEDIT_IS_SPELL_CHECKER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_SPELL_CHECKER))
+#define GEDIT_SPELL_CHECKER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_SPELL_CHECKER, GeditSpellChecker))
+
+typedef struct _GeditSpellChecker GeditSpellChecker;
+
+typedef struct _GeditSpellCheckerClass GeditSpellCheckerClass;
+
+struct _GeditSpellCheckerClass
+{
+ GObjectClass parent_class;
+
+ /* Signals */
+ void (*add_word_to_personal) (GeditSpellChecker *spell,
+ const gchar *word,
+ gint len);
+
+ void (*add_word_to_session) (GeditSpellChecker *spell,
+ const gchar *word,
+ gint len);
+
+ void (*set_language) (GeditSpellChecker *spell,
+ const GeditSpellCheckerLanguage *lang);
+
+ void (*clear_session) (GeditSpellChecker *spell);
+};
+
+
+GType gedit_spell_checker_get_type (void) G_GNUC_CONST;
+
+/* Constructors */
+GeditSpellChecker *gedit_spell_checker_new (void);
+
+gboolean gedit_spell_checker_set_language (GeditSpellChecker *spell,
+ const GeditSpellCheckerLanguage *lang);
+const GeditSpellCheckerLanguage
+ *gedit_spell_checker_get_language (GeditSpellChecker *spell);
+
+gboolean gedit_spell_checker_check_word (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize len);
+
+GSList *gedit_spell_checker_get_suggestions (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize len);
+
+gboolean gedit_spell_checker_add_word_to_personal
+ (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize len);
+
+gboolean gedit_spell_checker_add_word_to_session
+ (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize len);
+
+gboolean gedit_spell_checker_clear_session (GeditSpellChecker *spell);
+
+gboolean gedit_spell_checker_set_correction (GeditSpellChecker *spell,
+ const gchar *word,
+ gssize w_len,
+ const gchar *replacement,
+ gssize r_len);
+G_END_DECLS
+
+#endif /* __GEDIT_SPELL_CHECKER_H__ */
+
diff --git a/plugins/spell/gedit-spell-language-dialog.c b/plugins/spell/gedit-spell-language-dialog.c
new file mode 100755
index 00000000..1abba17f
--- /dev/null
+++ b/plugins/spell/gedit-spell-language-dialog.c
@@ -0,0 +1,309 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-spell-language-dialog.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-help.h>
+#include "gedit-spell-language-dialog.h"
+#include "gedit-spell-checker-language.h"
+
+
+enum
+{
+ COLUMN_LANGUAGE_NAME = 0,
+ COLUMN_LANGUAGE_POINTER,
+ ENCODING_NUM_COLS
+};
+
+
+struct _GeditSpellLanguageDialog
+{
+ GtkDialog dialog;
+
+ GtkWidget *languages_treeview;
+ GtkTreeModel *model;
+};
+
+G_DEFINE_TYPE(GeditSpellLanguageDialog, gedit_spell_language_dialog, GTK_TYPE_DIALOG)
+
+
+static void
+gedit_spell_language_dialog_class_init (GeditSpellLanguageDialogClass *klass)
+{
+ /* GObjectClass *object_class = G_OBJECT_CLASS (klass); */
+}
+
+static void
+dialog_response_handler (GtkDialog *dlg,
+ gint res_id)
+{
+ if (res_id == GTK_RESPONSE_HELP)
+ {
+ gedit_help_display (GTK_WINDOW (dlg),
+ NULL,
+ "gedit-spell-checker-plugin");
+
+ g_signal_stop_emission_by_name (dlg, "response");
+ }
+}
+
+static void
+scroll_to_selected (GtkTreeView *tree_view)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ model = gtk_tree_view_get_model (tree_view);
+ g_return_if_fail (model != NULL);
+
+ /* Scroll to selected */
+ selection = gtk_tree_view_get_selection (tree_view);
+ g_return_if_fail (selection != NULL);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ GtkTreePath* path;
+
+ path = gtk_tree_model_get_path (model, &iter);
+ g_return_if_fail (path != NULL);
+
+ gtk_tree_view_scroll_to_cell (tree_view,
+ path, NULL, TRUE, 1.0, 0.0);
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+language_row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GeditSpellLanguageDialog *dialog)
+{
+ gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+}
+
+static void
+create_dialog (GeditSpellLanguageDialog *dlg,
+ const gchar *data_dir)
+{
+ GtkWidget *error_widget;
+ GtkWidget *content;
+ gboolean ret;
+ GtkCellRenderer *cell;
+ GtkTreeViewColumn *column;
+ gchar *ui_file;
+ gchar *root_objects[] = {
+ "content",
+ NULL
+ };
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dlg),
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_OK,
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP,
+ NULL);
+
+ gtk_window_set_title (GTK_WINDOW (dlg), _("Set language"));
+ gtk_dialog_set_has_separator (GTK_DIALOG (dlg), FALSE);
+ gtk_window_set_modal (GTK_WINDOW (dlg), TRUE);
+ gtk_window_set_destroy_with_parent (GTK_WINDOW (dlg), TRUE);
+
+ /* HIG defaults */
+ gtk_container_set_border_width (GTK_CONTAINER (dlg), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ 2); /* 2 * 5 + 2 = 12 */
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dlg))),
+ 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dlg))),
+ 6);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (dialog_response_handler),
+ NULL);
+
+ ui_file = g_build_filename (data_dir, "languages-dialog.ui", NULL);
+ ret = gedit_utils_get_ui_objects (ui_file,
+ root_objects,
+ &error_widget,
+ "content", &content,
+ "languages_treeview", &dlg->languages_treeview,
+ NULL);
+ g_free (ui_file);
+
+ if (!ret)
+ {
+ gtk_widget_show (error_widget);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ error_widget,
+ TRUE, TRUE, 0);
+
+ return;
+ }
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dlg))),
+ content, TRUE, TRUE, 0);
+ g_object_unref (content);
+ gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+
+ dlg->model = GTK_TREE_MODEL (gtk_list_store_new (ENCODING_NUM_COLS,
+ G_TYPE_STRING,
+ G_TYPE_POINTER));
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (dlg->languages_treeview),
+ dlg->model);
+
+ g_object_unref (dlg->model);
+
+ /* Add the encoding column */
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Languages"),
+ cell,
+ "text",
+ COLUMN_LANGUAGE_NAME,
+ NULL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (dlg->languages_treeview),
+ column);
+
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (dlg->languages_treeview),
+ COLUMN_LANGUAGE_NAME);
+
+ g_signal_connect (dlg->languages_treeview,
+ "realize",
+ G_CALLBACK (scroll_to_selected),
+ dlg);
+ g_signal_connect (dlg->languages_treeview,
+ "row-activated",
+ G_CALLBACK (language_row_activated),
+ dlg);
+}
+
+static void
+gedit_spell_language_dialog_init (GeditSpellLanguageDialog *dlg)
+{
+
+}
+
+static void
+populate_language_list (GeditSpellLanguageDialog *dlg,
+ const GeditSpellCheckerLanguage *cur_lang)
+{
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ const GSList* langs;
+
+ /* create list store */
+ store = GTK_LIST_STORE (dlg->model);
+
+ langs = gedit_spell_checker_get_available_languages ();
+
+ while (langs)
+ {
+ const gchar *name;
+
+ name = gedit_spell_checker_language_to_string ((const GeditSpellCheckerLanguage*)langs->data);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_LANGUAGE_NAME, name,
+ COLUMN_LANGUAGE_POINTER, langs->data,
+ -1);
+
+ if (langs->data == cur_lang)
+ {
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->languages_treeview));
+ g_return_if_fail (selection != NULL);
+
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+
+ langs = g_slist_next (langs);
+ }
+}
+
+GtkWidget *
+gedit_spell_language_dialog_new (GtkWindow *parent,
+ const GeditSpellCheckerLanguage *cur_lang,
+ const gchar *data_dir)
+{
+ GeditSpellLanguageDialog *dlg;
+
+ g_return_val_if_fail (GTK_IS_WINDOW (parent), NULL);
+
+ dlg = g_object_new (GEDIT_TYPE_SPELL_LANGUAGE_DIALOG, NULL);
+
+ create_dialog (dlg, data_dir);
+
+ populate_language_list (dlg, cur_lang);
+
+ gtk_window_set_transient_for (GTK_WINDOW (dlg), parent);
+ gtk_widget_grab_focus (dlg->languages_treeview);
+
+ return GTK_WIDGET (dlg);
+}
+
+const GeditSpellCheckerLanguage *
+gedit_spell_language_get_selected_language (GeditSpellLanguageDialog *dlg)
+{
+ GValue value = {0, };
+ const GeditSpellCheckerLanguage* lang;
+
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dlg->languages_treeview));
+ g_return_val_if_fail (selection != NULL, NULL);
+
+ if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
+ return NULL;
+
+ gtk_tree_model_get_value (dlg->model,
+ &iter,
+ COLUMN_LANGUAGE_POINTER,
+ &value);
+
+ lang = (const GeditSpellCheckerLanguage* ) g_value_get_pointer (&value);
+
+ return lang;
+}
+
diff --git a/plugins/spell/gedit-spell-language-dialog.h b/plugins/spell/gedit-spell-language-dialog.h
new file mode 100755
index 00000000..4ae9c97d
--- /dev/null
+++ b/plugins/spell/gedit-spell-language-dialog.h
@@ -0,0 +1,67 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gedit-spell-language-dialog.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2002 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifndef __GEDIT_SPELL_LANGUAGE_DIALOG_H__
+#define __GEDIT_SPELL_LANGUAGE_DIALOG_H__
+
+#include <gtk/gtk.h>
+#include "gedit-spell-checker-language.h"
+
+G_BEGIN_DECLS
+
+#define GEDIT_TYPE_SPELL_LANGUAGE_DIALOG (gedit_spell_language_dialog_get_type())
+#define GEDIT_SPELL_LANGUAGE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG, GeditSpellLanguageDialog))
+#define GEDIT_SPELL_LANGUAGE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG, GeditSpellLanguageDialogClass))
+#define GEDIT_IS_SPELL_LANGUAGE_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG))
+#define GEDIT_IS_SPELL_LANGUAGE_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG))
+#define GEDIT_SPELL_LANGUAGE_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_SPELL_LANGUAGE_DIALOG, GeditSpellLanguageDialogClass))
+
+
+typedef struct _GeditSpellLanguageDialog GeditSpellLanguageDialog;
+
+typedef struct _GeditSpellLanguageDialogClass GeditSpellLanguageDialogClass;
+
+struct _GeditSpellLanguageDialogClass
+{
+ GtkDialogClass parent_class;
+};
+
+GType gedit_spell_language_dialog_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gedit_spell_language_dialog_new (GtkWindow *parent,
+ const GeditSpellCheckerLanguage *cur_lang,
+ const gchar *data_dir);
+
+const GeditSpellCheckerLanguage *
+ gedit_spell_language_get_selected_language (GeditSpellLanguageDialog *dlg);
+
+G_END_DECLS
+
+#endif /* __GEDIT_SPELL_LANGUAGE_DIALOG_H__ */
+
diff --git a/plugins/spell/gedit-spell-marshal.list b/plugins/spell/gedit-spell-marshal.list
new file mode 100755
index 00000000..007dcf7d
--- /dev/null
+++ b/plugins/spell/gedit-spell-marshal.list
@@ -0,0 +1,6 @@
+VOID:STRING
+VOID:STRING,STRING
+VOID:STRING,INT
+VOID:POINTER
+VOID:VOID
+
diff --git a/plugins/spell/gedit-spell-plugin.c b/plugins/spell/gedit-spell-plugin.c
new file mode 100755
index 00000000..6ef78e75
--- /dev/null
+++ b/plugins/spell/gedit-spell-plugin.c
@@ -0,0 +1,1217 @@
+/*
+ * gedit-spell-plugin.c
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gedit-spell-plugin.h"
+#include "gedit-spell-utils.h"
+
+#include <string.h> /* For strlen */
+
+#include <glib/gi18n.h>
+#include <gmodule.h>
+
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-prefs-manager.h>
+#include <gedit/gedit-statusbar.h>
+
+#include "gedit-spell-checker.h"
+#include "gedit-spell-checker-dialog.h"
+#include "gedit-spell-language-dialog.h"
+#include "gedit-automatic-spell-checker.h"
+
+#ifdef G_OS_WIN32
+#include <gedit/gedit-metadata-manager.h>
+#define GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE "spell-language"
+#define GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED "spell-enabled"
+#else
+#define GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE "metadata::gedit-spell-language"
+#define GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED "metadata::gedit-spell-enabled"
+#endif
+
+#define WINDOW_DATA_KEY "GeditSpellPluginWindowData"
+#define MENU_PATH "/MenuBar/ToolsMenu/ToolsOps_1"
+
+#define GEDIT_SPELL_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ GEDIT_TYPE_SPELL_PLUGIN, \
+ GeditSpellPluginPrivate))
+
+GEDIT_PLUGIN_REGISTER_TYPE(GeditSpellPlugin, gedit_spell_plugin)
+
+typedef struct
+{
+ GtkActionGroup *action_group;
+ guint ui_id;
+ guint message_cid;
+ gulong tab_added_id;
+ gulong tab_removed_id;
+} WindowData;
+
+typedef struct
+{
+ GeditPlugin *plugin;
+ GeditWindow *window;
+} ActionData;
+
+static void spell_cb (GtkAction *action, ActionData *action_data);
+static void set_language_cb (GtkAction *action, ActionData *action_data);
+static void auto_spell_cb (GtkAction *action, GeditWindow *window);
+
+/* UI actions. */
+static const GtkActionEntry action_entries[] =
+{
+ { "CheckSpell",
+ GTK_STOCK_SPELL_CHECK,
+ N_("_Check Spelling..."),
+ "<shift>F7",
+ N_("Check the current document for incorrect spelling"),
+ G_CALLBACK (spell_cb)
+ },
+
+ { "ConfigSpell",
+ NULL,
+ N_("Set _Language..."),
+ NULL,
+ N_("Set the language of the current document"),
+ G_CALLBACK (set_language_cb)
+ }
+};
+
+static const GtkToggleActionEntry toggle_action_entries[] =
+{
+ { "AutoSpell",
+ NULL,
+ N_("_Autocheck Spelling"),
+ NULL,
+ N_("Automatically spell-check the current document"),
+ G_CALLBACK (auto_spell_cb),
+ FALSE
+ }
+};
+
+typedef struct _CheckRange CheckRange;
+
+struct _CheckRange
+{
+ GtkTextMark *start_mark;
+ GtkTextMark *end_mark;
+
+ gint mw_start; /* misspelled word start */
+ gint mw_end; /* end */
+
+ GtkTextMark *current_mark;
+};
+
+static GQuark spell_checker_id = 0;
+static GQuark check_range_id = 0;
+
+static void
+gedit_spell_plugin_init (GeditSpellPlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSpellPlugin initializing");
+}
+
+static void
+gedit_spell_plugin_finalize (GObject *object)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditSpellPlugin finalizing");
+
+ G_OBJECT_CLASS (gedit_spell_plugin_parent_class)->finalize (object);
+}
+
+static void
+set_spell_language_cb (GeditSpellChecker *spell,
+ const GeditSpellCheckerLanguage *lang,
+ GeditDocument *doc)
+{
+ const gchar *key;
+
+ g_return_if_fail (GEDIT_IS_DOCUMENT (doc));
+ g_return_if_fail (lang != NULL);
+
+ key = gedit_spell_checker_language_to_key (lang);
+ g_return_if_fail (key != NULL);
+
+ gedit_document_set_metadata (doc, GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE,
+ key, NULL);
+}
+
+static void
+set_language_from_metadata (GeditSpellChecker *spell,
+ GeditDocument *doc)
+{
+ const GeditSpellCheckerLanguage *lang = NULL;
+ gchar *value = NULL;
+
+ value = gedit_document_get_metadata (doc, GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE);
+
+ if (value != NULL)
+ {
+ lang = gedit_spell_checker_language_from_key (value);
+ g_free (value);
+ }
+
+ if (lang != NULL)
+ {
+ g_signal_handlers_block_by_func (spell, set_spell_language_cb, doc);
+ gedit_spell_checker_set_language (spell, lang);
+ g_signal_handlers_unblock_by_func (spell, set_spell_language_cb, doc);
+ }
+}
+
+static GeditSpellChecker *
+get_spell_checker_from_document (GeditDocument *doc)
+{
+ GeditSpellChecker *spell;
+ gpointer data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (doc != NULL, NULL);
+
+ data = g_object_get_qdata (G_OBJECT (doc), spell_checker_id);
+
+ if (data == NULL)
+ {
+ spell = gedit_spell_checker_new ();
+
+ set_language_from_metadata (spell, doc);
+
+ g_object_set_qdata_full (G_OBJECT (doc),
+ spell_checker_id,
+ spell,
+ (GDestroyNotify) g_object_unref);
+
+ g_signal_connect (spell,
+ "set_language",
+ G_CALLBACK (set_spell_language_cb),
+ doc);
+ }
+ else
+ {
+ g_return_val_if_fail (GEDIT_IS_SPELL_CHECKER (data), NULL);
+ spell = GEDIT_SPELL_CHECKER (data);
+ }
+
+ return spell;
+}
+
+static CheckRange *
+get_check_range (GeditDocument *doc)
+{
+ CheckRange *range;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (doc != NULL, NULL);
+
+ range = (CheckRange *) g_object_get_qdata (G_OBJECT (doc), check_range_id);
+
+ return range;
+}
+
+static void
+update_current (GeditDocument *doc,
+ gint current)
+{
+ CheckRange *range;
+ GtkTextIter iter;
+ GtkTextIter end_iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (doc != NULL);
+ g_return_if_fail (current >= 0);
+
+ range = get_check_range (doc);
+ g_return_if_fail (range != NULL);
+
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc),
+ &iter, current);
+
+ if (!gtk_text_iter_inside_word (&iter))
+ {
+ /* if we're not inside a word,
+ * we must be in some spaces.
+ * skip forward to the beginning of the next word. */
+ if (!gtk_text_iter_is_end (&iter))
+ {
+ gtk_text_iter_forward_word_end (&iter);
+ gtk_text_iter_backward_word_start (&iter);
+ }
+ }
+ else
+ {
+ if (!gtk_text_iter_starts_word (&iter))
+ gtk_text_iter_backward_word_start (&iter);
+ }
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc),
+ &end_iter,
+ range->end_mark);
+
+ if (gtk_text_iter_compare (&end_iter, &iter) < 0)
+ {
+ gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc),
+ range->current_mark,
+ &end_iter);
+ }
+ else
+ {
+ gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc),
+ range->current_mark,
+ &iter);
+ }
+}
+
+static void
+set_check_range (GeditDocument *doc,
+ GtkTextIter *start,
+ GtkTextIter *end)
+{
+ CheckRange *range;
+ GtkTextIter iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ range = get_check_range (doc);
+
+ if (range == NULL)
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "There was not a previous check range");
+
+ gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &iter);
+
+ range = g_new0 (CheckRange, 1);
+
+ range->start_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc),
+ "check_range_start_mark", &iter, TRUE);
+
+ range->end_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc),
+ "check_range_end_mark", &iter, FALSE);
+
+ range->current_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (doc),
+ "check_range_current_mark", &iter, TRUE);
+
+ g_object_set_qdata_full (G_OBJECT (doc),
+ check_range_id,
+ range,
+ (GDestroyNotify)g_free);
+ }
+
+ if (gedit_spell_utils_skip_no_spell_check (start, end))
+ {
+ if (!gtk_text_iter_inside_word (end))
+ {
+ /* if we're neither inside a word,
+ * we must be in some spaces.
+ * skip backward to the end of the previous word. */
+ if (!gtk_text_iter_is_end (end))
+ {
+ gtk_text_iter_backward_word_start (end);
+ gtk_text_iter_forward_word_end (end);
+ }
+ }
+ else
+ {
+ if (!gtk_text_iter_ends_word (end))
+ gtk_text_iter_forward_word_end (end);
+ }
+ }
+ else
+ {
+ /* no spell checking in the specified range */
+ start = end;
+ }
+
+ gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc),
+ range->start_mark,
+ start);
+ gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (doc),
+ range->end_mark,
+ end);
+
+ range->mw_start = -1;
+ range->mw_end = -1;
+
+ update_current (doc, gtk_text_iter_get_offset (start));
+}
+
+static gchar *
+get_current_word (GeditDocument *doc, gint *start, gint *end)
+{
+ const CheckRange *range;
+ GtkTextIter end_iter;
+ GtkTextIter current_iter;
+ gint range_end;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (doc != NULL, NULL);
+ g_return_val_if_fail (start != NULL, NULL);
+ g_return_val_if_fail (end != NULL, NULL);
+
+ range = get_check_range (doc);
+ g_return_val_if_fail (range != NULL, NULL);
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc),
+ &end_iter, range->end_mark);
+
+ range_end = gtk_text_iter_get_offset (&end_iter);
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc),
+ &current_iter, range->current_mark);
+
+ end_iter = current_iter;
+
+ if (!gtk_text_iter_is_end (&end_iter))
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "Current is not end");
+
+ gtk_text_iter_forward_word_end (&end_iter);
+ }
+
+ *start = gtk_text_iter_get_offset (&current_iter);
+ *end = MIN (gtk_text_iter_get_offset (&end_iter), range_end);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Current word extends [%d, %d]", *start, *end);
+
+ if (!(*start < *end))
+ return NULL;
+
+ return gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc),
+ &current_iter,
+ &end_iter,
+ TRUE);
+}
+
+static gboolean
+goto_next_word (GeditDocument *doc)
+{
+ CheckRange *range;
+ GtkTextIter current_iter;
+ GtkTextIter old_current_iter;
+ GtkTextIter end_iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (doc != NULL, FALSE);
+
+ range = get_check_range (doc);
+ g_return_val_if_fail (range != NULL, FALSE);
+
+ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (doc),
+ &current_iter,
+ range->current_mark);
+ gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &end_iter);
+
+ old_current_iter = current_iter;
+
+ gtk_text_iter_forward_word_ends (&current_iter, 2);
+ gtk_text_iter_backward_word_start (&current_iter);
+
+ if (gedit_spell_utils_skip_no_spell_check (&current_iter, &end_iter) &&
+ (gtk_text_iter_compare (&old_current_iter, &current_iter) < 0) &&
+ (gtk_text_iter_compare (&current_iter, &end_iter) < 0))
+ {
+ update_current (doc, gtk_text_iter_get_offset (&current_iter));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gchar *
+get_next_misspelled_word (GeditView *view)
+{
+ GeditDocument *doc;
+ CheckRange *range;
+ gint start, end;
+ gchar *word;
+ GeditSpellChecker *spell;
+
+ g_return_val_if_fail (view != NULL, NULL);
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+ g_return_val_if_fail (doc != NULL, NULL);
+
+ range = get_check_range (doc);
+ g_return_val_if_fail (range != NULL, NULL);
+
+ spell = get_spell_checker_from_document (doc);
+ g_return_val_if_fail (spell != NULL, NULL);
+
+ word = get_current_word (doc, &start, &end);
+ if (word == NULL)
+ return NULL;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Word to check: %s", word);
+
+ while (gedit_spell_checker_check_word (spell, word, -1))
+ {
+ g_free (word);
+
+ if (!goto_next_word (doc))
+ return NULL;
+
+ /* may return null if we reached the end of the selection */
+ word = get_current_word (doc, &start, &end);
+ if (word == NULL)
+ return NULL;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Word to check: %s", word);
+ }
+
+ if (!goto_next_word (doc))
+ update_current (doc, gtk_text_buffer_get_char_count (GTK_TEXT_BUFFER (doc)));
+
+ if (word != NULL)
+ {
+ GtkTextIter s, e;
+
+ range->mw_start = start;
+ range->mw_end = end;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Select [%d, %d]", start, end);
+
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &s, start);
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &e, end);
+
+ gtk_text_buffer_select_range (GTK_TEXT_BUFFER (doc), &s, &e);
+
+ gedit_view_scroll_to_cursor (view);
+ }
+ else
+ {
+ range->mw_start = -1;
+ range->mw_end = -1;
+ }
+
+ return word;
+}
+
+static void
+ignore_cb (GeditSpellCheckerDialog *dlg,
+ const gchar *w,
+ GeditView *view)
+{
+ gchar *word = NULL;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (w != NULL);
+ g_return_if_fail (view != NULL);
+
+ word = get_next_misspelled_word (view);
+ if (word == NULL)
+ {
+ gedit_spell_checker_dialog_set_completed (dlg);
+
+ return;
+ }
+
+ gedit_spell_checker_dialog_set_misspelled_word (GEDIT_SPELL_CHECKER_DIALOG (dlg),
+ word,
+ -1);
+
+ g_free (word);
+}
+
+static void
+change_cb (GeditSpellCheckerDialog *dlg,
+ const gchar *word,
+ const gchar *change,
+ GeditView *view)
+{
+ GeditDocument *doc;
+ CheckRange *range;
+ gchar *w = NULL;
+ GtkTextIter start, end;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (view != NULL);
+ g_return_if_fail (word != NULL);
+ g_return_if_fail (change != NULL);
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+ g_return_if_fail (doc != NULL);
+
+ range = get_check_range (doc);
+ g_return_if_fail (range != NULL);
+
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &start, range->mw_start);
+ if (range->mw_end < 0)
+ gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &end);
+ else
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &end, range->mw_end);
+
+ w = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc), &start, &end, TRUE);
+ g_return_if_fail (w != NULL);
+
+ if (strcmp (w, word) != 0)
+ {
+ g_free (w);
+ return;
+ }
+
+ g_free (w);
+
+ gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER(doc));
+
+ gtk_text_buffer_delete (GTK_TEXT_BUFFER (doc), &start, &end);
+ gtk_text_buffer_insert (GTK_TEXT_BUFFER (doc), &start, change, -1);
+
+ gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER(doc));
+
+ update_current (doc, range->mw_start + g_utf8_strlen (change, -1));
+
+ /* go to next misspelled word */
+ ignore_cb (dlg, word, view);
+}
+
+static void
+change_all_cb (GeditSpellCheckerDialog *dlg,
+ const gchar *word,
+ const gchar *change,
+ GeditView *view)
+{
+ GeditDocument *doc;
+ CheckRange *range;
+ gchar *w = NULL;
+ GtkTextIter start, end;
+ gint flags = 0;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (view != NULL);
+ g_return_if_fail (word != NULL);
+ g_return_if_fail (change != NULL);
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+ g_return_if_fail (doc != NULL);
+
+ range = get_check_range (doc);
+ g_return_if_fail (range != NULL);
+
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &start, range->mw_start);
+ if (range->mw_end < 0)
+ gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &end);
+ else
+ gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (doc), &end, range->mw_end);
+
+ w = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc), &start, &end, TRUE);
+ g_return_if_fail (w != NULL);
+
+ if (strcmp (w, word) != 0)
+ {
+ g_free (w);
+ return;
+ }
+
+ g_free (w);
+
+ GEDIT_SEARCH_SET_CASE_SENSITIVE (flags, TRUE);
+ GEDIT_SEARCH_SET_ENTIRE_WORD (flags, TRUE);
+
+ /* CHECK: currently this function does escaping etc */
+ gedit_document_replace_all (doc, word, change, flags);
+
+ update_current (doc, range->mw_start + g_utf8_strlen (change, -1));
+
+ /* go to next misspelled word */
+ ignore_cb (dlg, word, view);
+}
+
+static void
+add_word_cb (GeditSpellCheckerDialog *dlg,
+ const gchar *word,
+ GeditView *view)
+{
+ g_return_if_fail (view != NULL);
+ g_return_if_fail (word != NULL);
+
+ /* go to next misspelled word */
+ ignore_cb (dlg, word, view);
+}
+
+static void
+language_dialog_response (GtkDialog *dlg,
+ gint res_id,
+ GeditSpellChecker *spell)
+{
+ if (res_id == GTK_RESPONSE_OK)
+ {
+ const GeditSpellCheckerLanguage *lang;
+
+ lang = gedit_spell_language_get_selected_language (GEDIT_SPELL_LANGUAGE_DIALOG (dlg));
+ if (lang != NULL)
+ gedit_spell_checker_set_language (spell, lang);
+ }
+
+ gtk_widget_destroy (GTK_WIDGET (dlg));
+}
+
+static void
+set_language_cb (GtkAction *action,
+ ActionData *action_data)
+{
+ GeditDocument *doc;
+ GeditSpellChecker *spell;
+ const GeditSpellCheckerLanguage *lang;
+ GtkWidget *dlg;
+ GtkWindowGroup *wg;
+ gchar *data_dir;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ doc = gedit_window_get_active_document (action_data->window);
+ g_return_if_fail (doc != NULL);
+
+ spell = get_spell_checker_from_document (doc);
+ g_return_if_fail (spell != NULL);
+
+ lang = gedit_spell_checker_get_language (spell);
+
+ data_dir = gedit_plugin_get_data_dir (action_data->plugin);
+ dlg = gedit_spell_language_dialog_new (GTK_WINDOW (action_data->window),
+ lang,
+ data_dir);
+ g_free (data_dir);
+
+ wg = gedit_window_get_group (action_data->window);
+
+ gtk_window_group_add_window (wg, GTK_WINDOW (dlg));
+
+ gtk_window_set_modal (GTK_WINDOW (dlg), TRUE);
+
+ g_signal_connect (dlg,
+ "response",
+ G_CALLBACK (language_dialog_response),
+ spell);
+
+ gtk_widget_show (dlg);
+}
+
+static void
+spell_cb (GtkAction *action,
+ ActionData *action_data)
+{
+ GeditView *view;
+ GeditDocument *doc;
+ GeditSpellChecker *spell;
+ GtkWidget *dlg;
+ GtkTextIter start, end;
+ gchar *word;
+ gchar *data_dir;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ view = gedit_window_get_active_view (action_data->window);
+ g_return_if_fail (view != NULL);
+
+ doc = GEDIT_DOCUMENT (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)));
+ g_return_if_fail (doc != NULL);
+
+ spell = get_spell_checker_from_document (doc);
+ g_return_if_fail (spell != NULL);
+
+ if (gtk_text_buffer_get_char_count (GTK_TEXT_BUFFER (doc)) <= 0)
+ {
+ WindowData *data;
+ GtkWidget *statusbar;
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (action_data->window),
+ WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ statusbar = gedit_window_get_statusbar (action_data->window);
+ gedit_statusbar_flash_message (GEDIT_STATUSBAR (statusbar),
+ data->message_cid,
+ _("The document is empty."));
+
+ return;
+ }
+
+ if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc),
+ &start,
+ &end))
+ {
+ /* no selection, get the whole doc */
+ gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc),
+ &start,
+ &end);
+ }
+
+ set_check_range (doc, &start, &end);
+
+ word = get_next_misspelled_word (view);
+ if (word == NULL)
+ {
+ WindowData *data;
+ GtkWidget *statusbar;
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (action_data->window),
+ WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ statusbar = gedit_window_get_statusbar (action_data->window);
+ gedit_statusbar_flash_message (GEDIT_STATUSBAR (statusbar),
+ data->message_cid,
+ _("No misspelled words"));
+
+ return;
+ }
+
+ data_dir = gedit_plugin_get_data_dir (action_data->plugin);
+ dlg = gedit_spell_checker_dialog_new_from_spell_checker (spell, data_dir);
+ g_free (data_dir);
+ gtk_window_set_modal (GTK_WINDOW (dlg), TRUE);
+ gtk_window_set_transient_for (GTK_WINDOW (dlg),
+ GTK_WINDOW (action_data->window));
+
+ g_signal_connect (dlg, "ignore", G_CALLBACK (ignore_cb), view);
+ g_signal_connect (dlg, "ignore_all", G_CALLBACK (ignore_cb), view);
+
+ g_signal_connect (dlg, "change", G_CALLBACK (change_cb), view);
+ g_signal_connect (dlg, "change_all", G_CALLBACK (change_all_cb), view);
+
+ g_signal_connect (dlg, "add_word_to_personal", G_CALLBACK (add_word_cb), view);
+
+ gedit_spell_checker_dialog_set_misspelled_word (GEDIT_SPELL_CHECKER_DIALOG (dlg),
+ word,
+ -1);
+
+ g_free (word);
+
+ gtk_widget_show (dlg);
+}
+
+static void
+set_auto_spell (GeditWindow *window,
+ GeditDocument *doc,
+ gboolean active)
+{
+ GeditAutomaticSpellChecker *autospell;
+ GeditSpellChecker *spell;
+
+ spell = get_spell_checker_from_document (doc);
+ g_return_if_fail (spell != NULL);
+
+ autospell = gedit_automatic_spell_checker_get_from_document (doc);
+
+ if (active)
+ {
+ if (autospell == NULL)
+ {
+ GeditView *active_view;
+
+ active_view = gedit_window_get_active_view (window);
+ g_return_if_fail (active_view != NULL);
+
+ autospell = gedit_automatic_spell_checker_new (doc, spell);
+ gedit_automatic_spell_checker_attach_view (autospell, active_view);
+ gedit_automatic_spell_checker_recheck_all (autospell);
+ }
+ }
+ else
+ {
+ if (autospell != NULL)
+ gedit_automatic_spell_checker_free (autospell);
+ }
+}
+
+static void
+auto_spell_cb (GtkAction *action,
+ GeditWindow *window)
+{
+
+ GeditDocument *doc;
+ gboolean active;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ active = gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action));
+
+ gedit_debug_message (DEBUG_PLUGINS, active ? "Auto Spell activated" : "Auto Spell deactivated");
+
+ doc = gedit_window_get_active_document (window);
+ if (doc == NULL)
+ return;
+
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED,
+ active ? "1" : NULL, NULL);
+
+ set_auto_spell (window, doc, active);
+}
+
+static void
+free_window_data (WindowData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ g_object_unref (data->action_group);
+ g_slice_free (WindowData, data);
+}
+
+static void
+free_action_data (gpointer data)
+{
+ g_return_if_fail (data != NULL);
+
+ g_slice_free (ActionData, data);
+}
+
+static void
+update_ui_real (GeditWindow *window,
+ WindowData *data)
+{
+ GeditDocument *doc;
+ GeditView *view;
+ gboolean autospell;
+ GtkAction *action;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ doc = gedit_window_get_active_document (window);
+ view = gedit_window_get_active_view (window);
+
+ autospell = (doc != NULL &&
+ gedit_automatic_spell_checker_get_from_document (doc) != NULL);
+
+ if (doc != NULL)
+ {
+ GeditTab *tab;
+ GeditTabState state;
+
+ tab = gedit_window_get_active_tab (window);
+ state = gedit_tab_get_state (tab);
+
+ /* If the document is loading we can't get the metadata so we
+ endup with an useless speller */
+ if (state == GEDIT_TAB_STATE_NORMAL)
+ {
+ action = gtk_action_group_get_action (data->action_group,
+ "AutoSpell");
+
+ g_signal_handlers_block_by_func (action, auto_spell_cb,
+ window);
+ set_auto_spell (window, doc, autospell);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ autospell);
+ g_signal_handlers_unblock_by_func (action, auto_spell_cb,
+ window);
+ }
+ }
+
+ gtk_action_group_set_sensitive (data->action_group,
+ (view != NULL) &&
+ gtk_text_view_get_editable (GTK_TEXT_VIEW (view)));
+}
+
+static void
+set_auto_spell_from_metadata (GeditWindow *window,
+ GeditDocument *doc,
+ GtkActionGroup *action_group)
+{
+ gboolean active = FALSE;
+ gchar *active_str;
+ GeditDocument *active_doc;
+
+ active_str = gedit_document_get_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED);
+
+ if (active_str)
+ {
+ active = *active_str == '1';
+
+ g_free (active_str);
+ }
+
+ set_auto_spell (window, doc, active);
+
+ /* In case that the doc is the active one we mark the spell action */
+ active_doc = gedit_window_get_active_document (window);
+
+ if (active_doc == doc && action_group != NULL)
+ {
+ GtkAction *action;
+
+ action = gtk_action_group_get_action (action_group,
+ "AutoSpell");
+
+ g_signal_handlers_block_by_func (action, auto_spell_cb,
+ window);
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ active);
+ g_signal_handlers_unblock_by_func (action, auto_spell_cb,
+ window);
+ }
+}
+
+static void
+on_document_loaded (GeditDocument *doc,
+ const GError *error,
+ GeditWindow *window)
+{
+ if (error == NULL)
+ {
+ WindowData *data;
+ GeditSpellChecker *spell;
+
+ spell = GEDIT_SPELL_CHECKER (g_object_get_qdata (G_OBJECT (doc),
+ spell_checker_id));
+ if (spell != NULL)
+ {
+ set_language_from_metadata (spell, doc);
+ }
+
+ data = g_object_get_data (G_OBJECT (window),
+ WINDOW_DATA_KEY);
+
+ set_auto_spell_from_metadata (window, doc, data->action_group);
+ }
+}
+
+static void
+on_document_saved (GeditDocument *doc,
+ const GError *error,
+ GeditWindow *window)
+{
+ GeditAutomaticSpellChecker *autospell;
+ GeditSpellChecker *spell;
+ const gchar *key;
+
+ if (error != NULL)
+ {
+ return;
+ }
+
+ /* Make sure to save the metadata here too */
+ autospell = gedit_automatic_spell_checker_get_from_document (doc);
+ spell = GEDIT_SPELL_CHECKER (g_object_get_qdata (G_OBJECT (doc), spell_checker_id));
+
+ if (spell != NULL)
+ {
+ key = gedit_spell_checker_language_to_key (gedit_spell_checker_get_language (spell));
+ }
+ else
+ {
+ key = NULL;
+ }
+
+ gedit_document_set_metadata (doc,
+ GEDIT_METADATA_ATTRIBUTE_SPELL_ENABLED,
+ autospell != NULL ? "1" : NULL,
+ GEDIT_METADATA_ATTRIBUTE_SPELL_LANGUAGE,
+ key,
+ NULL);
+}
+
+static void
+tab_added_cb (GeditWindow *window,
+ GeditTab *tab,
+ gpointer useless)
+{
+ GeditDocument *doc;
+ GeditView *view;
+
+ doc = gedit_tab_get_document (tab);
+ view = gedit_tab_get_view (tab);
+
+ g_signal_connect (doc, "loaded",
+ G_CALLBACK (on_document_loaded),
+ window);
+
+ g_signal_connect (doc, "saved",
+ G_CALLBACK (on_document_saved),
+ window);
+}
+
+static void
+tab_removed_cb (GeditWindow *window,
+ GeditTab *tab,
+ gpointer useless)
+{
+ GeditDocument *doc;
+ GeditView *view;
+
+ doc = gedit_tab_get_document (tab);
+ view = gedit_tab_get_view (tab);
+
+ g_signal_handlers_disconnect_by_func (doc, on_document_loaded, window);
+ g_signal_handlers_disconnect_by_func (doc, on_document_saved, window);
+}
+
+static void
+impl_activate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+ ActionData *action_data;
+ GList *docs, *l;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = g_slice_new (WindowData);
+ action_data = g_slice_new (ActionData);
+ action_data->plugin = plugin;
+ action_data->window = window;
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data->action_group = gtk_action_group_new ("GeditSpellPluginActions");
+ gtk_action_group_set_translation_domain (data->action_group,
+ GETTEXT_PACKAGE);
+ gtk_action_group_add_actions_full (data->action_group,
+ action_entries,
+ G_N_ELEMENTS (action_entries),
+ action_data,
+ (GDestroyNotify) free_action_data);
+ gtk_action_group_add_toggle_actions (data->action_group,
+ toggle_action_entries,
+ G_N_ELEMENTS (toggle_action_entries),
+ window);
+
+ gtk_ui_manager_insert_action_group (manager, data->action_group, -1);
+
+ data->ui_id = gtk_ui_manager_new_merge_id (manager);
+
+ data->message_cid = gtk_statusbar_get_context_id
+ (GTK_STATUSBAR (gedit_window_get_statusbar (window)),
+ "spell_plugin_message");
+
+ g_object_set_data_full (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ data,
+ (GDestroyNotify) free_window_data);
+
+ gtk_ui_manager_add_ui (manager,
+ data->ui_id,
+ MENU_PATH,
+ "CheckSpell",
+ "CheckSpell",
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ gtk_ui_manager_add_ui (manager,
+ data->ui_id,
+ MENU_PATH,
+ "AutoSpell",
+ "AutoSpell",
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ gtk_ui_manager_add_ui (manager,
+ data->ui_id,
+ MENU_PATH,
+ "ConfigSpell",
+ "ConfigSpell",
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ update_ui_real (window, data);
+
+ docs = gedit_window_get_documents (window);
+ for (l = docs; l != NULL; l = g_list_next (l))
+ {
+ GeditDocument *doc = GEDIT_DOCUMENT (l->data);
+
+ set_auto_spell_from_metadata (window, doc,
+ data->action_group);
+
+ g_signal_handlers_disconnect_by_func (doc,
+ on_document_loaded,
+ window);
+
+ g_signal_handlers_disconnect_by_func (doc,
+ on_document_saved,
+ window);
+ }
+
+ data->tab_added_id =
+ g_signal_connect (window, "tab-added",
+ G_CALLBACK (tab_added_cb), NULL);
+ data->tab_removed_id =
+ g_signal_connect (window, "tab-removed",
+ G_CALLBACK (tab_removed_cb), NULL);
+}
+
+static void
+impl_deactivate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ gtk_ui_manager_remove_ui (manager, data->ui_id);
+ gtk_ui_manager_remove_action_group (manager, data->action_group);
+
+ g_signal_handler_disconnect (window, data->tab_added_id);
+ g_signal_handler_disconnect (window, data->tab_removed_id);
+
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL);
+}
+
+static void
+impl_update_ui (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ update_ui_real (window, data);
+}
+
+static void
+gedit_spell_plugin_class_init (GeditSpellPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ object_class->finalize = gedit_spell_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+ plugin_class->update_ui = impl_update_ui;
+
+ if (spell_checker_id == 0)
+ spell_checker_id = g_quark_from_string ("GeditSpellCheckerID");
+
+ if (check_range_id == 0)
+ check_range_id = g_quark_from_string ("CheckRangeID");
+}
diff --git a/plugins/spell/gedit-spell-plugin.h b/plugins/spell/gedit-spell-plugin.h
new file mode 100755
index 00000000..7de5807a
--- /dev/null
+++ b/plugins/spell/gedit-spell-plugin.h
@@ -0,0 +1,75 @@
+/*
+ * gedit-spell-plugin.h
+ *
+ * Copyright (C) 2002-2005 Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifndef __GEDIT_SPELL_PLUGIN_H__
+#define __GEDIT_SPELL_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_SPELL_PLUGIN (gedit_spell_plugin_get_type ())
+#define GEDIT_SPELL_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPlugin))
+#define GEDIT_SPELL_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPluginClass))
+#define GEDIT_IS_SPELL_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_SPELL_PLUGIN))
+#define GEDIT_IS_SPELL_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_SPELL_PLUGIN))
+#define GEDIT_SPELL_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_SPELL_PLUGIN, GeditSpellPluginClass))
+
+/* Private structure type */
+typedef struct _GeditSpellPluginPrivate GeditSpellPluginPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditSpellPlugin GeditSpellPlugin;
+
+struct _GeditSpellPlugin
+{
+ GeditPlugin parent_instance;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditSpellPluginClass GeditSpellPluginClass;
+
+struct _GeditSpellPluginClass
+{
+ GeditPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_spell_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_SPELL_PLUGIN_H__ */
diff --git a/plugins/spell/gedit-spell-utils.c b/plugins/spell/gedit-spell-utils.c
new file mode 100755
index 00000000..0782d37a
--- /dev/null
+++ b/plugins/spell/gedit-spell-utils.c
@@ -0,0 +1,94 @@
+/*
+ * gedit-spell-utils.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#include <string.h>
+
+#include "gedit-spell-utils.h"
+#include <gtksourceview/gtksourcebuffer.h>
+
+gboolean
+gedit_spell_utils_is_digit (const char *text, gssize length)
+{
+ gunichar c;
+ const gchar *p;
+ const gchar *end;
+
+ g_return_val_if_fail (text != NULL, FALSE);
+
+ if (length < 0)
+ length = strlen (text);
+
+ p = text;
+ end = text + length;
+
+ while (p != end) {
+ const gchar *next;
+ next = g_utf8_next_char (p);
+
+ c = g_utf8_get_char (p);
+
+ if (!g_unichar_isdigit (c) && c != '.' && c != ',')
+ return FALSE;
+
+ p = next;
+ }
+
+ return TRUE;
+}
+
+gboolean
+gedit_spell_utils_skip_no_spell_check (GtkTextIter *start,
+ GtkTextIter *end)
+{
+ GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (gtk_text_iter_get_buffer (start));
+
+ while (gtk_source_buffer_iter_has_context_class (buffer, start, "no-spell-check"))
+ {
+ GtkTextIter last = *start;
+
+ if (!gtk_source_buffer_iter_forward_to_context_class_toggle (buffer, start, "no-spell-check"))
+ {
+ return FALSE;
+ }
+
+ if (gtk_text_iter_compare (start, &last) <= 0)
+ {
+ return FALSE;
+ }
+
+ gtk_text_iter_forward_word_end (start);
+ gtk_text_iter_backward_word_start (start);
+
+ if (gtk_text_iter_compare (start, &last) <= 0)
+ {
+ return FALSE;
+ }
+
+ if (gtk_text_iter_compare (start, end) >= 0)
+ {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
diff --git a/plugins/spell/gedit-spell-utils.h b/plugins/spell/gedit-spell-utils.h
new file mode 100755
index 00000000..fbfe4b1b
--- /dev/null
+++ b/plugins/spell/gedit-spell-utils.h
@@ -0,0 +1,37 @@
+/*
+ * gedit-spell-utils.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301 USA
+ */
+
+#ifndef __GEDIT_SPELL_UTILS_H__
+#define __GEDIT_SPELL_UTILS_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+gboolean gedit_spell_utils_is_digit (const char *text, gssize length);
+
+gboolean gedit_spell_utils_skip_no_spell_check (GtkTextIter *start, GtkTextIter *end);
+
+G_END_DECLS
+
+#endif /* __GEDIT_SPELL_UTILS_H__ */
+
diff --git a/plugins/spell/languages-dialog.ui b/plugins/spell/languages-dialog.ui
new file mode 100755
index 00000000..8ceb7056
--- /dev/null
+++ b/plugins/spell/languages-dialog.ui
@@ -0,0 +1,145 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkDialog" id="dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Set language</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">True</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="helpbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="closebutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="content">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">11</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Select the _language of the current document.</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">True</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">languages_treeview</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="languages_treeview">
+ <property name="height_request">180</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ <property name="fixed_height_mode">False</property>
+ <property name="hover_selection">False</property>
+ <property name="hover_expand">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">helpbutton1</action-widget>
+ <action-widget response="-6">closebutton1</action-widget>
+ <action-widget response="-5">button1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/spell/spell-checker.ui b/plugins/spell/spell-checker.ui
new file mode 100755
index 00000000..ea84290f
--- /dev/null
+++ b/plugins/spell/spell-checker.ui
@@ -0,0 +1,482 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <object class="GtkImage" id="check_word_image">
+ <property name="stock">gtk-spell-check</property>
+ <property name="icon_size">4</property>
+ </object>
+ <object class="GtkImage" id="add_word_image">
+ <property name="stock">gtk-add</property>
+ <property name="icon_size">4</property>
+ </object>
+ <object class="GtkImage" id="ignore_image">
+ <property name="stock">gtk-go-down</property>
+ <property name="icon_size">4</property>
+ </object>
+ <object class="GtkImage" id="change_image">
+ <property name="stock">gtk-convert</property>
+ <property name="icon_size">4</property>
+ </object>
+ <object class="GtkImage" id="ignore_all_image">
+ <property name="stock">gtk-goto-bottom</property>
+ <property name="icon_size">4</property>
+ </object>
+ <object class="GtkImage" id="change_all_image">
+ <property name="stock">gtk-convert</property>
+ <property name="icon_size">4</property>
+ </object>
+ <object class="GtkWindow" id="check_spelling_window">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">Check spelling</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <child>
+ <object class="GtkVBox" id="content">
+ <property name="border_width">12</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Misspelled word:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="misspelled_word_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">word</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <attributes>
+ <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Change _to:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">word_entry</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkEntry" id="word_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"/>
+ <property name="has_frame">True</property>
+ <property name="activates_default">False</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="check_word_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="image">check_word_image</property>
+ <property name="label" translatable="yes">Check _Word</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkTable" id="table2">
+ <property name="visible">True</property>
+ <property name="n_rows">3</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Suggestions:</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_CENTER</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="mnemonic_widget">suggestions_list</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="x_options">fill</property>
+ <property name="y_options"/>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_ETCHED_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="suggestions_list">
+ <property name="width_request">200</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkTable" id="table3">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">2</property>
+ <property name="homogeneous">True</property>
+ <property name="row_spacing">12</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkButton" id="ignore_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="image">ignore_image</property>
+ <property name="label" translatable="yes">_Ignore</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">expand</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="change_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="image">change_image</property>
+ <property name="label" translatable="yes">Cha_nge</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">expand</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ignore_all_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="image">ignore_all_image</property>
+ <property name="label" translatable="yes">Ignore _All</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">0</property>
+ <property name="bottom_attach">1</property>
+ <property name="y_options">expand</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="change_all_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="image">change_all_image</property>
+ <property name="label" translatable="yes">Change A_ll</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options">expand</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">11</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">User dictionary:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">7.45058e-09</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">True</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkButton" id="add_word_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="image">add_word_image</property>
+ <property name="label" translatable="yes">Add w_ord</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox32">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel" id="label44">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Language:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="language_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Language</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">True</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <attributes>
+ <attribute name="weight" value="PANGO_WEIGHT_BOLD"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="right_attach">1</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkButton" id="close_button">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-close</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options">fill</property>
+ <property name="y_options">fill</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/plugins/spell/spell.gedit-plugin.desktop.in b/plugins/spell/spell.gedit-plugin.desktop.in
new file mode 100755
index 00000000..c75c9eee
--- /dev/null
+++ b/plugins/spell/spell.gedit-plugin.desktop.in
@@ -0,0 +1,9 @@
+[Gedit Plugin]
+Module=spell
+IAge=2
+_Name=Spell Checker
+_Description=Checks the spelling of the current document.
+Icon=gtk-spell-check
+Authors=Paolo Maggi <[email protected]>
+Copyright=Copyright © 2002-2005 Paolo Maggi
+Website=http://www.gedit.org
diff --git a/plugins/taglist/HTML.tags.xml.in b/plugins/taglist/HTML.tags.xml.in
new file mode 100755
index 00000000..116cc247
--- /dev/null
+++ b/plugins/taglist/HTML.tags.xml.in
@@ -0,0 +1,2672 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TagList xmlns="http://gedit.sourceforge.net/some-location">
+<TagGroup _name="XHTML 1.0 - Tags" sort="true">
+
+ <Tag _name="Abbreviated form">
+ <Begin>&lt;abbr title="</Begin>
+ <End>"&gt;&lt;/abbr&gt;</End>
+ </Tag>
+
+ <Tag _name="Abbreviation">
+ <Begin>abbr="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Accessibility key character">
+ <Begin>accesskey="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Acronym">
+ <Begin>&lt;acronym title="</Begin>
+ <End>"&gt;&lt;/acronym&gt;</End>
+ </Tag>
+
+ <Tag _name="Align">
+ <Begin>align="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Alignment character">
+ <Begin>char="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Alternative">
+ <Begin>alt="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Anchor URI">
+ <Begin>&lt;a href="</Begin>
+ <End>"&gt;&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Anchor">
+ <Begin>&lt;a id="</Begin>
+ <End>"&gt;&lt;/a&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Applet class file code (deprecated)">
+ <Begin>code="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Associated information">
+ <Begin>content="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Author info">
+ <Begin>&lt;address&gt;</Begin>
+ <End>&lt;/address&gt;</End>
+ </Tag>
+
+ <Tag _name="Axis related headers">
+ <Begin>axis="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Background color (deprecated)">
+ <Begin>bgcolor="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Background texture tile (deprecated)">
+ <Begin>background="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Base font (deprecated)">
+ <Begin>&lt;basefont&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Base URI">
+ <Begin>codebase="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Bold">
+ <Begin>&lt;b&gt;</Begin>
+ <End>&lt;/b&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Border (deprecated)">
+ <Begin>border="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Cell rowspan">
+ <Begin>rowspan="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Center (deprecated)">
+ <Begin>&lt;center&gt;</Begin>
+ <End>&lt;/center&gt;</End>
+ </Tag>
+
+ <Tag _name="Character encoding of linked resource">
+ <Begin>charset="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Checked state">
+ <Begin>checked="checked"</Begin>
+ </Tag>
+
+ <Tag _name="Citation">
+ <Begin>&lt;cite&gt;</Begin>
+ <End>&lt;/cite&gt;</End>
+ </Tag>
+
+ <Tag _name="Cite reason for change">
+ <Begin>cite="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Class implementation ID">
+ <Begin>classid="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Class list">
+ <Begin>class="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Clear text flow control">
+ <Begin>clear="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Code content type">
+ <Begin>codetype="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Color of selected links (deprecated)">
+ <Begin>alink="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Column span">
+ <Begin>colspan="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Columns">
+ <Begin>cols="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Comment">
+ <Begin>&lt;!-- </Begin>
+ <End> --&gt;</End>
+ </Tag>
+
+ <Tag _name="Computer code fragment">
+ <Begin>&lt;code&gt;</Begin>
+ <End>&lt;/code&gt;</End>
+ </Tag>
+
+ <!-- The "type" attribute is deprecated for the "ol" tag only,
+ since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Content type (deprecated)">
+ <Begin>type="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Coordinates">
+ <Begin>coords="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Date and time of change">
+ <Begin>datetime="YYYYMMDD"</Begin>
+ </Tag>
+
+<!-- NOTE: used in "object" tag -->
+ <Tag _name="Declare flag">
+ <Begin>declare="declare"</Begin>
+ </Tag>
+
+ <!-- Translators: DEFER is an optional attribute of the <script> tag.
+ It indicates that the script is not going to generate any document
+ content. The browser can continue parsing and drawing the page. -->
+ <Tag _name="Defer attribute">
+ <Begin>defer="defer"</Begin>
+ </Tag>
+
+ <Tag _name="Definition description">
+ <Begin>&lt;dd&gt;</Begin>
+ <End>&lt;/dd&gt;</End>
+ </Tag>
+
+ <Tag _name="Definition list">
+ <Begin>&lt;dl&gt;</Begin>
+ <End>&lt;/dl&gt;</End>
+ </Tag>
+
+ <Tag _name="Definition term">
+ <Begin>&lt;dt&gt;</Begin>
+ <End>&lt;/dt&gt;</End>
+ </Tag>
+
+ <Tag _name="Deleted text">
+ <Begin>&lt;del&gt;</Begin>
+ <End>&lt;/del&gt;</End>
+ </Tag>
+
+ <Tag _name="Directionality">
+ <Begin>dir="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Directionality (deprecated)">
+ <Begin>&lt;dir&gt;</Begin>
+ <End>&lt;/dir&gt;</End>
+ </Tag>
+
+ <Tag _name="Disabled">
+ <Begin>disabled="disabled"</Begin>
+ </Tag>
+
+ <Tag _name="DIV container">
+ <Begin>&lt;div&gt;</Begin>
+ <End>&lt;/div&gt;</End>
+ </Tag>
+
+ <Tag _name="DIV Style container">
+ <Begin>&lt;div style=&gt;</Begin>
+ <End>&lt;/div&gt;</End>
+ </Tag>
+
+ <Tag _name="Document base">
+ <Begin>&lt;base href="</Begin>
+ <End>" /&gt;</End>
+ </Tag>
+
+ <Tag _name="Document body">
+ <Begin>&lt;body&gt;</Begin>
+ <End>&lt;/body&gt;</End>
+ </Tag>
+
+ <Tag _name="Document head">
+ <Begin>&lt;head&gt;</Begin>
+ <End>&lt;/head&gt;</End>
+ </Tag>
+
+ <Tag _name="Element ID">
+ <Begin>id="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Document title">
+ <Begin>&lt;title&gt;</Begin>
+ <End>&lt;/title&gt;</End>
+ </Tag>
+
+ <Tag _name="Document type">
+ <Begin>&lt;!DOCTYPE&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Emphasis">
+ <Begin>&lt;em&gt;</Begin>
+ <End>&lt;/em&gt;</End>
+ </Tag>
+
+ <Tag _name="Encode type">
+ <Begin>enctype="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Font face (deprecated)">
+ <Begin>face="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="For label">
+ <Begin>for="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Forced line break">
+ <Begin>&lt;br /&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Form action handler">
+ <Begin>action="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Form control group">
+ <Begin>&lt;fieldset&gt;</Begin>
+ <End>&lt;/fieldset&gt;</End>
+ </Tag>
+
+ <Tag _name="Form field label text">
+ <Begin>&lt;label&gt;</Begin>
+ <End>&lt;/label&gt;</End>
+ </Tag>
+
+ <Tag _name="Form input type">
+ <Begin>&lt;input type="</Begin>
+ <End>" /&gt;</End>
+ </Tag>
+
+ <Tag _name="Form input">
+ <Begin>&lt;input /&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Form method">
+ <Begin>&lt;form method="</Begin>
+ <End>"&gt;&lt;/form&gt;</End>
+ </Tag>
+
+ <Tag _name="Form method">
+ <Begin>method="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Form">
+ <Begin>&lt;form&gt;</Begin>
+ <End>&lt;/form&gt;</End>
+ </Tag>
+
+ <Tag _name="Forward link">
+ <Begin>rel="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Frame render parts">
+ <Begin>frame="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Frame source">
+ <Begin>&lt;frame src="</Begin>
+ <End>"&gt;&lt;/frame&gt;</End>
+ </Tag>
+
+ <Tag _name="Frame target">
+ <Begin>target="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Frame">
+ <Begin>&lt;frame&gt;</Begin>
+ <End>&lt;/frame&gt;</End>
+ </Tag>
+
+ <Tag _name="Frame border">
+ <Begin>frameborder="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Frameset columns">
+ <Begin>&lt;frameset cols="</Begin>
+ <End>"&gt;&lt;/frameset&gt;</End>
+ </Tag>
+
+ <Tag _name="Frameset rows">
+ <Begin>&lt;frameset rows="</Begin>
+ <End>"&gt;&lt;/frameset&gt;</End>
+ </Tag>
+
+ <Tag _name="Frameset">
+ <Begin>&lt;frameset&gt;</Begin>
+ <End>&lt;/frameset&gt;</End>
+ </Tag>
+
+ <Tag _name="Frame spacing">
+ <Begin>framespacing="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Generic embedded object">
+ <Begin>&lt;object&gt;</Begin>
+ <End>&lt;/object&gt;</End>
+ </Tag>
+
+ <Tag _name="Generic metainformation">
+ <Begin>&lt;meta content="</Begin>
+ <End>"&gt;&lt;/meta&gt;</End>
+ </Tag>
+
+ <Tag _name="Generic span">
+ <Begin>&lt;span&gt;</Begin>
+ <End>&lt;/span&gt;</End>
+ </Tag>
+
+ <Tag _name="Header cell IDs">
+ <Begin>headers="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Heading 1">
+ <Begin>&lt;h1&gt;</Begin>
+ <End>&lt;/h1&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 2">
+ <Begin>&lt;h2&gt;</Begin>
+ <End>&lt;/h2&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 3">
+ <Begin>&lt;h3&gt;</Begin>
+ <End>&lt;/h3&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 4">
+ <Begin>&lt;h4&gt;</Begin>
+ <End>&lt;/h4&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 5">
+ <Begin>&lt;h5&gt;</Begin>
+ <End>&lt;/h5&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 6">
+ <Begin>&lt;h6&gt;</Begin>
+ <End>&lt;/h6&gt;</End>
+ </Tag>
+
+ <Tag _name="Height">
+ <Begin>height="</Begin>
+ </Tag>
+
+ <Tag _name="Horizontal rule">
+ <Begin>&lt;hr /&gt;</Begin>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Horizontal space (deprecated)">
+ <Begin>hspace="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="HREF URI">
+ <Begin>href="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="HTML root element">
+ <Begin>&lt;html&gt;</Begin>
+ <End>&lt;/html&gt;</End>
+ </Tag>
+
+ <Tag _name="HTTP header name">
+ <Begin>http-equiv="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="I18N BiDi override">
+ <Begin>&lt;bdo&gt;</Begin>
+ <End>&lt;/bdo&gt;</End>
+ </Tag>
+
+ <Tag _name="Image map area">
+ <Begin>&lt;area alt="</Begin>
+ <End>"&gt;&lt;/area&gt;</End>
+ </Tag>
+
+ <Tag _name="Image map name">
+ <Begin>&lt;map name="</Begin>
+ <End>"&gt;&lt;/map&gt;</End>
+ </Tag>
+
+ <Tag _name="Image map">
+ <Begin>&lt;map id="</Begin>
+ <End>"&gt;&lt;/map&gt;</End>
+ </Tag>
+
+ <Tag _name="Image">
+ <Begin>&lt;img id="</Begin>
+ <End>" src="" alt="" /&gt;</End>
+ </Tag>
+
+ <Tag _name="Inline frame">
+ <Begin>&lt;iframe&gt;</Begin>
+ <End>&lt;/iframe&gt;</End>
+ </Tag>
+
+ <Tag _name="Inserted text">
+ <Begin>&lt;ins&gt;</Begin>
+ <End>&lt;/ins&gt;</End>
+ </Tag>
+
+ <Tag _name="Instance definition">
+ <Begin>&lt;dfn&gt;</Begin>
+ <End>&lt;/dfn&gt;</End>
+ </Tag>
+
+ <Tag _name="Italic text">
+ <Begin>&lt;i&gt;</Begin>
+ <End>&lt;/i&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Java applet (deprecated)">
+ <Begin>&lt;applet height="</Begin>
+ <End>" width=""&gt;&lt;/applet&gt;</End>
+ </Tag>
+
+ <Tag _name="Label">
+ <Begin>label="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Language code">
+ <Begin>hreflang="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Language code">
+ <Begin>lang="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Large text style">
+ <Begin>&lt;big&gt;</Begin>
+ <End>&lt;/big&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Link color (deprecated)">
+ <Begin>link="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="List item">
+ <Begin>&lt;li&gt;</Begin>
+ <End>&lt;/li&gt;</End>
+ </Tag>
+
+ <Tag _name="List of MIME types for file upload">
+ <Begin>accept="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="List of supported character sets">
+ <Begin>accept-charset="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Local change to font">
+ <Begin>&lt;font&gt;</Begin>
+ <End>&lt;/font&gt;</End>
+ </Tag>
+
+ <Tag _name="Long description link">
+ <Begin>longdesc="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Long quotation">
+ <Begin>&lt;blockquote&gt;</Begin>
+ <End>&lt;/blockquote&gt;</End>
+ </Tag>
+
+<!-- Supported in XHTML 1.0 Frameset DTD only. -->
+ <Tag _name="Margin pixel height">
+ <Begin>marginheight="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Supported in XHTML 1.0 Frameset DTD only. -->
+ <Tag _name="Margin pixel width">
+ <Begin>marginwidth="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Maximum length of text field">
+ <Begin>maxlength="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Output media">
+ <Begin>media="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <!-- Here I take some liberties: There's no mandatory attributes,
+ but those are most common, and will likely be used. -->
+ <Tag _name="Media-independent link">
+ <Begin>&lt;link rel="</Begin>
+ <End>" type="" href="" /&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Menu list (deprecated)">
+ <Begin>&lt;menu&gt;</Begin>
+ <End>&lt;/menu&gt;</End>
+ </Tag>
+
+ <Tag _name="Multi-line text field">
+ <Begin>&lt;textarea cols="</Begin>
+ <End>" rows=""&gt;&lt;/textarea&gt;</End>
+ </Tag>
+
+ <Tag _name="Multiple">
+ <Begin>multiple="multiple"</Begin>
+ </Tag>
+
+ <Tag _name="Name">
+ <Begin>name="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Named property value">
+ <Begin>&lt;param name="</Begin>
+ <End>"&gt;&lt;/param&gt;</End>
+ </Tag>
+
+ <Tag _name="No frames">
+ <Begin>&lt;noframes&gt;</Begin>
+ <End>&lt;/noframes&gt;</End>
+ </Tag>
+
+ <Tag _name="No resize">
+ <Begin>noresize="noresize"</Begin>
+ </Tag>
+
+ <Tag _name="No script">
+ <Begin>&lt;noscript&gt;</Begin>
+ <End>&lt;/noscript&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="No shade (deprecated)">
+ <Begin>noshade="noshade"</Begin>
+ </Tag>
+
+ <Tag _name="No URI">
+ <Begin>nohref="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="No word wrap (deprecated)">
+ <Begin>nowrap="nowrap"</Begin>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Object applet file (deprecated)">
+ <Begin>object="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Object data reference">
+ <Begin>data="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Offset for alignment character">
+ <Begin>charoff="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnBlur event">
+ <Begin>onblur="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnChange event">
+ <Begin>onchange="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnClick event">
+ <Begin>onclick="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnDblClick event">
+ <Begin>ondbclick="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnFocus event">
+ <Begin>onfocus="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnKeyDown event">
+ <Begin>onkeydown="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnKeyPress event">
+ <Begin>onkeypress="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnKeyUp event">
+ <Begin>onkeyup="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnLoad event">
+ <Begin>onload="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnMouseDown event">
+ <Begin>onmousedown="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnMouseMove event">
+ <Begin>onmousemove="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnMouseOut event">
+ <Begin>onmouseout="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnMouseOver event">
+ <Begin>onmouseover="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnMouseUp event">
+ <Begin>onmouseup="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnReset event">
+ <Begin>onreset="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnSelect event">
+ <Begin>onselect="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnSubmit event">
+ <Begin>onsubmit="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="OnUnload event">
+ <Begin>onunload="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Option group">
+ <Begin>&lt;optgroup label="</Begin>
+ <End>"&gt;&lt;/optgroup&gt;</End>
+ </Tag>
+
+ <Tag _name="Option selector">
+ <Begin>&lt;select&gt;</Begin>
+ <End>&lt;/select&gt;</End>
+ </Tag>
+
+ <Tag _name="Ordered list">
+ <Begin>&lt;ol&gt;</Begin>
+ <End>&lt;/ol&gt;</End>
+ </Tag>
+
+ <Tag _name="Paragraph class">
+ <Begin>&lt;p class="</Begin>
+ <End>"&gt;&lt;/p&gt;</End>
+ </Tag>
+
+ <Tag _name="Paragraph style">
+ <Begin>&lt;p style="</Begin>
+ <End>"&gt;&lt;/p&gt;</End>
+ </Tag>
+
+ <Tag _name="Paragraph">
+ <Begin>&lt;p&gt;</Begin>
+ <End>&lt;/p&gt;</End>
+ </Tag>
+
+ <Tag _name="Preformatted text">
+ <Begin>&lt;pre&gt;</Begin>
+ <End>&lt;/pre&gt;</End>
+ </Tag>
+
+ <Tag _name="Profile metainfo dictionary">
+ <Begin>profile="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Push button">
+ <Begin>&lt;button&gt;</Begin>
+ <End>&lt;/button&gt;</End>
+ </Tag>
+
+ <Tag _name="ReadOnly text and password">
+ <Begin>readonly="readonly"</Begin>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Reduced spacing (deprecated)">
+ <Begin>compact="compact_rendering"</Begin>
+ </Tag>
+
+ <Tag _name="Reverse link">
+ <Begin>rev="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Rows">
+ <Begin>rows="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Rulings between rows and columns">
+ <Begin>rules="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Sample program output, scripts">
+ <Begin>&lt;samp&gt;</Begin>
+ <End>&lt;/samp&gt;</End>
+ </Tag>
+
+ <Tag _name="Scope covered by header cells">
+ <Begin>scope="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Script language name">
+ <Begin>language="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Script statements">
+ <Begin>&lt;script type="</Begin>
+ <End>"&gt;&lt;/script&gt;</End>
+ </Tag>
+
+ <Tag _name="Scrollbar">
+ <Begin>scrolling="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Selectable option">
+ <Begin>&lt;option&gt;</Begin>
+ <End>&lt;/option&gt;</End>
+ </Tag>
+
+ <Tag _name="Selected">
+ <Begin>selected="selected"</Begin>
+ </Tag>
+
+ <Tag _name="Server-side image map">
+ <Begin>ismap="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Shape">
+ <Begin>shape="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Short inline quotation">
+ <Begin>&lt;q&gt;</Begin>
+ <End>&lt;/q&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Size (deprecated)">
+ <Begin>size="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Small text style">
+ <Begin>&lt;small&gt;</Begin>
+ <End>&lt;/small&gt;</End>
+ </Tag>
+
+ <Tag _name="Source">
+ <Begin>src="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Space-separated archive list">
+ <Begin>archive="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Spacing between cells">
+ <Begin>cellspacing="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Spacing within cells">
+ <Begin>cellpadding="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Span">
+ <Begin>span="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Standby load message">
+ <Begin>standby="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Starting sequence number (deprecated)">
+ <Begin>start="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Strike-through text style (deprecated)">
+ <Begin>&lt;s&gt;</Begin>
+ <End>&lt;/s&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Strike-through text (deprecated)">
+ <Begin>&lt;strike&gt;</Begin>
+ <End>&lt;/strike&gt;</End>
+ </Tag>
+
+ <Tag _name="Strong emphasis">
+ <Begin>&lt;strong&gt;</Begin>
+ <End>&lt;/strong&gt;</End>
+ </Tag>
+
+ <Tag _name="Style info">
+ <Begin>&lt;style type="text/css"&gt;</Begin>
+ <End>&lt;/style&gt;</End>
+ </Tag>
+
+ <Tag _name="Style info">
+ <Begin>style="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Subscript">
+ <Begin>&lt;sub&gt;</Begin>
+ <End>&lt;/sub&gt;</End>
+ </Tag>
+
+ <Tag _name="Superscript">
+ <Begin>&lt;sup&gt;</Begin>
+ <End>&lt;/sup&gt;</End>
+ </Tag>
+
+ <Tag _name="Table body">
+ <Begin>&lt;tbody&gt;</Begin>
+ <End>&lt;/tbody&gt;</End>
+ </Tag>
+
+ <Tag _name="Table caption">
+ <Begin>&lt;caption&gt;</Begin>
+ <End>&lt;/caption&gt;</End>
+ </Tag>
+
+ <Tag _name="Table column group properties">
+ <Begin>&lt;colgroup&gt;</Begin>
+ <End>&lt;/colgroup&gt;</End>
+ </Tag>
+
+ <Tag _name="Table column properties">
+ <Begin>&lt;col&gt;</Begin>
+ <End>&lt;/col&gt;</End>
+ </Tag>
+
+ <Tag _name="Table data cell">
+ <Begin>&lt;td&gt;</Begin>
+ <End>&lt;/td&gt;</End>
+ </Tag>
+
+ <Tag _name="Table footer">
+ <Begin>&lt;tfoot&gt;</Begin>
+ <End>&lt;/tfoot&gt;</End>
+ </Tag>
+
+ <Tag _name="Table header cell">
+ <Begin>&lt;th&gt;</Begin>
+ <End>&lt;/th&gt;</End>
+ </Tag>
+
+ <Tag _name="Table header">
+ <Begin>&lt;thead&gt;</Begin>
+ <End>&lt;/thead&gt;</End>
+ </Tag>
+
+ <Tag _name="Table row">
+ <Begin>&lt;tr&gt;</Begin>
+ <End>&lt;/tr&gt;</End>
+ </Tag>
+
+ <Tag _name="Table summary">
+ <Begin>summary="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Table">
+ <Begin>&lt;table&gt;</Begin>
+ <End>&lt;/table&gt;</End>
+ </Tag>
+
+ <Tag _name="Target - Blank">
+ <Begin>&lt;a target="_blank"&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Target - Parent">
+ <Begin>&lt;a target="_parent"&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Target - Self">
+ <Begin>&lt;a target="_self"&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Target - Top">
+ <Begin>&lt;a target="_top"&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Teletype or monospace text style">
+ <Begin>&lt;tt&gt;</Begin>
+ <End>&lt;/tt&gt;</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Text color (deprecated)">
+ <Begin>color="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Text color (deprecated)">
+ <Begin>text="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Text entered by user">
+ <Begin>&lt;kbd&gt;</Begin>
+ <End>&lt;/kbd&gt;</End>
+ </Tag>
+
+ <Tag _name="Title">
+ <Begin>title="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Underlined text style">
+ <Begin>&lt;u&gt;</Begin>
+ <End>&lt;/u&gt;</End>
+ </Tag>
+
+ <Tag _name="Unordered list">
+ <Begin>&lt;ul&gt;</Begin>
+ <End>&lt;/ul&gt;</End>
+ </Tag>
+
+ <Tag _name="Use image map">
+ <Begin>usemap="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Value interpretation">
+ <Begin>valuetype="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Value">
+ <Begin>value="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Variable or program argument">
+ <Begin>&lt;var&gt;</Begin>
+ <End>&lt;/var&gt;</End>
+ </Tag>
+
+ <Tag _name="Vertical cell alignment">
+ <Begin>valign="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Vertical space (deprecated)">
+ <Begin>vspace="</Begin>
+ <End>"</End>
+ </Tag>
+
+<!-- Deprecated since HTML 4.01, not supported in XHTML 1.0 Strict DTD. -->
+ <Tag _name="Visited link color (deprecated)">
+ <Begin>vlink="</Begin>
+ <End>"</End>
+ </Tag>
+
+ <Tag _name="Width">
+ <Begin>width="</Begin>
+ <End>"</End>
+ </Tag>
+
+</TagGroup>
+
+<TagGroup _name="HTML - Tags" sort="true">
+
+ <Tag _name="Abbreviated form">
+ <Begin>&lt;abbr&gt;</Begin>
+ <End>&lt;/abbr&gt;</End>
+ </Tag>
+
+ <Tag _name="Abbreviation">
+ <Begin>abbr=</Begin>
+ </Tag>
+
+ <Tag _name="Above">
+ <Begin>&lt;above&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Accessibility key character">
+ <Begin>accesskey=</Begin>
+ </Tag>
+
+ <Tag _name="Acronym">
+ <Begin>&lt;acronym&gt;</Begin>
+ <End>&lt;/acronym&gt;</End>
+ </Tag>
+
+ <Tag _name="Align">
+ <Begin>align=</Begin>
+ </Tag>
+
+ <Tag _name="Alignment character">
+ <Begin>char=</Begin>
+ </Tag>
+
+ <Tag _name="Alternative">
+ <Begin>alt=</Begin>
+ </Tag>
+
+ <Tag _name="Anchor URI">
+ <Begin>&lt;a href=&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Anchor">
+ <Begin>&lt;a&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Applet class file code">
+ <Begin>code=</Begin>
+ </Tag>
+
+ <Tag _name="Array">
+ <Begin>&lt;array&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Associated information">
+ <Begin>content=</Begin>
+ </Tag>
+
+ <Tag _name="Author info">
+ <Begin>&lt;address&gt;</Begin>
+ <End>&lt;/address&gt;</End>
+ </Tag>
+
+ <Tag _name="Axis related headers">
+ <Begin>axis=</Begin>
+ </Tag>
+
+ <Tag _name="Background color">
+ <Begin>bgcolor=</Begin>
+ </Tag>
+
+ <Tag _name="Background texture tile">
+ <Begin>background=</Begin>
+ </Tag>
+
+ <Tag _name="Base font">
+ <Begin>&lt;basefont&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Base URI">
+ <Begin>codebase=</Begin>
+ </Tag>
+
+ <Tag _name="Bold">
+ <Begin>&lt;b&gt;</Begin>
+ <End>&lt;/b&gt;</End>
+ </Tag>
+
+ <Tag _name="Border color">
+ <Begin>bordercolor=</Begin>
+ </Tag>
+
+ <Tag _name="Border">
+ <Begin>border=</Begin>
+ </Tag>
+
+ <Tag _name="Cell rowspan">
+ <Begin>rowspan=</Begin>
+ </Tag>
+
+ <Tag _name="Center">
+ <Begin>&lt;center&gt;</Begin>
+ <End>&lt;/center&gt;</End>
+ </Tag>
+
+ <Tag _name="Character encoding of linked resource">
+ <Begin>charset=</Begin>
+ </Tag>
+
+ <Tag _name="Checked (state)">
+ <Begin>checked</Begin>
+ </Tag>
+
+ <Tag _name="Citation">
+ <Begin>&lt;cite&gt;</Begin>
+ <End>&lt;/cite&gt;</End>
+ </Tag>
+
+ <Tag _name="Cite reason for change">
+ <Begin>cite=</Begin>
+ </Tag>
+
+ <Tag _name="Class implementation ID">
+ <Begin>classid=</Begin>
+ </Tag>
+
+ <Tag _name="Class list">
+ <Begin>class=</Begin>
+ </Tag>
+
+ <Tag _name="Clear text flow control">
+ <Begin>clear=</Begin>
+ </Tag>
+
+ <Tag _name="Code content type">
+ <Begin>codetype=</Begin>
+ </Tag>
+
+ <Tag _name="Color of selected links">
+ <Begin>alink=</Begin>
+ </Tag>
+
+ <Tag _name="Column span">
+ <Begin>colspan=</Begin>
+ </Tag>
+
+ <Tag _name="Columns">
+ <Begin>cols=</Begin>
+ </Tag>
+
+ <Tag _name="Comment">
+ <Begin>&lt;!-- </Begin>
+ <End> --&gt;</End>
+ </Tag>
+
+ <Tag _name="Comment">
+ <Begin>&lt;comment&gt;</Begin>
+ <End>&lt;/comment&gt;</End>
+ </Tag>
+
+ <Tag _name="Computer code fragment">
+ <Begin>&lt;code&gt;</Begin>
+ <End>&lt;/code&gt;</End>
+ </Tag>
+
+ <Tag _name="Content scheme">
+ <Begin>scheme=</Begin>
+ </Tag>
+
+ <Tag _name="Content type">
+ <Begin>type=</Begin>
+ </Tag>
+
+ <Tag _name="Coordinates">
+ <Begin>coords=</Begin>
+ </Tag>
+
+ <Tag _name="Date and time of change">
+ <Begin>datetime=</Begin>
+ </Tag>
+
+ <Tag _name="Declare flag">
+ <Begin>declare</Begin>
+ </Tag>
+
+ <!-- Translators: DEFER is an optional attribute of the <script> tag.
+ It indicates that the script is not going to generate any document
+ content. The browser can continue parsing and drawing the page. -->
+ <Tag _name="Defer attribute">
+ <Begin>defer</Begin>
+ </Tag>
+
+ <Tag _name="Definition description">
+ <Begin>&lt;dd&gt;</Begin>
+ <End>&lt;/dd&gt;</End>
+ </Tag>
+
+ <Tag _name="Definition list">
+ <Begin>&lt;dl&gt;</Begin>
+ <End>&lt;/dl&gt;</End>
+ </Tag>
+
+ <Tag _name="Definition term">
+ <Begin>&lt;dt&gt;</Begin>
+ <End>&lt;/dt&gt;</End>
+ </Tag>
+
+ <Tag _name="Deleted text">
+ <Begin>&lt;del&gt;</Begin>
+ <End>&lt;/del&gt;</End>
+ </Tag>
+
+ <Tag _name="Direction">
+ <Begin>direction=</Begin>
+ </Tag>
+
+ <Tag _name="Directionality">
+ <Begin>dir=</Begin>
+ </Tag>
+
+ <Tag _name="Directory list">
+ <Begin>&lt;dir&gt;</Begin>
+ <End>&lt;/dir&gt;</End>
+ </Tag>
+
+ <Tag _name="Disabled">
+ <Begin>disabled</Begin>
+ </Tag>
+
+ <Tag _name="DIV container">
+ <Begin>&lt;div&gt;</Begin>
+ <End>&lt;/div&gt;</End>
+ </Tag>
+
+ <Tag _name="DIV Style container">
+ <Begin>&lt;div style=&gt;</Begin>
+ <End>&lt;/div&gt;</End>
+ </Tag>
+
+ <Tag _name="Document base">
+ <Begin>&lt;base&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Document body">
+ <Begin>&lt;body&gt;</Begin>
+ <End>&lt;/body&gt;</End>
+ </Tag>
+
+ <Tag _name="Document head">
+ <Begin>&lt;head&gt;</Begin>
+ <End>&lt;/head&gt;</End>
+ </Tag>
+
+ <Tag _name="Element ID">
+ <Begin>id=</Begin>
+ </Tag>
+
+ <Tag _name="Document title">
+ <Begin>&lt;title&gt;</Begin>
+ <End>&lt;/title&gt;</End>
+ </Tag>
+
+ <Tag _name="Document type">
+ <Begin>&lt;!DOCTYPE&gt;</Begin>
+ </Tag>
+
+ <Tag _name="HTML version">
+ <Begin>version=</Begin>
+ </Tag>
+
+ <Tag _name="Embedded object">
+ <Begin>&lt;embed&gt;</Begin>
+ <End>&lt;/embed&gt;</End>
+ </Tag>
+
+ <Tag _name="Emphasis">
+ <Begin>&lt;em&gt;</Begin>
+ <End>&lt;/em&gt;</End>
+ </Tag>
+
+ <Tag _name="Encode type">
+ <Begin>enctype=</Begin>
+ </Tag>
+
+ <Tag _name="Figure">
+ <Begin>&lt;fig&gt;</Begin>
+ <End>&lt;/fig&gt;</End>
+ </Tag>
+
+ <Tag _name="Font face">
+ <Begin>face=</Begin>
+ </Tag>
+
+ <Tag _name="For label">
+ <Begin>for=</Begin>
+ </Tag>
+
+ <Tag _name="Forced line break">
+ <Begin>&lt;br /&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Form action handler">
+ <Begin>action=</Begin>
+ </Tag>
+
+ <Tag _name="Form control group">
+ <Begin>&lt;fieldset&gt;</Begin>
+ <End>&lt;/fieldset&gt;</End>
+ </Tag>
+
+ <Tag _name="Form field label text">
+ <Begin>&lt;label&gt;</Begin>
+ <End>&lt;/label&gt;</End>
+ </Tag>
+
+ <Tag _name="Form input type">
+ <Begin>&lt;input type=&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Form input">
+ <Begin>&lt;input&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Form method">
+ <Begin>&lt;form method=&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Form method">
+ <Begin>method=</Begin>
+ </Tag>
+
+ <Tag _name="Form">
+ <Begin>&lt;form&gt;</Begin>
+ <End>&lt;/form&gt;</End>
+ </Tag>
+
+ <Tag _name="Forward link">
+ <Begin>rel=</Begin>
+ </Tag>
+
+ <Tag _name="Frame render parts">
+ <Begin>frame=</Begin>
+ </Tag>
+
+ <Tag _name="Frame source">
+ <Begin>&lt;frame src=&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Frame target">
+ <Begin>target=</Begin>
+ </Tag>
+
+ <Tag _name="Frame">
+ <Begin>&lt;frame&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Frameborder">
+ <Begin>frameborder=</Begin>
+ </Tag>
+
+ <Tag _name="Frameset columns">
+ <Begin>&lt;frameset cols=&gt;</Begin>
+ <End>&lt;/frameset&gt;</End>
+ </Tag>
+
+ <Tag _name="Frameset rows">
+ <Begin>&lt;frameset rows=&gt;</Begin>
+ <End>&lt;/frameset&gt;</End>
+ </Tag>
+
+ <Tag _name="Frameset">
+ <Begin>&lt;frameset&gt;</Begin>
+ <End>&lt;/frameset&gt;</End>
+ </Tag>
+
+ <Tag _name="Framespacing">
+ <Begin>framespacing=</Begin>
+ </Tag>
+
+ <Tag _name="Generic embedded object">
+ <Begin>&lt;object&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Generic metainformation">
+ <Begin>&lt;meta&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Generic span">
+ <Begin>&lt;span&gt;</Begin>
+ <End>&lt;/span&gt;</End>
+ </Tag>
+
+ <Tag _name="Header cell IDs">
+ <Begin>headers=</Begin>
+ </Tag>
+
+ <Tag _name="Heading 1">
+ <Begin>&lt;h1&gt;</Begin>
+ <End>&lt;/h1&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 2">
+ <Begin>&lt;h2&gt;</Begin>
+ <End>&lt;/h2&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 3">
+ <Begin>&lt;h3&gt;</Begin>
+ <End>&lt;/h3&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 4">
+ <Begin>&lt;h4&gt;</Begin>
+ <End>&lt;/h4&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 5">
+ <Begin>&lt;h5&gt;</Begin>
+ <End>&lt;/h5&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading 6">
+ <Begin>&lt;h6&gt;</Begin>
+ <End>&lt;/h6&gt;</End>
+ </Tag>
+
+ <Tag _name="Heading">
+ <Begin>&lt;h&gt;</Begin>
+ <End>&lt;/h&gt;</End>
+ </Tag>
+
+ <Tag _name="Height">
+ <Begin>height=</Begin>
+ </Tag>
+
+ <Tag _name="Horizontal rule">
+ <Begin>&lt;hr /&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Horizontal space">
+ <Begin>hspace=</Begin>
+ </Tag>
+
+ <Tag _name="HREF URI">
+ <Begin>href=</Begin>
+ </Tag>
+
+ <Tag _name="HTML root element">
+ <Begin>&lt;html&gt;</Begin>
+ <End>&lt;/html&gt;</End>
+ </Tag>
+
+ <Tag _name="HTTP header name">
+ <Begin>http-equiv=</Begin>
+ </Tag>
+
+ <Tag _name="I18N BiDi override">
+ <Begin>&lt;BDO&gt;</Begin>
+ <End>&lt;/BDO&gt;</End>
+ </Tag>
+
+ <Tag _name="Image map area">
+ <Begin>&lt;area&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Image map name">
+ <Begin>&lt;map name=&gt;</Begin>
+ <End>&lt;/map&gt;</End>
+ </Tag>
+
+ <Tag _name="Image map">
+ <Begin>&lt;map&gt;</Begin>
+ <End>&lt;/map&gt;</End>
+ </Tag>
+
+ <Tag _name="Image source">
+ <Begin>&lt;img src=&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Image">
+ <Begin>&lt;img&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Inline frame">
+ <Begin>&lt;iframe&gt;</Begin>
+ <End>&lt;/iframe&gt;</End>
+ </Tag>
+
+ <Tag _name="Inline layer">
+ <Begin>&lt;ilayer&gt;</Begin>
+ <End>&lt;/ilayer&gt;</End>
+ </Tag>
+
+ <Tag _name="Inserted text">
+ <Begin>&lt;ins&gt;</Begin>
+ <End>&lt;/ins&gt;</End>
+ </Tag>
+
+ <Tag _name="Instance definition">
+ <Begin>&lt;dfn&gt;</Begin>
+ <End>&lt;/dfn&gt;</End>
+ </Tag>
+
+ <Tag _name="Italic text">
+ <Begin>&lt;i&gt;</Begin>
+ <End>&lt;/i&gt;</End>
+ </Tag>
+
+ <Tag _name="Java applet">
+ <Begin>&lt;applet&gt;</Begin>
+ <End>&lt;/applet&gt;</End>
+ </Tag>
+
+ <Tag _name="Label">
+ <Begin>label=</Begin>
+ </Tag>
+
+ <Tag _name="Language code">
+ <Begin>hreflang=</Begin>
+ </Tag>
+
+ <Tag _name="Language code">
+ <Begin>lang=</Begin>
+ </Tag>
+
+ <Tag _name="Large text style">
+ <Begin>&lt;big&gt;</Begin>
+ <End>&lt;/big&gt;</End>
+ </Tag>
+
+ <Tag _name="Layer">
+ <Begin>&lt;layer&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Link color">
+ <Begin>link=</Begin>
+ </Tag>
+
+ <Tag _name="List item">
+ <Begin>&lt;li&gt;</Begin>
+ <End>&lt;/li&gt;</End>
+ </Tag>
+
+ <Tag _name="List of MIME types for file upload">
+ <Begin>accept=</Begin>
+ </Tag>
+
+ <Tag _name="List of supported character sets">
+ <Begin>accept-charset=</Begin>
+ </Tag>
+
+ <Tag _name="Listing">
+ <Begin>&lt;listing&gt;</Begin>
+ <End>&lt;/listing&gt;</End>
+ </Tag>
+
+ <Tag _name="Local change to font">
+ <Begin>&lt;font&gt;</Begin>
+ <End>&lt;/font&gt;</End>
+ </Tag>
+
+ <Tag _name="Long description link">
+ <Begin>longdesc=</Begin>
+ </Tag>
+
+ <Tag _name="Long quotation">
+ <Begin>&lt;blockquote&gt;</Begin>
+ <End>&lt;/blockquote&gt;</End>
+ </Tag>
+
+ <Tag _name="Mail link">
+ <Begin>mailto=</Begin>
+ </Tag>
+
+ <Tag _name="Margin pixel height">
+ <Begin>marginheight=</Begin>
+ </Tag>
+
+ <Tag _name="Margin pixel width">
+ <Begin>marginwidth=</Begin>
+ </Tag>
+
+ <Tag _name="Marquee">
+ <Begin>&lt;marquee&gt;</Begin>
+ <End>&lt;/marquee&gt;</End>
+ </Tag>
+
+ <Tag _name="Maximum length of text field">
+ <Begin>maxlength=</Begin>
+ </Tag>
+
+ <Tag _name="Output media">
+ <Begin>media=</Begin>
+ </Tag>
+
+ <Tag _name="Media-independent link">
+ <Begin>&lt;link rel="&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Menu list">
+ <Begin>&lt;menu&gt;</Begin>
+ <End>&lt;/menu&gt;</End>
+ </Tag>
+
+ <Tag _name="Multicolumn">
+ <Begin>&lt;multicol&gt;</Begin>
+ <End>&lt;/multicol&gt;</End>
+ </Tag>
+
+ <Tag _name="Multi-line text field">
+ <Begin>&lt;textarea&gt;</Begin>
+ <End>&lt;/textarea&gt;</End>
+ </Tag>
+
+ <Tag _name="Multiple">
+ <Begin>multiple</Begin>
+ </Tag>
+
+ <Tag _name="Name">
+ <Begin>name=</Begin>
+ </Tag>
+
+ <Tag _name="Named property value">
+ <Begin>&lt;param&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Next ID">
+ <Begin>&lt;nextid&gt;</Begin>
+ </Tag>
+
+ <Tag _name="No embedded objects">
+ <Begin>&lt;noembed&gt;</Begin>
+ </Tag>
+
+ <Tag _name="No frames">
+ <Begin>&lt;noframes&gt;</Begin>
+ <End>&lt;/noframes&gt;</End>
+ </Tag>
+
+ <Tag _name="No layers">
+ <Begin>&lt;nolayer&gt;</Begin>
+ <End>&lt;/nolayer&gt;</End>
+ </Tag>
+
+ <Tag _name="No line break">
+ <Begin>&lt;nobr&gt;</Begin>
+ <End>&lt;/nobr&gt;</End>
+ </Tag>
+
+ <Tag _name="No resize">
+ <Begin>noresize</Begin>
+ </Tag>
+
+ <Tag _name="No script">
+ <Begin>&lt;noscript&gt;</Begin>
+ <End>&lt;/noscript&gt;</End>
+ </Tag>
+
+ <Tag _name="No shade">
+ <Begin>noshade</Begin>
+ </Tag>
+
+ <Tag _name="No URI">
+ <Begin>nohref</Begin>
+ </Tag>
+
+ <Tag _name="No word wrap">
+ <Begin>nowrap</Begin>
+ </Tag>
+
+ <Tag _name="Note">
+ <Begin>&lt;note&gt;</Begin>
+ <End>&lt;/note&gt;</End>
+ </Tag>
+
+ <Tag _name="Object applet file">
+ <Begin>object=</Begin>
+ </Tag>
+
+ <Tag _name="Object data reference">
+ <Begin>data=</Begin>
+ </Tag>
+
+ <Tag _name="Offset for alignment character">
+ <Begin>charoff=</Begin>
+ </Tag>
+
+ <Tag _name="OnBlur event">
+ <Begin>onblur=</Begin>
+ </Tag>
+
+ <Tag _name="OnChange event">
+ <Begin>onchange=</Begin>
+ </Tag>
+
+ <Tag _name="OnClick event">
+ <Begin>onclick=</Begin>
+ </Tag>
+
+ <Tag _name="OnDblClick event">
+ <Begin>ondbclick=</Begin>
+ </Tag>
+
+ <Tag _name="OnFocus event">
+ <Begin>onfocus=</Begin>
+ </Tag>
+
+ <Tag _name="OnKeyDown event">
+ <Begin>onkeydown=</Begin>
+ </Tag>
+
+ <Tag _name="OnKeyPress event">
+ <Begin>onkeypress=</Begin>
+ </Tag>
+
+ <Tag _name="OnKeyUp event">
+ <Begin>onkeyup=</Begin>
+ </Tag>
+
+ <Tag _name="OnLoad event">
+ <Begin>onload=</Begin>
+ </Tag>
+
+ <Tag _name="OnMouseDown event">
+ <Begin>onmousedown=</Begin>
+ </Tag>
+
+ <Tag _name="OnMouseMove event">
+ <Begin>onmousemove=</Begin>
+ </Tag>
+
+ <Tag _name="OnMouseOut event">
+ <Begin>onmouseout=</Begin>
+ </Tag>
+
+ <Tag _name="OnMouseOver event">
+ <Begin>onmouseover=</Begin>
+ </Tag>
+
+ <Tag _name="OnMouseUp event">
+ <Begin>onmouseup=</Begin>
+ </Tag>
+
+ <Tag _name="OnReset event">
+ <Begin>onreset=</Begin>
+ </Tag>
+
+ <Tag _name="OnSelect event">
+ <Begin>onselect=</Begin>
+ </Tag>
+
+ <Tag _name="OnSubmit event">
+ <Begin>onsubmit=</Begin>
+ </Tag>
+
+ <Tag _name="OnUnload event">
+ <Begin>onunload=</Begin>
+ </Tag>
+
+ <Tag _name="Option group">
+ <Begin>&lt;optgroup&gt;</Begin>
+ <End>&lt;/optgroup&gt;</End>
+ </Tag>
+
+ <Tag _name="Option selector">
+ <Begin>&lt;select&gt;</Begin>
+ <End>&lt;/select&gt;</End>
+ </Tag>
+
+ <Tag _name="Ordered list">
+ <Begin>&lt;ol&gt;</Begin>
+ <End>&lt;/ol&gt;</End>
+ </Tag>
+
+ <Tag _name="Paragraph class">
+ <Begin>&lt;p class=&gt;</Begin>
+ <End>&lt;/p&gt;</End>
+ </Tag>
+
+ <Tag _name="Paragraph style">
+ <Begin>&lt;p style=&gt;</Begin>
+ <End>&lt;/p&gt;</End>
+ </Tag>
+
+ <Tag _name="Paragraph">
+ <Begin>&lt;p&gt;</Begin>
+ <End>&lt;/p&gt;</End>
+ </Tag>
+
+ <Tag _name="Preformatted listing">
+ <Begin>&lt;xmp&gt;</Begin>
+ <End>&lt;/xmp&gt;</End>
+ </Tag>
+
+ <Tag _name="Preformatted text">
+ <Begin>&lt;pre&gt;</Begin>
+ <End>&lt;/pre&gt;</End>
+ </Tag>
+
+ <Tag _name="Profile metainfo dictionary">
+ <Begin>profile=</Begin>
+ </Tag>
+
+ <Tag _name="Prompt message">
+ <Begin>prompt=</Begin>
+ </Tag>
+
+ <Tag _name="Push button">
+ <Begin>&lt;button&gt;</Begin>
+ <End>&lt;/button&gt;</End>
+ </Tag>
+
+ <Tag _name="Quote">
+ <Begin>&lt;quote&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Range">
+ <Begin>&lt;range&gt;</Begin>
+ </Tag>
+
+ <Tag _name="ReadOnly text and password">
+ <Begin>readonly</Begin>
+ </Tag>
+
+ <Tag _name="Reduced spacing">
+ <Begin>compact</Begin>
+ </Tag>
+
+ <Tag _name="Reverse link">
+ <Begin>rev=</Begin>
+ </Tag>
+
+ <Tag _name="Root">
+ <Begin>&lt;root&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Rows">
+ <Begin>rows=</Begin>
+ </Tag>
+
+ <Tag _name="Rulings between rows and columns">
+ <Begin>rules=</Begin>
+ </Tag>
+
+ <Tag _name="Sample program output, scripts">
+ <Begin>&lt;samp&gt;</Begin>
+ <End>&lt;/samp&gt;</End>
+ </Tag>
+
+ <Tag _name="Scope covered by header cells">
+ <Begin>scope=</Begin>
+ </Tag>
+
+ <Tag _name="Script language name">
+ <Begin>language=</Begin>
+ </Tag>
+
+ <Tag _name="Script statements">
+ <Begin>&lt;script&gt;</Begin>
+ <End>&lt;/script&gt;</End>
+ </Tag>
+
+ <Tag _name="Scrollbar">
+ <Begin>scrolling=</Begin>
+ </Tag>
+
+ <Tag _name="Selectable option">
+ <Begin>&lt;option&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Selected">
+ <Begin>selected</Begin>
+ </Tag>
+
+ <Tag _name="Server-side image map">
+ <Begin>ismap</Begin>
+ </Tag>
+
+ <Tag _name="Shape">
+ <Begin>shape=</Begin>
+ </Tag>
+
+ <Tag _name="Short inline quotation">
+ <Begin>&lt;q&gt;</Begin>
+ <End>&lt;/q&gt;</End>
+ </Tag>
+
+ <Tag _name="Single line prompt">
+ <Begin>&lt;isindex&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Size">
+ <Begin>size=</Begin>
+ </Tag>
+
+ <Tag _name="Small text style">
+ <Begin>&lt;small&gt;</Begin>
+ <End>&lt;/small&gt;</End>
+ </Tag>
+
+ <Tag _name="Soft line break">
+ <Begin>&lt;wbr&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Sound">
+ <Begin>&lt;sound&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Source">
+ <Begin>src=</Begin>
+ </Tag>
+
+ <Tag _name="Space-separated archive list">
+ <Begin>archive=</Begin>
+ </Tag>
+
+ <Tag _name="Spacer">
+ <Begin>&lt;spacer&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Spacing between cells">
+ <Begin>cellspacing=</Begin>
+ </Tag>
+
+ <Tag _name="Spacing within cells">
+ <Begin>cellpadding=</Begin>
+ </Tag>
+
+ <Tag _name="Span">
+ <Begin>span=</Begin>
+ </Tag>
+
+ <Tag _name="Square root">
+ <Begin>&lt;sqrt&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Standby load message">
+ <Begin>standby=</Begin>
+ </Tag>
+
+ <Tag _name="Starting sequence number">
+ <Begin>start</Begin>
+ </Tag>
+
+ <Tag _name="Strike-through text style">
+ <Begin>&lt;s&gt;</Begin>
+ <End>&lt;/s&gt;</End>
+ </Tag>
+
+ <Tag _name="Strike-through text">
+ <Begin>&lt;strike&gt;</Begin>
+ <End>&lt;/strike&gt;</End>
+ </Tag>
+
+ <Tag _name="Strong emphasis">
+ <Begin>&lt;strong&gt;</Begin>
+ <End>&lt;/strong&gt;</End>
+ </Tag>
+
+ <Tag _name="Style info">
+ <Begin>&lt;style&gt;</Begin>
+ <End>&lt;/style&gt;</End>
+ </Tag>
+
+ <Tag _name="Style info">
+ <Begin>style=</Begin>
+ </Tag>
+
+ <Tag _name="Subscript">
+ <Begin>&lt;sub&gt;</Begin>
+ <End>&lt;/sub&gt;</End>
+ </Tag>
+
+ <Tag _name="Superscript">
+ <Begin>&lt;sup&gt;</Begin>
+ <End>&lt;/sup&gt;</End>
+ </Tag>
+
+ <Tag _name="Tab order position">
+ <Begin>tabindex=</Begin>
+ </Tag>
+
+ <Tag _name="Table body">
+ <Begin>&lt;tbody&gt;</Begin>
+ <End>&lt;/tbody&gt;</End>
+ </Tag>
+
+ <Tag _name="Table caption">
+ <Begin>&lt;caption&gt;</Begin>
+ <End>&lt;/caption&gt;</End>
+ </Tag>
+
+ <Tag _name="Table column group properties">
+ <Begin>&lt;colgroup&gt;</Begin>
+ <End>&lt;/colgroup&gt;</End>
+ </Tag>
+
+ <Tag _name="Table column properties">
+ <Begin>&lt;col&gt;</Begin>
+ <End>&lt;/col&gt;</End>
+ </Tag>
+
+ <Tag _name="Table data cell">
+ <Begin>&lt;td&gt;</Begin>
+ <End>&lt;/td&gt;</End>
+ </Tag>
+
+ <Tag _name="Table footer">
+ <Begin>&lt;tfoot&gt;</Begin>
+ <End>&lt;/tfoot&gt;</End>
+ </Tag>
+
+ <Tag _name="Table header cell">
+ <Begin>&lt;th&gt;</Begin>
+ <End>&lt;/th&gt;</End>
+ </Tag>
+
+ <Tag _name="Table header">
+ <Begin>&lt;thead&gt;</Begin>
+ <End>&lt;/thead&gt;</End>
+ </Tag>
+
+ <Tag _name="Table row">
+ <Begin>&lt;tr&gt;</Begin>
+ <End>&lt;/tr&gt;</End>
+ </Tag>
+
+ <Tag _name="Table summary">
+ <Begin>summary=</Begin>
+ </Tag>
+
+ <Tag _name="Table">
+ <Begin>&lt;table&gt;</Begin>
+ <End>&lt;/table&gt;</End>
+ </Tag>
+
+ <Tag _name="Target - Blank">
+ <Begin>&lt;a target="_blank"&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Target - Parent">
+ <Begin>&lt;a target="_parent"&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Target - Self">
+ <Begin>&lt;a target="_self"&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Target - Top">
+ <Begin>&lt;a target="_top"&gt;</Begin>
+ <End>&lt;/a&gt;</End>
+ </Tag>
+
+ <Tag _name="Teletype or monospace text style">
+ <Begin>&lt;tt&gt;</Begin>
+ <End>&lt;/tt&gt;</End>
+ </Tag>
+
+ <Tag _name="Text color">
+ <Begin>color=</Begin>
+ </Tag>
+
+ <Tag _name="Text color">
+ <Begin>text=</Begin>
+ </Tag>
+
+ <Tag _name="Text entered by user">
+ <Begin>&lt;kbd&gt;</Begin>
+ <End>&lt;/kbd&gt;</End>
+ </Tag>
+
+ <Tag _name="Text">
+ <Begin>&lt;text&gt;</Begin>
+ </Tag>
+
+ <Tag _name="Title">
+ <Begin>title=</Begin>
+ </Tag>
+
+ <Tag _name="Top margin in pixels">
+ <Begin>topmargin</Begin>
+ </Tag>
+
+ <Tag _name="Underlined text style">
+ <Begin>&lt;u&gt;</Begin>
+ <End>&lt;/u&gt;</End>
+ </Tag>
+
+ <Tag _name="Unordered list">
+ <Begin>&lt;ul&gt;</Begin>
+ <End>&lt;/ul&gt;</End>
+ </Tag>
+
+ <Tag _name="URL">
+ <Begin>url=</Begin>
+ </Tag>
+
+ <Tag _name="Use image map">
+ <Begin>usemap=</Begin>
+ </Tag>
+
+ <Tag _name="Value interpretation">
+ <Begin>valuetype=</Begin>
+ </Tag>
+
+ <Tag _name="Value">
+ <Begin>value=</Begin>
+ </Tag>
+
+ <Tag _name="Variable or program argument">
+ <Begin>&lt;var&gt;</Begin>
+ <End>&lt;/var&gt;</End>
+ </Tag>
+
+ <Tag _name="Vertical cell alignment">
+ <Begin>valign=</Begin>
+ </Tag>
+
+ <Tag _name="Vertical space">
+ <Begin>vspace=</Begin>
+ </Tag>
+
+ <Tag _name="Visited link color">
+ <Begin>vlink=</Begin>
+ </Tag>
+
+ <Tag _name="Width">
+ <Begin>width=</Begin>
+ </Tag>
+
+</TagGroup>
+
+<TagGroup _name="HTML - Special Characters" sort="false">
+
+ <Tag _name="Non-breaking space">
+ <Begin>&amp;nbsp;</Begin>
+ </Tag>
+
+ <Tag name="Soft hyphen­">
+ <Begin>&amp;shy;</Begin>
+ </Tag>
+
+ <Tag name="&quot;">
+ <Begin>&amp;quot;</Begin>
+ </Tag>
+
+ <Tag name="&amp;">
+ <Begin>&amp;amp;</Begin>
+ </Tag>
+
+ <Tag name="¡">
+ <Begin>&amp;iexcl;</Begin>
+ </Tag>
+
+ <Tag name="¦">
+ <Begin>&amp;brvbar;</Begin>
+ </Tag>
+
+ <Tag name="¨">
+ <Begin>&amp;uml;</Begin>
+ </Tag>
+
+ <Tag name="¯">
+ <Begin>&amp;macr;</Begin>
+ </Tag>
+
+ <Tag name="´">
+ <Begin>&amp;acute;</Begin>
+ </Tag>
+
+ <Tag name="¸">
+ <Begin>&amp;cedil;</Begin>
+ </Tag>
+
+ <Tag name="&lt;">
+ <Begin>&amp;lt;</Begin>
+ </Tag>
+
+ <Tag name="&gt;">
+ <Begin>&amp;gt;</Begin>
+ </Tag>
+
+ <Tag name="±">
+ <Begin>&amp;plusmn;</Begin>
+ </Tag>
+
+ <Tag name="«">
+ <Begin>&amp;laquo;</Begin>
+ </Tag>
+
+ <Tag name="»">
+ <Begin>&amp;raquo;</Begin>
+ </Tag>
+
+ <Tag name="×">
+ <Begin>&amp;times;</Begin>
+ </Tag>
+
+ <Tag name="÷">
+ <Begin>&amp;divide;</Begin>
+ </Tag>
+
+ <Tag name="¢">
+ <Begin>&amp;cent;</Begin>
+ </Tag>
+
+ <Tag name="£">
+ <Begin>&amp;pound;</Begin>
+ </Tag>
+
+ <Tag name="€">
+ <Begin>&amp;euro;</Begin>
+ </Tag>
+
+ <Tag name="¤">
+ <Begin>&amp;curren;</Begin>
+ </Tag>
+
+ <Tag name="¥">
+ <Begin>&amp;yen;</Begin>
+ </Tag>
+
+ <Tag name="§">
+ <Begin>&amp;sect;</Begin>
+ </Tag>
+
+ <Tag name="©">
+ <Begin>&amp;copy;</Begin>
+ </Tag>
+
+ <Tag name="¬">
+ <Begin>&amp;not;</Begin>
+ </Tag>
+
+ <Tag name="®">
+ <Begin>&amp;reg;</Begin>
+ </Tag>
+
+ <Tag name="™">
+ <Begin>&amp;trade;</Begin>
+ </Tag>
+
+ <Tag name="°">
+ <Begin>&amp;deg;</Begin>
+ </Tag>
+
+ <Tag name="µ">
+ <Begin>&amp;micro;</Begin>
+ </Tag>
+
+ <Tag name="¶">
+ <Begin>&amp;para;</Begin>
+ </Tag>
+
+ <Tag name="·">
+ <Begin>&amp;middot;</Begin>
+ </Tag>
+
+ <Tag name="¼">
+ <Begin>&amp;frac14;</Begin>
+ </Tag>
+
+ <Tag name="½">
+ <Begin>&amp;frac12;</Begin>
+ </Tag>
+
+ <Tag name="¾">
+ <Begin>&amp;frac34;</Begin>
+ </Tag>
+
+ <Tag name="¹">
+ <Begin>&amp;sup1;</Begin>
+ </Tag>
+
+ <Tag name="²">
+ <Begin>&amp;sup2;</Begin>
+ </Tag>
+
+ <Tag name="³">
+ <Begin>&amp;sup3;</Begin>
+ </Tag>
+
+ <Tag name="á">
+ <Begin>&amp;aacute;</Begin>
+ </Tag>
+
+ <Tag name="Á">
+ <Begin>&amp;Aacute;</Begin>
+ </Tag>
+
+ <Tag name="â">
+ <Begin>&amp;acirc;</Begin>
+ </Tag>
+
+ <Tag name="Â">
+ <Begin>&amp;Acirc;</Begin>
+ </Tag>
+
+ <Tag name="à">
+ <Begin>&amp;agrave;</Begin>
+ </Tag>
+
+ <Tag name="À">
+ <Begin>&amp;Agrave;</Begin>
+ </Tag>
+
+ <Tag name="å">
+ <Begin>&amp;aring;</Begin>
+ </Tag>
+
+ <Tag name="Å">
+ <Begin>&amp;Aring;</Begin>
+ </Tag>
+
+ <Tag name="ã">
+ <Begin>&amp;atilde;</Begin>
+ </Tag>
+
+ <Tag name="Ã">
+ <Begin>&amp;Atilde;</Begin>
+ </Tag>
+
+ <Tag name="ä">
+ <Begin>&amp;auml;</Begin>
+ </Tag>
+
+ <Tag name="Ä">
+ <Begin>&amp;Auml;</Begin>
+ </Tag>
+
+ <Tag name="ª">
+ <Begin>&amp;ordf;</Begin>
+ </Tag>
+
+ <Tag name="æ">
+ <Begin>&amp;aelig;</Begin>
+ </Tag>
+
+ <Tag name="Æ">
+ <Begin>&amp;AElig;</Begin>
+ </Tag>
+
+ <Tag name="ç">
+ <Begin>&amp;ccedil;</Begin>
+ </Tag>
+
+ <Tag name="Ç">
+ <Begin>&amp;Ccedil;</Begin>
+ </Tag>
+
+ <Tag name="Ð">
+ <Begin>&amp;ETH;</Begin>
+ </Tag>
+
+ <Tag name="ð">
+ <Begin>&amp;eth;</Begin>
+ </Tag>
+
+ <Tag name="é">
+ <Begin>&amp;eacute;</Begin>
+ </Tag>
+
+ <Tag name="É">
+ <Begin>&amp;Eacute;</Begin>
+ </Tag>
+
+ <Tag name="ê">
+ <Begin>&amp;ecirc;</Begin>
+ </Tag>
+
+ <Tag name="Ê">
+ <Begin>&amp;Ecirc;</Begin>
+ </Tag>
+
+ <Tag name="è">
+ <Begin>&amp;egrave;</Begin>
+ </Tag>
+
+ <Tag name="È">
+ <Begin>&amp;Egrave;</Begin>
+ </Tag>
+
+ <Tag name="ë">
+ <Begin>&amp;euml;</Begin>
+ </Tag>
+
+ <Tag name="Ë">
+ <Begin>&amp;Euml;</Begin>
+ </Tag>
+
+ <Tag name="í">
+ <Begin>&amp;iacute;</Begin>
+ </Tag>
+
+ <Tag name="Í">
+ <Begin>&amp;Iacute;</Begin>
+ </Tag>
+
+ <Tag name="î">
+ <Begin>&amp;icirc;</Begin>
+ </Tag>
+
+ <Tag name="Î">
+ <Begin>&amp;Icirc;</Begin>
+ </Tag>
+
+ <Tag name="ì">
+ <Begin>&amp;igrave;</Begin>
+ </Tag>
+
+ <Tag name="Ì">
+ <Begin>&amp;Igrave;</Begin>
+ </Tag>
+
+ <Tag name="ï">
+ <Begin>&amp;iuml;</Begin>
+ </Tag>
+
+ <Tag name="Ï">
+ <Begin>&amp;Iuml;</Begin>
+ </Tag>
+
+ <Tag name="ñ">
+ <Begin>&amp;ntilde;</Begin>
+ </Tag>
+
+ <Tag name="Ñ">
+ <Begin>&amp;Ntilde;</Begin>
+ </Tag>
+
+ <Tag name="ó">
+ <Begin>&amp;oacute;</Begin>
+ </Tag>
+
+ <Tag name="Ó">
+ <Begin>&amp;Oacute;</Begin>
+ </Tag>
+
+ <Tag name="ô">
+ <Begin>&amp;ocirc;</Begin>
+ </Tag>
+
+ <Tag name="Ô">
+ <Begin>&amp;Ocirc;</Begin>
+ </Tag>
+
+ <Tag name="ò">
+ <Begin>&amp;ograve;</Begin>
+ </Tag>
+
+ <Tag name="Ò">
+ <Begin>&amp;Ograve;</Begin>
+ </Tag>
+
+ <Tag name="º">
+ <Begin>&amp;ordm;</Begin>
+ </Tag>
+
+ <Tag name="ø">
+ <Begin>&amp;oslash;</Begin>
+ </Tag>
+
+ <Tag name="Ø">
+ <Begin>&amp;Oslash;</Begin>
+ </Tag>
+
+ <Tag name="õ">
+ <Begin>&amp;otilde;</Begin>
+ </Tag>
+
+ <Tag name="Õ">
+ <Begin>&amp;Otilde;</Begin>
+ </Tag>
+
+ <Tag name="ö">
+ <Begin>&amp;ouml;</Begin>
+ </Tag>
+
+ <Tag name="Ö">
+ <Begin>&amp;Ouml;</Begin>
+ </Tag>
+
+ <Tag name="ß">
+ <Begin>&amp;szlig;</Begin>
+ </Tag>
+
+ <Tag name="þ">
+ <Begin>&amp;thorn;</Begin>
+ </Tag>
+
+ <Tag name="Þ">
+ <Begin>&amp;THORN;</Begin>
+ </Tag>
+
+ <Tag name="ú">
+ <Begin>&amp;uacute;</Begin>
+ </Tag>
+
+ <Tag name="Ú">
+ <Begin>&amp;Uacute;</Begin>
+ </Tag>
+
+ <Tag name="û">
+ <Begin>&amp;ucirc;</Begin>
+ </Tag>
+
+ <Tag name="Û">
+ <Begin>&amp;Ucirc;</Begin>
+ </Tag>
+
+ <Tag name="ù">
+ <Begin>&amp;ugrave;</Begin>
+ </Tag>
+
+ <Tag name="Ù">
+ <Begin>&amp;Ugrave;</Begin>
+ </Tag>
+
+ <Tag name="ü">
+ <Begin>&amp;uuml;</Begin>
+ </Tag>
+
+ <Tag name="Ü">
+ <Begin>&amp;Uuml;</Begin>
+ </Tag>
+
+ <Tag name="ý">
+ <Begin>&amp;yacute;</Begin>
+ </Tag>
+
+ <Tag name="Ý">
+ <Begin>&amp;Yacute;</Begin>
+ </Tag>
+
+ <Tag name="ÿ">
+ <Begin>&amp;yuml;</Begin>
+ </Tag>
+
+</TagGroup>
+</TagList>
diff --git a/plugins/taglist/Latex.tags.xml.in b/plugins/taglist/Latex.tags.xml.in
new file mode 100755
index 00000000..5ac71c0d
--- /dev/null
+++ b/plugins/taglist/Latex.tags.xml.in
@@ -0,0 +1,344 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TagList xmlns="http://gedit.sourceforge.net/some-location">
+<TagGroup _name="Latex - Tags" sort="true">
+ <Tag _name="Bibliography (cite)">
+ <Begin>\cite{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Bibliography (item)">
+ <Begin>\bibitem{ </Begin>
+ <End> }{} &#10;</End>
+ </Tag>
+
+ <Tag _name="Bibliography (shortcite)">
+ <Begin>\shortcite{ </Begin>
+ <End> }</End>
+ </Tag>
+
+ <Tag _name="Bibliography (thebibliography)">
+ <Begin>\begin{thebibliography}{99}&#10;</Begin>
+ <End>&#10;\end{thebibliography}</End>
+ </Tag>
+
+ <Tag _name="Brackets ()">
+ <Begin>\left( </Begin>
+ <End> \right) </End>
+ </Tag>
+
+ <Tag _name="Brackets []">
+ <Begin>\left[ </Begin>
+ <End> \right] </End>
+ </Tag>
+
+ <Tag _name="Brackets {}">
+ <Begin>\left\{ </Begin>
+ <End> \right\} </End>
+ </Tag>
+
+ <Tag _name="Brackets &lt;&gt;">
+ <Begin>\left\langle </Begin>
+ <End> \right\rangle </End>
+ </Tag>
+
+ <Tag _name="File input">
+ <Begin>\input{ </Begin>
+ <End> }&#10;</End>
+ </Tag>
+
+ <Tag _name="Function cosine">
+ <Begin>\cos{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Function e^">
+ <Begin>e^{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Function exp">
+ <Begin>\exp{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Function log">
+ <Begin>\log{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Function log10">
+ <Begin>\log_{10}{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Function sine">
+ <Begin>\sin{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Greek alpha">
+ <Begin>\alpha </Begin>
+ </Tag>
+
+ <Tag _name="Greek beta">
+ <Begin>\beta </Begin>
+ </Tag>
+
+ <Tag _name="Greek epsilon">
+ <Begin>\varepsilon </Begin>
+ </Tag>
+
+ <Tag _name="Greek gamma">
+ <Begin>\gamma </Begin>
+ </Tag>
+
+ <Tag _name="Greek lambda">
+ <Begin>\lambda </Begin>
+ </Tag>
+
+ <Tag _name="Greek rho">
+ <Begin>\rho </Begin>
+ </Tag>
+
+ <Tag _name="Greek tau">
+ <Begin>\tau </Begin>
+ </Tag>
+
+ <Tag _name="Header 0 (chapter)">
+ <Begin>\chapter{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header 0 (chapter*)">
+ <Begin>\chapter*{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header 1 (section)">
+ <Begin>\section{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header 1 (section*)">
+ <Begin>\section*{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header 2 (subsection)">
+ <Begin>\subsection{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header 2 (subsection*)">
+ <Begin>\subsection*{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header 3 (subsubsection)">
+ <Begin>\subsubsection{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header 3 (subsubsection*)">
+ <Begin>\subsubsection*{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header 4 (paragraph)">
+ <Begin>\paragraph{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Header appendix">
+ <Begin>\appendix&#10; </Begin>
+ </Tag>
+
+ <Tag _name="List description">
+ <Begin>\begin{description}&#10;</Begin>
+ <End>&#10;\end{description}&#10;</End>
+ </Tag>
+
+ <Tag _name="List enumerate">
+ <Begin>\begin{enumerate}&#10;</Begin>
+ <End>&#10;\end{enumerate}&#10;</End>
+ </Tag>
+
+ <Tag _name="List itemize">
+ <Begin>\begin{itemize}&#10;</Begin>
+ <End>&#10;\end{itemize}&#10;</End>
+ </Tag>
+
+ <Tag _name="Item with label">
+ <Begin>\item[ </Begin>
+ <End> ]</End>
+ </Tag>
+
+ <Tag _name="Item">
+ <Begin>\item </Begin>
+ </Tag>
+
+ <Tag _name="Maths (display)">
+ <Begin>&#10;$$&#10;</Begin>
+ <End>&#10;$$&#10;</End>
+ </Tag>
+
+ <Tag _name="Maths (inline)">
+ <Begin>$ </Begin>
+ <End> $ </End>
+ </Tag>
+
+ <Tag _name="Operator fraction">
+ <Begin>\frac{ </Begin>
+ <End> }{} </End>
+ </Tag>
+
+ <Tag _name="Operator integral (display)">
+ <Begin>\int\limits_{ </Begin>
+ <End> }^{} </End>
+ </Tag>
+
+ <Tag _name="Operator integral (inline)">
+ <Begin>\int_{ </Begin>
+ <End> }^{} </End>
+ </Tag>
+
+ <Tag _name="Operator sum (display)">
+ <Begin>\sum\limits_{ </Begin>
+ <End> }^{} </End>
+ </Tag>
+
+ <Tag _name="Operator sum (inline)">
+ <Begin>\sum_{ </Begin>
+ <End> }^{} </End>
+ </Tag>
+
+ <Tag _name="Reference label">
+ <Begin>\label{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Reference ref">
+ <Begin>{\protect\ref{ </Begin>
+ <End> }} </End>
+ </Tag>
+
+ <Tag _name="Symbol &lt;&lt;">
+ <Begin>\ll </Begin>
+ </Tag>
+
+ <Tag _name="Symbol &lt;=">
+ <Begin>\le </Begin>
+ </Tag>
+
+ <Tag _name="Symbol &gt;=">
+ <Begin>\ge </Begin>
+ </Tag>
+
+ <Tag _name="Symbol &gt;&gt;">
+ <Begin>\gg </Begin>
+ </Tag>
+
+ <Tag _name="Symbol and">
+ <Begin>\mathrm{and} </Begin>
+ </Tag>
+
+ <Tag _name="Symbol const">
+ <Begin>\mathrm{const} </Begin>
+ </Tag>
+
+ <Tag _name="Symbol d2-by-dt2-partial">
+ <Begin>\frac{\partial{ </Begin>
+ <End> }^2}{\partial t^2} </End>
+ </Tag>
+
+ <Tag _name="Symbol dagger">
+ <Begin>\dagger </Begin>
+ </Tag>
+
+ <Tag _name="Symbol d-by-dt">
+ <Begin>\frac{{\mathrm{d}} </Begin>
+ <End> }{{\mathrm{d}}t} </End>
+ </Tag>
+
+ <Tag _name="Symbol d-by-dt-partial">
+ <Begin>\frac{\partial </Begin>
+ <End> }{\partial t} </End>
+ </Tag>
+
+ <Tag _name="Symbol equiv">
+ <Begin>\equiv </Begin>
+ </Tag>
+
+ <Tag _name="Symbol en-dash --">
+ <Begin>-- </Begin>
+ </Tag>
+
+ <Tag _name="Symbol em-dash ---">
+ <Begin>--- </Begin>
+ </Tag>
+
+ <Tag _name="Symbol infinity">
+ <Begin>\infty </Begin>
+ </Tag>
+
+ <Tag _name="Symbol mathspace ,">
+ <Begin>\quad\mathrm{,} </Begin>
+ </Tag>
+
+ <Tag _name="Symbol mathspace .">
+ <Begin>\quad\mathrm{.} </Begin>
+ </Tag>
+
+ <Tag _name="Symbol mathspace _">
+ <Begin>\quad </Begin>
+ </Tag>
+
+ <Tag _name="Symbol mathspace __">
+ <Begin>\qquad </Begin>
+ </Tag>
+
+ <Tag _name="Symbol simeq">
+ <Begin>\simeq </Begin>
+ </Tag>
+
+ <Tag _name="Symbol star">
+ <Begin>\star </Begin>
+ </Tag>
+
+ <Tag _name="Typeface bold">
+ <Begin>\textbf{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Typeface type">
+ <Begin>\texttt{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Typeface italic">
+ <Begin>\textit{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Typeface slanted">
+ <Begin>\textsl{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Emphasis">
+ <Begin>\emph{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Unbreakable text">
+ <Begin>\mbox{ </Begin>
+ <End> } </End>
+ </Tag>
+
+ <Tag _name="Footnote">
+ <Begin>\footnote{ </Begin>
+ <End> } </End>
+ </Tag>
+
+</TagGroup>
+</TagList>
diff --git a/plugins/taglist/Makefile.am b/plugins/taglist/Makefile.am
new file mode 100755
index 00000000..b04f4584
--- /dev/null
+++ b/plugins/taglist/Makefile.am
@@ -0,0 +1,60 @@
+# Tag list plugin
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+taglistdir = $(GEDIT_PLUGINS_DATA_DIR)/taglist
+
+taglist_in_files = \
+ HTML.tags.xml.in \
+ Latex.tags.xml.in \
+ XSLT.tags.xml.in \
+ XUL.tags.xml.in
+
+taglist_DATA = $(taglist_in_files:.tags.xml.in=.tags.gz)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+plugin_LTLIBRARIES = libtaglist.la
+
+libtaglist_la_SOURCES = \
+ gedit-taglist-plugin-parser.c \
+ gedit-taglist-plugin-parser.h \
+ gedit-taglist-plugin-panel.c \
+ gedit-taglist-plugin-panel.h \
+ gedit-taglist-plugin.c \
+ gedit-taglist-plugin.h
+
+libtaglist_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libtaglist_la_LIBADD = $(GEDIT_LIBS)
+
+plugin_in_files = taglist.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po)
+ $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+GZIP_ENV = -9
+
+%.tags.gz: %.tags.xml.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po)
+ LC_ALL=C $(INTLTOOL_MERGE) $(top_srcdir)/po $< $(@:.gz=) -x -u -c $(top_builddir)/po/.intltool-merge-cache
+ GZIP=$(GZIP_ENV) gzip -f $(@:.gz=)
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = \
+ $(taglist_in_files) $(taglist_DATA) \
+ $(plugin_in_files) $(plugin_DATA)
+
+CLEANFILES = \
+ $(taglist_DATA) \
+ $(plugin_DATA)
+DISTCLEANFILES = \
+ $(taglist_DATA) \
+ $(plugin_DATA)
+
+
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/taglist/XSLT.tags.xml.in b/plugins/taglist/XSLT.tags.xml.in
new file mode 100755
index 00000000..f9591a25
--- /dev/null
+++ b/plugins/taglist/XSLT.tags.xml.in
@@ -0,0 +1,337 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ XSLT Tags for gedit
+ Jens Askengren <[email protected]>
+-->
+<TagList xmlns="http://gedit.sourceforge.net/some-location">
+<TagGroup _name="XSLT - Elements" sort="true">
+ <Tag name="apply-imports">
+ <Begin>&lt;xsl:apply-imports&gt;</Begin>
+ <End>&lt;/xsl:apply-imports&gt;</End>
+ </Tag>
+ <Tag name="apply-templates">
+ <Begin>&lt;xsl:apply-templates select="</Begin>
+ <End>"/&gt;</End>
+ </Tag>
+ <Tag name="attribute">
+ <Begin>&lt;xsl:attribute name="</Begin>
+ <End>"&gt;&lt;/xsl:attribute&gt;</End>
+ </Tag>
+ <Tag name="attribute-set">
+ <Begin>&lt;xsl:attribute-set name="</Begin>
+ <End>"&gt;&lt;/xsl:attribute-set&gt;</End>
+ </Tag>
+ <Tag name="call-template">
+ <Begin>&lt;xsl:call-template name="</Begin>
+ <End>"/&gt;</End>
+ </Tag>
+ <Tag name="choose">
+ <Begin>&lt;xsl:choose&gt;&#10;</Begin>
+ <End>&#10;&lt;/xsl:choose&gt;</End>
+ </Tag>
+ <Tag name="comment">
+ <Begin>&lt;xsl:comment&gt;</Begin>
+ <End>&lt;/xsl:comment&gt;</End>
+ </Tag>
+ <Tag name="copy">
+ <Begin>&lt;xsl:copy/&gt;</Begin>
+ </Tag>
+ <Tag name="copy-of">
+ <Begin>&lt;xsl:copy-of select="</Begin>
+ <End>"/&gt;</End>
+ </Tag>
+ <Tag name="decimal-format">
+ <Begin>&lt;xsl:decimal-format/&gt;</Begin>
+ </Tag>
+ <Tag name="element">
+ <Begin>&lt;xsl:element name="</Begin>
+ <End>"&gt;&lt;/xsl:element&gt;</End>
+ </Tag>
+ <Tag name="fallback">
+ <Begin>&lt;xsl:fallback&gt;</Begin>
+ <End>&lt;/xsl:fallback&gt;</End>
+ </Tag>
+ <Tag name="for-each">
+ <Begin>&lt;xsl:for-each select="</Begin>
+ <End>"&gt;&#10;&lt;/xsl:for-each&gt;</End>
+ </Tag>
+ <Tag name="if">
+ <Begin>&lt;xsl:if test="</Begin>
+ <End>"&gt;&#10;&#10;&lt;/xsl:if&gt;</End>
+ </Tag>
+ <Tag name="import">
+ <Begin>&lt;xsl:import href="</Begin>
+ <End>"/&gt;</End>
+ </Tag>
+ <Tag name="include">
+ <Begin>&lt;xsl:include href="</Begin>
+ <End>"/&gt;</End>
+ </Tag>
+ <Tag name="key">
+ <Begin>&lt;xsl:key name="</Begin>
+ <End>" match="" use=""/&gt;</End>
+ </Tag>
+ <Tag name="message">
+ <Begin>&lt;xsl:message&gt;</Begin>
+ <End>&lt;/xsl:message&gt;</End>
+ </Tag>
+ <Tag name="namespace-alias">
+ <Begin>&lt;xsl:namespace-alias stylesheet-prefix="</Begin>
+ <End>" result-prefix=""/&gt;</End>
+ </Tag>
+ <Tag name="number">
+ <Begin>&lt;xsl:number/&gt;</Begin>
+ </Tag>
+ <Tag name="otherwise">
+ <Begin>&lt;xsl:otherwise&gt;</Begin>
+ <End>&lt;/xsl:otherwise&gt;</End>
+ </Tag>
+ <Tag name="output">
+ <Begin>&lt;xsl:output encoding="utf-8" method="</Begin>
+ <End>"/&gt;</End>
+ </Tag>
+ <Tag name="param">
+ <Begin>&lt;xsl:param name="</Begin>
+ <End>"&gt;&lt;/xsl:param&gt;</End>
+ </Tag>
+ <Tag name="preserve-space">
+ <Begin>&lt;xsl:preserve-space elements="</Begin>
+ <End>"&gt;&lt;/xsl:preserve-space&gt;</End>
+ </Tag>
+ <Tag name="processing-instruction">
+ <Begin>&lt;xsl:processing-instruction name="</Begin>
+ <End>"&gt;&lt;/xsl:processing-instruction&gt;</End>
+ </Tag>
+ <Tag name="sort">
+ <Begin>&lt;xsl:sort/&gt;</Begin>
+ </Tag>
+ <Tag name="strip-space">
+ <Begin>&lt;xsl:strip-space elements="</Begin>
+ <End>"&gt;&lt;/xsl:strip-space&gt;</End>
+ </Tag>
+ <Tag name="stylesheet">
+ <Begin>&lt;xsl:stylesheet version="</Begin>
+ <End>"/&gt;&#10;&#10;&lt;/xsl:stylesheet&gt;</End>
+ </Tag>
+ <Tag name="template">
+ <Begin>&lt;xsl:template match="</Begin>
+ <End>"&gt;&#10;&#10;&lt;/xsl:template&gt;</End>
+ </Tag>
+ <Tag name="text">
+ <Begin>&lt;xsl:text/&gt;</Begin>
+ </Tag>
+ <Tag name="value-of">
+ <Begin>&lt;xsl:value-of select="</Begin>
+ <End>"/&gt;</End>
+ </Tag>
+ <Tag name="variable">
+ <Begin>&lt;xsl:variable select="</Begin>
+ <End>"/&gt;</End>
+ </Tag>
+ <Tag name="when">
+ <Begin>&lt;xsl:when test="</Begin>
+ <End>&gt;&#10;&#10;&lt;/xsl:when&gt;</End>
+ </Tag>
+ <Tag name="with-param">
+ <Begin>&lt;xsl:with-param name="</Begin>
+ <End>"&gt;&lt;/xsl:with-param&gt;</End>
+ </Tag>
+</TagGroup>
+
+<TagGroup _name="XSLT - Functions" sort="true">
+ <Tag name="boolean">
+ <Begin>boolean(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="ceiling">
+ <Begin>ceiling(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="comment">
+ <Begin>comment()</Begin>
+ </Tag>
+ <Tag name="concat">
+ <Begin>concat(</Begin>
+ <End>,,)</End>
+ </Tag>
+ <Tag name="contains">
+ <Begin>contains(</Begin>
+ <End>,)</End>
+ </Tag>
+ <Tag name="count">
+ <Begin>count(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="current">
+ <Begin>current()</Begin>
+ </Tag>
+ <Tag name="document">
+ <Begin>document(</Begin>
+ <End>,)</End>
+ </Tag>
+ <Tag name="element-available">
+ <Begin>element-available(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="false">
+ <Begin>false()</Begin>
+ </Tag>
+ <Tag name="floor">
+ <Begin>floor(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="format-number">
+ <Begin>format-number(</Begin>
+ <End>,,)</End>
+ </Tag>
+ <Tag name="function-available">
+ <Begin>function-available(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="generate-id">
+ <Begin>generate-id(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="id">
+ <Begin>id(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="key">
+ <Begin>key(</Begin>
+ <End>,)</End>
+ </Tag>
+ <Tag name="lang">
+ <Begin>lang(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="last">
+ <Begin>last()</Begin>
+ </Tag>
+ <Tag name="local-name">
+ <Begin>local-name(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="name">
+ <Begin>name(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="namespace-uri">
+ <Begin>namespace-uri(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="node">
+ <Begin>node()</Begin>
+ </Tag>
+ <Tag name="normalize-space">
+ <Begin>normalize-space(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="not">
+ <Begin>not(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="number">
+ <Begin>number(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="position">
+ <Begin>position()</Begin>
+ </Tag>
+ <Tag name="processing-instruction">
+ <Begin>processing-instruction(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="round">
+ <Begin>round(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="starts-with">
+ <Begin>starts-with(</Begin>
+ <End>,)</End>
+ </Tag>
+ <Tag name="string">
+ <Begin>string(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="string-length">
+ <Begin>string-length(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="substring">
+ <Begin>substring(</Begin>
+ <End>,,)</End>
+ </Tag>
+ <Tag name="substring-after">
+ <Begin>substring-after(</Begin>
+ <End>,)</End>
+ </Tag>
+ <Tag name="substring-before">
+ <Begin>substring-before(</Begin>
+ <End>,)</End>
+ </Tag>
+ <Tag name="sum">
+ <Begin>sum(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="system-property">
+ <Begin>system-property(</Begin>
+ <End>)</End>
+ </Tag>
+ <Tag name="text">
+ <Begin>text()</Begin>
+ </Tag>
+ <Tag name="translate">
+ <Begin>translate(</Begin>
+ <End>,,)</End>
+ </Tag>
+ <Tag name="true">
+ <Begin>true()</Begin>
+ </Tag>
+ <Tag name="unparsed-entity-uri">
+ <Begin>unparsed-entity-uri(</Begin>
+ <End>)</End>
+ </Tag>
+</TagGroup>
+
+<TagGroup _name="XSLT - Axes" sort="true">
+ <Tag _name="ancestor">
+ <Begin>ancestor::</Begin>
+ </Tag>
+ <Tag _name="ancestor-or-self">
+ <Begin>ancestor-or-self::</Begin>
+ </Tag>
+ <Tag _name="attribute">
+ <Begin>@</Begin>
+ </Tag>
+ <Tag _name="child">
+ <Begin>child::</Begin>
+ </Tag>
+ <Tag _name="descendant">
+ <Begin>descendant::</Begin>
+ </Tag>
+ <Tag _name="descendant-or-self">
+ <Begin>//</Begin>
+ </Tag>
+ <Tag _name="following">
+ <Begin>following::</Begin>
+ </Tag>
+ <Tag _name="following-sibling">
+ <Begin>following-sibling::</Begin>
+ </Tag>
+ <Tag _name="namespace">
+ <Begin>namespace::</Begin>
+ </Tag>
+ <Tag _name="parent">
+ <Begin>..</Begin>
+ </Tag>
+ <Tag _name="preceding">
+ <Begin>preceding::</Begin>
+ </Tag>
+ <Tag _name="preceding-sibling">
+ <Begin>preceding-sibling::</Begin>
+ </Tag>
+ <Tag _name="self">
+ <Begin>.</Begin>
+ </Tag>
+</TagGroup>
+</TagList>
diff --git a/plugins/taglist/XUL.tags.xml.in b/plugins/taglist/XUL.tags.xml.in
new file mode 100755
index 00000000..fc4c26a7
--- /dev/null
+++ b/plugins/taglist/XUL.tags.xml.in
@@ -0,0 +1,536 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<TagList xmlns="http://gedit.sourceforge.net/some-location">
+<TagGroup _name="XUL - Tags" sort="true">
+
+ <Tag name="action">
+ <Begin>&lt;action&gt;</Begin>
+ <End>&lt;/action&gt;</End>
+ </Tag>
+
+ <Tag name="arrowscrollbox">
+ <Begin>&lt;arrowscrollbox orient="vertical" flex="1"&gt;</Begin>
+ <End>&lt;/arrowscrollbox&gt;</End>
+ </Tag>
+
+ <Tag name="bbox">
+ <Begin>&lt;bbox&gt;</Begin>
+ <End>&lt;/bbox&gt;</End>
+ </Tag>
+
+ <Tag name="binding">
+ <Begin>&lt;binding id=""&gt;</Begin>
+ <End>&lt;/binding&gt;</End>
+ </Tag>
+
+ <Tag name="bindings">
+ <Begin>&lt;bindings xmlns="http://www.mozilla.org/xbl"&gt;</Begin>
+ <End>&lt;/bindings&gt;</End>
+ </Tag>
+
+ <Tag name="box">
+ <Begin>&lt;box orient=""&gt;</Begin>
+ <End>&lt;/box&gt;</End>
+ </Tag>
+
+ <Tag name="broadcaster">
+ <Begin>&lt;broadcaster id="" label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="broadcasterset">
+ <Begin>&lt;broadcasterset&gt;</Begin>
+ <End>&lt;/broadcasterset&gt;</End>
+ </Tag>
+
+ <Tag name="button">
+ <Begin>&lt;button id="" label="" oncommand=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="browser">
+ <Begin>&lt;browser src="" type="content" flex="1"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="checkbox">
+ <Begin>&lt;checkbox id="" checked="false" label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="caption">
+ <Begin>&lt;caption label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="colorpicker">
+ <Begin>&lt;colorpicker&gt;</Begin>
+ <End>&lt;/colorpicker&gt;</End>
+ </Tag>
+
+ <Tag name="column">
+ <Begin>&lt;column flex="1"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="columns">
+ <Begin>&lt;columns&gt;</Begin>
+ <End>&lt;/columns&gt;</End>
+ </Tag>
+
+ <Tag name="commandset">
+ <Begin>&lt;commandset id="" commandupdater="true" events="focus" oncommandupdate=""/&gt;</Begin>
+ <End>&lt;/commandset&gt;</End>
+ </Tag>
+
+ <Tag name="command">
+ <Begin>&lt;command id="" oncommand=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="conditions">
+ <Begin>&lt;conditions&gt;</Begin>
+ <End>&lt;/conditions&gt;</End>
+ </Tag>
+
+ <Tag name="content">
+ <Begin>&lt;content uri=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="deck">
+ <Begin>&lt;deck selectedIndex="1"&gt;</Begin>
+ <End>&lt;/deck&gt;</End>
+ </Tag>
+
+ <Tag name="description">
+ <Begin>&lt;description&gt;</Begin>
+ <End>&lt;/description&gt;</End>
+ </Tag>
+
+ <Tag name="dialog">
+ <Begin>&lt;dialog id="" title=""
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="accept,cancel"
+ ondialogaccept=""
+ ondialogcancel=""&gt;</Begin>
+ <End>&lt;/dialog&gt;</End>
+ </Tag>
+
+ <Tag name="dialogheader">
+ <Begin>&lt;dialogheader title="" description=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="editor">
+ <Begin>&lt;editor src=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="grid">
+ <Begin>&lt;grid&gt;</Begin>
+ <End>&lt;/grid&gt;</End>
+ </Tag>
+
+ <Tag name="grippy">
+ <Begin>&lt;grippy&gt;</Begin>
+ <End>&lt;/grippy&gt;</End>
+ </Tag>
+
+ <Tag name="groupbox">
+ <Begin>&lt;groupbox flex="1"&gt;&lt;caption label=""&gt;</Begin>
+ <End>&lt;/groupbox&gt;</End>
+ </Tag>
+
+ <Tag name="hbox">
+ <Begin>&lt;hbox flex="1"&gt;</Begin>
+ <End>&lt;/hbox&gt;</End>
+ </Tag>
+
+ <Tag name="iframe">
+ <Begin>&lt;iframe id="" src="" flex="1"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="image">
+ <Begin>&lt;image id="" src=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="key">
+ <Begin>&lt;key id="" modifiers="control alt" key="" onkeypress=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="keyset">
+ <Begin>&lt;keyset&gt;</Begin>
+ <End>&lt;/keyset&gt;</End>
+ </Tag>
+
+ <Tag name="label">
+ <Begin>&lt;label value="" control=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="listbox">
+ <Begin>&lt;listbox rows=""&gt;</Begin>
+ <End>&lt;/listbox&gt;</End>
+ </Tag>
+
+ <Tag name="listcell">
+ <Begin>&lt;listcell label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="listcol">
+ <Begin>&lt;listcol&gt;</Begin>
+ <End>&lt;/listcol&gt;</End>
+ </Tag>
+
+ <Tag name="listcols">
+ <Begin>&lt;listcols&gt;</Begin>
+ <End>&lt;/listcols&gt;</End>
+ </Tag>
+
+ <Tag name="listhead">
+ <Begin>&lt;listhead&gt;</Begin>
+ <End>&lt;/listhead&gt;</End>
+ </Tag>
+
+ <Tag name="listheader">
+ <Begin>&lt;listheader label="" flex="1"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="listitem">
+ <Begin>&lt;listitem id="" value="" label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="member">
+ <Begin>&lt;member container="" child=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="menu">
+ <Begin>&lt;menu accesskey=""&gt;</Begin>
+ <End>&lt;/menu&gt;</End>
+ </Tag>
+
+ <Tag name="menubar">
+ <Begin>&lt;menubar&gt;</Begin>
+ <End>&lt;/menubar&gt;</End>
+ </Tag>
+
+ <Tag name="menuitem">
+ <Begin>&lt;menuitem value="" label="" accesskey="" acceltext="" oncommand=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="menulist">
+ <Begin>&lt;menulist label=""&gt;</Begin>
+ <End>&lt;/menulist&gt;</End>
+ </Tag>
+
+ <Tag name="menupopup">
+ <Begin>&lt;menupopup id=""&gt;</Begin>
+ <End>&lt;/menupopup&gt;</End>
+ </Tag>
+
+ <Tag name="menuseparator">
+ <Begin>&lt;menuseparator/&gt;</Begin>
+ </Tag>
+
+ <Tag name="observes">
+ <Begin>&lt;observes element="" attribute="" onbroadcast=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="overlay">
+ <Begin>&lt;overlay id=""
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"&gt;</Begin>
+ <End>&lt;/overlay&gt;</End>
+ </Tag>
+
+ <Tag name="page">
+ <Begin>&lt;page&gt;</Begin>
+ <End>&lt;/page&gt;</End>
+ </Tag>
+
+ <Tag name="popup">
+ <Begin>&lt;popup id="" position="after_start"&gt;</Begin>
+ <End>&lt;/popup&gt;</End>
+ </Tag>
+
+ <Tag name="popupset">
+ <Begin>&lt;popupset&gt;</Begin>
+ <End>&lt;/popupset&gt;</End>
+ </Tag>
+
+ <Tag name="preference">
+ <Begin>&lt;preference&gt;</Begin>
+ <End>&lt;/preference&gt;</End>
+ </Tag>
+
+ <Tag name="preferences">
+ <Begin>&lt;preferences&gt;</Begin>
+ <End>&lt;/preferences&gt;</End>
+ </Tag>
+
+ <Tag name="prefpane">
+ <Begin>&lt;prefpane&gt;</Begin>
+ <End>&lt;/prefpane&gt;</End>
+ </Tag>
+
+ <Tag name="prefwindow">
+ <Begin>&lt;prefwindow&gt;</Begin>
+ <End>&lt;/prefwindow&gt;</End>
+ </Tag>
+
+ <Tag name="progressmeter">
+ <Begin>&lt;progressmeter id="" mode="determined" value="50%"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="radio">
+ <Begin>&lt;radio id="" value="" name="" label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="radiogroup">
+ <Begin>&lt;radiogroup&gt;</Begin>
+ <End>&lt;/radiogroup&gt;</End>
+ </Tag>
+
+ <Tag name="resizer">
+ <Begin>&lt;resizer&gt;</Begin>
+ <End>&lt;/resizer&gt;</End>
+ </Tag>
+
+ <Tag name="richlistbox">
+ <Begin>&lt;richlistbox&gt;</Begin>
+ <End>&lt;/richlistbox&gt;</End>
+ </Tag>
+
+ <Tag name="richlistitem">
+ <Begin>&lt;richlistitem&gt;</Begin>
+ <End>&lt;/richlistitem&gt;</End>
+ </Tag>
+
+ <Tag name="row">
+ <Begin>&lt;row flex="1"&gt;</Begin>
+ <End>&lt;/row&gt;</End>
+ </Tag>
+
+ <Tag name="rows">
+ <Begin>&lt;rows&gt;</Begin>
+ <End>&lt;/rows&gt;</End>
+ </Tag>
+
+ <Tag name="rule">
+ <Begin>&lt;rule&gt;</Begin>
+ <End>&lt;/rule&gt;</End>
+ </Tag>
+
+ <Tag name="script">
+ <Begin>&lt;script src="" language="Javascript"&gt;</Begin>
+ <End>&lt;/script&gt;</End>
+ </Tag>
+
+ <Tag name="scrollbar">
+ <Begin>&lt;scrollbar id="" orient="vertical" curpos="20" maxpos="100" increment="1" pageincrement="10"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="scrollbox">
+ <Begin>&lt;scrollbox&gt;</Begin>
+ <End>&lt;/scrollbox&gt;</End>
+ </Tag>
+
+ <Tag name="scrollcorner">
+ <Begin>&lt;scrollcorner&gt;</Begin>
+ <End>&lt;/scrollcorner&gt;</End>
+ </Tag>
+
+ <Tag name="separator">
+ <Begin>&lt;separator&gt;</Begin>
+ <End>&lt;/separator&gt;</End>
+ </Tag>
+
+ <Tag name="spacer">
+ <Begin>&lt;spacer flex="1"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="splitter">
+ <Begin>&lt;splitter state="open" collapse="before" resizebefore="closest" resizeafter="grow"&gt;&lt;grippy/&gt;</Begin>
+ <End>&lt;/splitter&gt;</End>
+ </Tag>
+
+ <Tag name="stack">
+ <Begin>&lt;stack&gt;</Begin>
+ <End>&lt;/stack&gt;</End>
+ </Tag>
+
+ <Tag name="statusbar">
+ <Begin>&lt;statusbar&gt;</Begin>
+ <End>&lt;/statusbar&gt;</End>
+ </Tag>
+
+ <Tag name="statusbarpanel">
+ <Begin>&lt;statusbarpanel id="" label=""&gt;</Begin>
+ <End>&lt;/statusbarpanel&gt;</End>
+ </Tag>
+
+ <Tag name="stringbundle">
+ <Begin>&lt;stringbundle id="" src=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="stringbundleset">
+ <Begin>&lt;stringbundleset&gt;</Begin>
+ <End>&lt;/stringbundleset&gt;</End>
+ </Tag>
+
+ <Tag name="tab">
+ <Begin>&lt;tab id="" label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="tabbrowser">
+ <Begin>&lt;tabbrowser&gt;</Begin>
+ <End>&lt;/tabbrowser&gt;</End>
+ </Tag>
+
+ <Tag name="tabbox">
+ <Begin>&lt;tabbox id=""&gt;</Begin>
+ <End>&lt;/tabbox&gt;</End>
+ </Tag>
+
+ <Tag name="tabpanel">
+ <Begin>&lt;tabpanel id=""&gt;</Begin>
+ <End>&lt;/tabpanel&gt;</End>
+ </Tag>
+
+ <Tag name="tabpanels">
+ <Begin>&lt;tabpanels&gt;</Begin>
+ <End>&lt;/tabpanels&gt;</End>
+ </Tag>
+
+ <Tag name="tabs">
+ <Begin>&lt;tabs&gt;</Begin>
+ <End>&lt;/tabs&gt;</End>
+ </Tag>
+
+ <Tag name="template">
+ <Begin>&lt;template&gt;</Begin>
+ <End>&lt;/template&gt;</End>
+ </Tag>
+
+ <Tag name="textnode">
+ <Begin>&lt;textnode&gt;</Begin>
+ <End>&lt;/textnode&gt;</End>
+ </Tag>
+
+ <Tag name="textbox">
+ <Begin>&lt;textbox id="" value="" multiline="false"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="titlebar">
+ <Begin>&lt;titlebar&gt;</Begin>
+ <End>&lt;/titlebar&gt;</End>
+ </Tag>
+
+ <Tag name="toolbar">
+ <Begin>&lt;toolbar id=""&gt;</Begin>
+ <End>&lt;/toolbar&gt;</End>
+ </Tag>
+
+ <Tag name="toolbarbutton">
+ <Begin>&lt;toolbarbutton id="" label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="toolbargrippy">
+ <Begin>&lt;toolbargrippy&gt;</Begin>
+ <End>&lt;/toolbargrippy&gt;</End>
+ </Tag>
+
+ <Tag name="toolbaritem">
+ <Begin>&lt;toolbaritem&gt;</Begin>
+ <End>&lt;/toolbaritem&gt;</End>
+ </Tag>
+
+ <Tag name="toolbarpalette">
+ <Begin>&lt;toolbarpalette&gt;</Begin>
+ <End>&lt;/toolbarpalette&gt;</End>
+ </Tag>
+
+ <Tag name="toolbarseparator">
+ <Begin>&lt;toolbarseparator&gt;</Begin>
+ <End>&lt;/toolbarseparator&gt;</End>
+ </Tag>
+
+ <Tag name="toolbarset">
+ <Begin>&lt;toolbarset&gt;</Begin>
+ <End>&lt;/toolbarset&gt;</End>
+ </Tag>
+
+ <Tag name="toolbarspacer">
+ <Begin>&lt;toolbarspacer&gt;</Begin>
+ <End>&lt;/toolbarspacer&gt;</End>
+ </Tag>
+
+ <Tag name="toolbarspring">
+ <Begin>&lt;toolbarspring&gt;</Begin>
+ <End>&lt;/toolbarspring&gt;</End>
+ </Tag>
+
+ <Tag name="toolbox">
+ <Begin>&lt;toolbox&gt;</Begin>
+ <End>&lt;/toolbox&gt;</End>
+ </Tag>
+
+ <Tag name="tooltip">
+ <Begin>&lt;tooltip id="" orient="vertical"&gt;</Begin>
+ <End>&lt;/tooltip&gt;</End>
+ </Tag>
+
+ <Tag name="tree">
+ <Begin>&lt;tree id="" flex="1"&gt;</Begin>
+ <End>&lt;/tree&gt;</End>
+ </Tag>
+
+ <Tag name="treecell">
+ <Begin>&lt;treecell label=""/&gt;</Begin>
+ </Tag>
+
+ <Tag name="treechildren">
+ <Begin>&lt;treechildren&gt;</Begin>
+ <End>&lt;/treechildren&gt;</End>
+ </Tag>
+
+ <Tag name="treecol">
+ <Begin>&lt;treecol label="" flex="1" primary="true"/&gt;</Begin>
+ </Tag>
+
+ <Tag name="treecols">
+ <Begin>&lt;treecols&gt;</Begin>
+ <End>&lt;/treecols&gt;</End>
+ </Tag>
+
+ <Tag name="treeitem">
+ <Begin>&lt;treeitem container="true" open="true"&gt;</Begin>
+ <End>&lt;/treeitem&gt;</End>
+ </Tag>
+
+ <Tag name="treerow">
+ <Begin>&lt;treerow&gt;</Begin>
+ <End>&lt;/treerow&gt;</End>
+ </Tag>
+
+ <Tag name="treeseparator">
+ <Begin>&lt;treeseparator&gt;</Begin>
+ <End>&lt;/treeseparator&gt;</End>
+ </Tag>
+
+ <Tag name="triple">
+ <Begin>&lt;triple&gt;</Begin>
+ <End>&lt;/triple&gt;</End>
+ </Tag>
+
+ <Tag name="vbox">
+ <Begin>&lt;vbox flex="1"&gt;</Begin>
+ <End>&lt;/vbox&gt;</End>
+ </Tag>
+
+ <Tag name="window">
+ <Begin>&lt;window id="" title="" orient="horizontal" pageid="" next=""
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"&gt;</Begin>
+ <End>&lt;/window&gt;</End>
+ </Tag>
+
+ <Tag name="wizard">
+ <Begin>&lt;wizard id="" title="" wizardnext="" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"&gt;</Begin>
+ <End>&lt;/wizard&gt;</End>
+ </Tag>
+
+ <Tag name="wizardpage">
+ <Begin>&lt;wizardpage description="" onwizardfinish="" pageadvanced=""&gt;</Begin>
+ <End>&lt;/wizardpage&gt;</End>
+ </Tag>
+
+</TagGroup>
+</TagList>
diff --git a/plugins/taglist/gedit-taglist-plugin-panel.c b/plugins/taglist/gedit-taglist-plugin-panel.c
new file mode 100755
index 00000000..d66acc08
--- /dev/null
+++ b/plugins/taglist/gedit-taglist-plugin-panel.c
@@ -0,0 +1,776 @@
+/*
+ * gedit-taglist-plugin-panel.c
+ * This file is part of gedit
+ *
+ * Copyright (C) 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "gedit-taglist-plugin-panel.h"
+#include "gedit-taglist-plugin-parser.h"
+
+#include <gedit/gedit-utils.h>
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-plugin.h>
+
+#include <gdk/gdkkeysyms.h>
+#include <glib/gi18n.h>
+
+#define GEDIT_TAGLIST_PLUGIN_PANEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, \
+ GeditTaglistPluginPanelPrivate))
+
+enum
+{
+ COLUMN_TAG_NAME,
+ COLUMN_TAG_INDEX_IN_GROUP,
+ NUM_COLUMNS
+};
+
+struct _GeditTaglistPluginPanelPrivate
+{
+ GeditWindow *window;
+
+ GtkWidget *tag_groups_combo;
+ GtkWidget *tags_list;
+ GtkWidget *preview;
+
+ TagGroup *selected_tag_group;
+
+ gchar *data_dir;
+};
+
+GEDIT_PLUGIN_DEFINE_TYPE (GeditTaglistPluginPanel, gedit_taglist_plugin_panel, GTK_TYPE_VBOX)
+
+enum
+{
+ PROP_0,
+ PROP_WINDOW,
+};
+
+static void
+set_window (GeditTaglistPluginPanel *panel,
+ GeditWindow *window)
+{
+ g_return_if_fail (panel->priv->window == NULL);
+ g_return_if_fail (GEDIT_IS_WINDOW (window));
+
+ panel->priv->window = window;
+
+ /* TODO */
+}
+
+static void
+gedit_taglist_plugin_panel_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GeditTaglistPluginPanel *panel = GEDIT_TAGLIST_PLUGIN_PANEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ set_window (panel, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_taglist_plugin_panel_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GeditTaglistPluginPanel *panel = GEDIT_TAGLIST_PLUGIN_PANEL (object);
+
+ switch (prop_id)
+ {
+ case PROP_WINDOW:
+ g_value_set_object (value,
+ GEDIT_TAGLIST_PLUGIN_PANEL_GET_PRIVATE (panel)->window);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gedit_taglist_plugin_panel_finalize (GObject *object)
+{
+ GeditTaglistPluginPanel *panel = GEDIT_TAGLIST_PLUGIN_PANEL (object);
+
+ g_free (panel->priv->data_dir);
+
+ G_OBJECT_CLASS (gedit_taglist_plugin_panel_parent_class)->finalize (object);
+}
+
+static void
+gedit_taglist_plugin_panel_class_init (GeditTaglistPluginPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gedit_taglist_plugin_panel_finalize;
+ object_class->get_property = gedit_taglist_plugin_panel_get_property;
+ object_class->set_property = gedit_taglist_plugin_panel_set_property;
+
+ g_object_class_install_property (object_class,
+ PROP_WINDOW,
+ g_param_spec_object ("window",
+ "Window",
+ "The GeditWindow this GeditTaglistPluginPanel is associated with",
+ GEDIT_TYPE_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (object_class, sizeof(GeditTaglistPluginPanelPrivate));
+}
+
+static void
+insert_tag (GeditTaglistPluginPanel *panel,
+ Tag *tag,
+ gboolean grab_focus)
+{
+ GeditView *view;
+ GtkTextBuffer *buffer;
+ GtkTextIter start, end;
+ GtkTextIter cursor;
+ gboolean sel = FALSE;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ view = gedit_window_get_active_view (panel->priv->window);
+ g_return_if_fail (view != NULL);
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view));
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ /* always insert the begin tag at the beginning of the selection
+ * and the end tag at the end, if there is no selection they will
+ * be automatically inserted at the cursor position.
+ */
+
+ if (tag->begin != NULL)
+ {
+ sel = gtk_text_buffer_get_selection_bounds (buffer,
+ &start,
+ &end);
+
+ gtk_text_buffer_insert (buffer,
+ &start,
+ (gchar *)tag->begin,
+ -1);
+
+ /* get iterators again since they have been invalidated and move
+ * the cursor after the selection */
+ gtk_text_buffer_get_selection_bounds (buffer,
+ &start,
+ &cursor);
+ }
+
+ if (tag->end != NULL)
+ {
+ sel = gtk_text_buffer_get_selection_bounds (buffer,
+ &start,
+ &end);
+
+ gtk_text_buffer_insert (buffer,
+ &end,
+ (gchar *)tag->end,
+ -1);
+
+ /* if there is no selection and we have a paired tag, move the
+ * cursor between the pair, otherwise move it at the end */
+ if (!sel)
+ {
+ gint offset;
+
+ offset = gtk_text_iter_get_offset (&end) -
+ g_utf8_strlen ((gchar *)tag->end, -1);
+
+ gtk_text_buffer_get_iter_at_offset (buffer,
+ &end,
+ offset);
+ }
+
+ cursor = end;
+ }
+
+ gtk_text_buffer_place_cursor (buffer, &cursor);
+
+ gtk_text_buffer_end_user_action (buffer);
+
+ if (grab_focus)
+ gtk_widget_grab_focus (GTK_WIDGET (view));
+}
+
+static void
+tag_list_row_activated_cb (GtkTreeView *tag_list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GeditTaglistPluginPanel *panel)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gint index;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ model = gtk_tree_view_get_model (tag_list);
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ g_return_if_fail (&iter != NULL);
+
+ gtk_tree_model_get (model, &iter, COLUMN_TAG_INDEX_IN_GROUP, &index, -1);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Index: %d", index);
+
+ insert_tag (panel,
+ (Tag*)g_list_nth_data (panel->priv->selected_tag_group->tags, index),
+ TRUE);
+}
+
+static gboolean
+tag_list_key_press_event_cb (GtkTreeView *tag_list,
+ GdkEventKey *event,
+ GeditTaglistPluginPanel *panel)
+{
+ gboolean grab_focus;
+
+ grab_focus = (event->state & GDK_CONTROL_MASK) != 0;
+
+ if (event->keyval == GDK_Return)
+ {
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ gint index;
+
+ gedit_debug_message (DEBUG_PLUGINS, "RETURN Pressed");
+
+ model = gtk_tree_view_get_model (tag_list);
+
+ selection = gtk_tree_view_get_selection (tag_list);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ gtk_tree_model_get (model, &iter, COLUMN_TAG_INDEX_IN_GROUP, &index, -1);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Index: %d", index);
+
+ insert_tag (panel,
+ (Tag*)g_list_nth_data (panel->priv->selected_tag_group->tags, index),
+ grab_focus);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GtkTreeModel*
+create_model (GeditTaglistPluginPanel *panel)
+{
+ gint i = 0;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GList *list;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ /* create list store */
+ store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT);
+
+ /* add data to the list store */
+ list = panel->priv->selected_tag_group->tags;
+
+ while (list != NULL)
+ {
+ const gchar* tag_name;
+
+ tag_name = (gchar *)((Tag*)list->data)->name;
+
+ gedit_debug_message (DEBUG_PLUGINS, "%d : %s", i, tag_name);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_TAG_NAME, tag_name,
+ COLUMN_TAG_INDEX_IN_GROUP, i,
+ -1);
+ ++i;
+
+ list = g_list_next (list);
+ }
+
+ gedit_debug_message (DEBUG_PLUGINS, "Rows: %d ",
+ gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL));
+
+ return GTK_TREE_MODEL (store);
+}
+
+static void
+populate_tags_list (GeditTaglistPluginPanel *panel)
+{
+ GtkTreeModel* model;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (taglist != NULL);
+
+ model = create_model (panel);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (panel->priv->tags_list),
+ model);
+ g_object_unref (model);
+}
+
+static TagGroup *
+find_tag_group (const gchar *name)
+{
+ GList *l;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (taglist != NULL, NULL);
+
+ for (l = taglist->tag_groups; l != NULL; l = g_list_next (l))
+ {
+ if (strcmp (name, (gchar *)((TagGroup*)l->data)->name) == 0)
+ return (TagGroup*)l->data;
+ }
+
+ return NULL;
+}
+
+static void
+populate_tag_groups_combo (GeditTaglistPluginPanel *panel)
+{
+ GList *l;
+ GtkComboBox *combo;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ combo = GTK_COMBO_BOX (panel->priv->tag_groups_combo);
+
+ if (taglist == NULL)
+ return;
+
+ for (l = taglist->tag_groups; l != NULL; l = g_list_next (l))
+ {
+ gtk_combo_box_append_text (combo,
+ (gchar *)((TagGroup*)l->data)->name);
+ }
+
+ gtk_combo_box_set_active (combo, 0);
+
+ return;
+}
+
+static void
+selected_group_changed (GtkComboBox *combo,
+ GeditTaglistPluginPanel *panel)
+{
+ gchar* group_name;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ group_name = gtk_combo_box_get_active_text (combo);
+
+ if ((group_name == NULL) || (strlen (group_name) <= 0))
+ {
+ g_free (group_name);
+ return;
+ }
+
+ if ((panel->priv->selected_tag_group == NULL) ||
+ (strcmp (group_name, (gchar *)panel->priv->selected_tag_group->name) != 0))
+ {
+ panel->priv->selected_tag_group = find_tag_group (group_name);
+ g_return_if_fail (panel->priv->selected_tag_group != NULL);
+
+ gedit_debug_message (DEBUG_PLUGINS,
+ "New selected group: %s",
+ panel->priv->selected_tag_group->name);
+
+ populate_tags_list (panel);
+ }
+
+ /* Clean up preview */
+ gtk_label_set_text (GTK_LABEL (panel->priv->preview),
+ "");
+
+ g_free (group_name);
+}
+
+static gchar *
+create_preview_string (Tag *tag)
+{
+ GString *str;
+
+ str = g_string_new ("<tt><small>");
+
+ if (tag->begin != NULL)
+ {
+ gchar *markup;
+
+ markup = g_markup_escape_text ((gchar *)tag->begin, -1);
+ g_string_append (str, markup);
+ g_free (markup);
+ }
+
+ if (tag->end != NULL)
+ {
+ gchar *markup;
+
+ markup = g_markup_escape_text ((gchar *)tag->end, -1);
+ g_string_append (str, markup);
+ g_free (markup);
+ }
+
+ g_string_append (str, "</small></tt>");
+
+ return g_string_free (str, FALSE);
+}
+
+static void
+update_preview (GeditTaglistPluginPanel *panel,
+ Tag *tag)
+{
+ gchar *str;
+
+ str = create_preview_string (tag);
+
+ gtk_label_set_markup (GTK_LABEL (panel->priv->preview),
+ str);
+
+ g_free (str);
+}
+
+static void
+tag_list_cursor_changed_cb (GtkTreeView *tag_list,
+ gpointer data)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ gint index;
+
+ GeditTaglistPluginPanel *panel = (GeditTaglistPluginPanel *)data;
+
+ model = gtk_tree_view_get_model (tag_list);
+
+ selection = gtk_tree_view_get_selection (tag_list);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ gtk_tree_model_get (model, &iter, COLUMN_TAG_INDEX_IN_GROUP, &index, -1);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Index: %d", index);
+
+ update_preview (panel,
+ (Tag*)g_list_nth_data (panel->priv->selected_tag_group->tags, index));
+ }
+}
+
+static gboolean
+tags_list_query_tooltip_cb (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tip,
+ GtkTooltip *tooltip,
+ GeditTaglistPluginPanel *panel)
+{
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkTreePath *path = NULL;
+ gint index;
+ Tag *tag;
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+
+ if (keyboard_tip)
+ {
+ gtk_tree_view_get_cursor (GTK_TREE_VIEW (widget),
+ &path,
+ NULL);
+
+ if (path == NULL)
+ {
+ return FALSE;
+ }
+ }
+ else
+ {
+ gint bin_x, bin_y;
+
+ gtk_tree_view_convert_widget_to_bin_window_coords (GTK_TREE_VIEW (widget),
+ x, y,
+ &bin_x, &bin_y);
+
+ if (!gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+ bin_x, bin_y,
+ &path,
+ NULL, NULL, NULL))
+ {
+ return FALSE;
+ }
+ }
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter,
+ COLUMN_TAG_INDEX_IN_GROUP, &index,
+ -1);
+
+ tag = g_list_nth_data (panel->priv->selected_tag_group->tags, index);
+ if (tag != NULL)
+ {
+ gchar *tip;
+
+ tip = create_preview_string (tag);
+ gtk_tooltip_set_markup (tooltip, tip);
+ g_free (tip);
+ gtk_tree_path_free (path);
+
+ return TRUE;
+ }
+
+ gtk_tree_path_free (path);
+
+ return FALSE;
+}
+
+static gboolean
+expose_event_cb (GtkWidget *panel,
+ GdkEventExpose *event,
+ gpointer user_data)
+{
+ GeditTaglistPluginPanel *ppanel = GEDIT_TAGLIST_PLUGIN_PANEL (panel);
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ /* If needed load taglists from files at the first expose */
+ if (taglist == NULL)
+ create_taglist (ppanel->priv->data_dir);
+
+ /* And populate combo box */
+ populate_tag_groups_combo (GEDIT_TAGLIST_PLUGIN_PANEL (panel));
+
+ /* We need to manage only the first expose event -> disconnect */
+ g_signal_handlers_disconnect_by_func (panel, expose_event_cb, NULL);
+
+ return FALSE;
+}
+
+static void
+set_combo_tooltip (GtkWidget *widget,
+ gpointer data)
+{
+ if (GTK_IS_BUTTON (widget))
+ {
+ gtk_widget_set_tooltip_text (widget,
+ _("Select the group of tags you want to use"));
+ }
+}
+
+static void
+realize_tag_groups_combo (GtkWidget *combo,
+ gpointer data)
+{
+ gtk_container_forall (GTK_CONTAINER (combo),
+ set_combo_tooltip,
+ NULL);
+}
+
+static void
+add_preview_widget (GeditTaglistPluginPanel *panel)
+{
+ GtkWidget *expander;
+ GtkWidget *frame;
+
+ expander = gtk_expander_new_with_mnemonic (_("_Preview"));
+
+ panel->priv->preview = gtk_label_new (NULL);
+ gtk_widget_set_size_request (panel->priv->preview, -1, 80);
+
+ gtk_label_set_line_wrap (GTK_LABEL (panel->priv->preview), TRUE);
+ gtk_label_set_use_markup (GTK_LABEL (panel->priv->preview), TRUE);
+ gtk_misc_set_alignment (GTK_MISC (panel->priv->preview), 0, 0);
+ gtk_misc_set_padding (GTK_MISC (panel->priv->preview), 6, 6);
+ gtk_label_set_selectable (GTK_LABEL (panel->priv->preview), TRUE);
+ gtk_label_set_selectable (GTK_LABEL (panel->priv->preview), TRUE);
+ gtk_label_set_ellipsize (GTK_LABEL (panel->priv->preview),
+ PANGO_ELLIPSIZE_END);
+
+ frame = gtk_frame_new (0);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+
+ gtk_container_add (GTK_CONTAINER (frame),
+ panel->priv->preview);
+
+ gtk_container_add (GTK_CONTAINER (expander),
+ frame);
+
+ gtk_box_pack_start (GTK_BOX (panel), expander, FALSE, FALSE, 0);
+
+ gtk_widget_show_all (expander);
+}
+
+static void
+gedit_taglist_plugin_panel_init (GeditTaglistPluginPanel *panel)
+{
+ GtkWidget *sw;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GList *focus_chain = NULL;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ panel->priv = GEDIT_TAGLIST_PLUGIN_PANEL_GET_PRIVATE (panel);
+ panel->priv->data_dir = NULL;
+
+ /* Build the window content */
+ panel->priv->tag_groups_combo = gtk_combo_box_new_text ();
+ gtk_box_pack_start (GTK_BOX (panel),
+ panel->priv->tag_groups_combo,
+ FALSE,
+ TRUE,
+ 0);
+
+ g_signal_connect (panel->priv->tag_groups_combo,
+ "realize",
+ G_CALLBACK (realize_tag_groups_combo),
+ panel);
+
+ sw = gtk_scrolled_window_new (NULL, NULL);
+
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
+ GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (panel), sw, TRUE, TRUE, 0);
+
+ /* Create tree view */
+ panel->priv->tags_list = gtk_tree_view_new ();
+
+ gedit_utils_set_atk_name_description (panel->priv->tag_groups_combo,
+ _("Available Tag Lists"),
+ NULL);
+ gedit_utils_set_atk_name_description (panel->priv->tags_list,
+ _("Tags"),
+ NULL);
+ gedit_utils_set_atk_relation (panel->priv->tag_groups_combo,
+ panel->priv->tags_list,
+ ATK_RELATION_CONTROLLER_FOR);
+ gedit_utils_set_atk_relation (panel->priv->tags_list,
+ panel->priv->tag_groups_combo,
+ ATK_RELATION_CONTROLLED_BY);
+
+ gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (panel->priv->tags_list), FALSE);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (panel->priv->tags_list), FALSE);
+
+ g_object_set (panel->priv->tags_list, "has-tooltip", TRUE, NULL);
+
+ /* Add the tags column */
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (_("Tags"),
+ cell,
+ "text",
+ COLUMN_TAG_NAME,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (panel->priv->tags_list),
+ column);
+
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (panel->priv->tags_list),
+ COLUMN_TAG_NAME);
+
+ gtk_container_add (GTK_CONTAINER (sw), panel->priv->tags_list);
+
+ focus_chain = g_list_prepend (focus_chain, panel->priv->tags_list);
+ focus_chain = g_list_prepend (focus_chain, panel->priv->tag_groups_combo);
+
+ gtk_container_set_focus_chain (GTK_CONTAINER (panel),
+ focus_chain);
+ g_list_free (focus_chain);
+
+ add_preview_widget (panel);
+
+ gtk_widget_show_all (GTK_WIDGET (sw));
+ gtk_widget_show (GTK_WIDGET (panel->priv->tag_groups_combo));
+
+ g_signal_connect_after (panel->priv->tags_list,
+ "row_activated",
+ G_CALLBACK (tag_list_row_activated_cb),
+ panel);
+ g_signal_connect (panel->priv->tags_list,
+ "key_press_event",
+ G_CALLBACK (tag_list_key_press_event_cb),
+ panel);
+ g_signal_connect (panel->priv->tags_list,
+ "query-tooltip",
+ G_CALLBACK (tags_list_query_tooltip_cb),
+ panel);
+ g_signal_connect (panel->priv->tags_list,
+ "cursor_changed",
+ G_CALLBACK (tag_list_cursor_changed_cb),
+ panel);
+ g_signal_connect (panel->priv->tag_groups_combo,
+ "changed",
+ G_CALLBACK (selected_group_changed),
+ panel);
+ g_signal_connect (panel,
+ "expose-event",
+ G_CALLBACK (expose_event_cb),
+ NULL);
+}
+
+GtkWidget *
+gedit_taglist_plugin_panel_new (GeditWindow *window,
+ const gchar *data_dir)
+{
+ GeditTaglistPluginPanel *panel;
+
+ g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL);
+
+ panel = g_object_new (GEDIT_TYPE_TAGLIST_PLUGIN_PANEL,
+ "window", window,
+ NULL);
+
+ panel->priv->data_dir = g_strdup (data_dir);
+
+ return GTK_WIDGET (panel);
+}
diff --git a/plugins/taglist/gedit-taglist-plugin-panel.h b/plugins/taglist/gedit-taglist-plugin-panel.h
new file mode 100755
index 00000000..3f8e82d0
--- /dev/null
+++ b/plugins/taglist/gedit-taglist-plugin-panel.h
@@ -0,0 +1,89 @@
+/*
+ * gedit-taglist-plugin-panel.h
+ * This file is part of gedit
+ *
+ * Copyright (C) 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __GEDIT_TAGLIST_PLUGIN_PANEL_H__
+#define __GEDIT_TAGLIST_PLUGIN_PANEL_H__
+
+#include <gtk/gtk.h>
+
+#include <gedit/gedit-window.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_TAGLIST_PLUGIN_PANEL (gedit_taglist_plugin_panel_get_type())
+#define GEDIT_TAGLIST_PLUGIN_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, GeditTaglistPluginPanel))
+#define GEDIT_TAGLIST_PLUGIN_PANEL_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, GeditTaglistPluginPanel const))
+#define GEDIT_TAGLIST_PLUGIN_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, GeditTaglistPluginPanelClass))
+#define GEDIT_IS_TAGLIST_PLUGIN_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL))
+#define GEDIT_IS_TAGLIST_PLUGIN_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL))
+#define GEDIT_TAGLIST_PLUGIN_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_TAGLIST_PLUGIN_PANEL, GeditTaglistPluginPanelClass))
+
+/* Private structure type */
+typedef struct _GeditTaglistPluginPanelPrivate GeditTaglistPluginPanelPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditTaglistPluginPanel GeditTaglistPluginPanel;
+
+struct _GeditTaglistPluginPanel
+{
+ GtkVBox vbox;
+
+ /*< private > */
+ GeditTaglistPluginPanelPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditTaglistPluginPanelClass GeditTaglistPluginPanelClass;
+
+struct _GeditTaglistPluginPanelClass
+{
+ GtkVBoxClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_taglist_plugin_panel_register_type (GTypeModule *module);
+
+GType gedit_taglist_plugin_panel_get_type (void) G_GNUC_CONST;
+
+GtkWidget *gedit_taglist_plugin_panel_new (GeditWindow *window,
+ const gchar *data_dir);
+
+G_END_DECLS
+
+#endif /* __GEDIT_TAGLIST_PLUGIN_PANEL_H__ */
diff --git a/plugins/taglist/gedit-taglist-plugin-parser.c b/plugins/taglist/gedit-taglist-plugin-parser.c
new file mode 100755
index 00000000..e09c0e25
--- /dev/null
+++ b/plugins/taglist/gedit-taglist-plugin-parser.c
@@ -0,0 +1,655 @@
+/*
+ * gedit-taglist-plugin-parser.c
+ * This file is part of gedit
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+/* FIXME: we should rewrite the parser to avoid using DOM */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <libxml/parser.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <gedit/gedit-debug.h>
+
+#include "gedit-taglist-plugin-parser.h"
+
+/* we screwed up so we still look here for compatibility */
+#define USER_GEDIT_TAGLIST_PLUGIN_LOCATION_LEGACY ".gedit-2/plugins/taglist/"
+#define USER_GEDIT_TAGLIST_PLUGIN_LOCATION "gedit/taglist/"
+
+TagList *taglist = NULL;
+static gint taglist_ref_count = 0;
+
+static gboolean parse_tag (Tag *tag, xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur);
+static gboolean parse_tag_group (TagGroup *tg, const gchar *fn,
+ xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur,
+ gboolean sort);
+static TagGroup* get_tag_group (const gchar* filename, xmlDocPtr doc,
+ xmlNsPtr ns, xmlNodePtr cur);
+static TagList* lookup_best_lang (TagList *taglist, const gchar *filename,
+ xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur);
+static TagList *parse_taglist_file (const gchar* filename);
+static TagList *parse_taglist_dir (const gchar *dir);
+
+static void free_tag (Tag *tag);
+static void free_tag_group (TagGroup *tag_group);
+
+static gboolean
+parse_tag (Tag *tag, xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur)
+{
+ /*
+ gedit_debug_message (DEBUG_PLUGINS, " Tag name: %s", tag->name);
+ */
+ /* We don't care what the top level element name is */
+ cur = cur->xmlChildrenNode;
+
+ while (cur != NULL)
+ {
+ if ((!xmlStrcmp (cur->name, (const xmlChar *)"Begin")) &&
+ (cur->ns == ns))
+ {
+ tag->begin = xmlNodeListGetString (doc, cur->xmlChildrenNode, 1);
+ /*
+ gedit_debug_message (DEBUG_PLUGINS, " - Begin: %s", tag->begin);
+ */
+ }
+
+ if ((!xmlStrcmp (cur->name, (const xmlChar *)"End")) &&
+ (cur->ns == ns))
+ {
+ tag->end = xmlNodeListGetString (doc, cur->xmlChildrenNode, 1);
+ /*
+ gedit_debug_message (DEBUG_PLUGINS, " - End: %s", tag->end);
+ */
+ }
+
+ cur = cur->next;
+ }
+
+ if ((tag->begin == NULL) && (tag->end == NULL))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gint
+tags_cmp (gconstpointer a, gconstpointer b)
+{
+ gchar *tag_a = (gchar*)((Tag *)a)->name;
+ gchar *tag_b = (gchar*)((Tag *)b)->name;
+
+ return g_utf8_collate (tag_a, tag_b);
+}
+
+static gboolean
+parse_tag_group (TagGroup *tg, const gchar* fn, xmlDocPtr doc,
+ xmlNsPtr ns, xmlNodePtr cur, gboolean sort)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "Parse TagGroup: %s", tg->name);
+
+ /* We don't care what the top level element name is */
+ cur = cur->xmlChildrenNode;
+
+ while (cur != NULL)
+ {
+ if ((xmlStrcmp (cur->name, (const xmlChar *) "Tag")) || (cur->ns != ns))
+ {
+ g_warning ("The tag list file '%s' is of the wrong type, "
+ "was '%s', 'Tag' expected.", fn, cur->name);
+
+ return FALSE;
+ }
+ else
+ {
+ Tag *tag;
+
+ tag = g_new0 (Tag, 1);
+
+ /* Get Tag name */
+ tag->name = xmlGetProp (cur, (const xmlChar *) "name");
+
+ if (tag->name == NULL)
+ {
+ /* Error: No name */
+ g_warning ("The tag list file '%s' is of the wrong type, "
+ "Tag without name.", fn);
+
+ g_free (tag);
+
+ return FALSE;
+ }
+ else
+ {
+ /* Parse Tag */
+ if (parse_tag (tag, doc, ns, cur))
+ {
+ /* Prepend Tag to TagGroup */
+ tg->tags = g_list_prepend (tg->tags, tag);
+ }
+ else
+ {
+ /* Error parsing Tag */
+ g_warning ("The tag list file '%s' is of the wrong type, "
+ "error parsing Tag '%s' in TagGroup '%s'.",
+ fn, tag->name, tg->name);
+
+ free_tag (tag);
+
+ return FALSE;
+ }
+ }
+ }
+
+ cur = cur->next;
+ }
+
+ if (sort)
+ tg->tags = g_list_sort (tg->tags, tags_cmp);
+ else
+ tg->tags = g_list_reverse (tg->tags);
+
+ return TRUE;
+}
+
+static TagGroup*
+get_tag_group (const gchar* filename, xmlDocPtr doc,
+ xmlNsPtr ns, xmlNodePtr cur)
+{
+ TagGroup *tag_group;
+ xmlChar *sort_str;
+ gboolean sort = FALSE;
+
+ tag_group = g_new0 (TagGroup, 1);
+
+ /* Get TagGroup name */
+ tag_group->name = xmlGetProp (cur, (const xmlChar *) "name");
+
+ sort_str = xmlGetProp (cur, (const xmlChar *) "sort");
+
+ if ((sort_str != NULL) &&
+ ((xmlStrcasecmp (sort_str, (const xmlChar *) "yes") == 0) ||
+ (xmlStrcasecmp (sort_str, (const xmlChar *) "true") == 0) ||
+ (xmlStrcasecmp (sort_str, (const xmlChar *) "1") == 0)))
+ {
+ sort = TRUE;
+ }
+
+ xmlFree(sort_str);
+
+ if (tag_group->name == NULL)
+ {
+ /* Error: No name */
+ g_warning ("The tag list file '%s' is of the wrong type, "
+ "TagGroup without name.", filename);
+
+ g_free (tag_group);
+ }
+ else
+ {
+ /* Name found */
+ gboolean exists = FALSE;
+ GList *t = taglist->tag_groups;
+
+ /* Check if the tag group already exists */
+ while (t && !exists)
+ {
+ gchar *tgn = (gchar*)((TagGroup*)(t->data))->name;
+
+ if (strcmp (tgn, (gchar*)tag_group->name) == 0)
+ {
+ gedit_debug_message (DEBUG_PLUGINS,
+ "Tag group '%s' already exists.", tgn);
+
+ exists = TRUE;
+
+ free_tag_group (tag_group);
+ }
+
+ t = g_list_next (t);
+ }
+
+ if (!exists)
+ {
+ /* Parse tag group */
+ if (parse_tag_group (tag_group, filename, doc, ns, cur, sort))
+ {
+ return tag_group;
+ }
+ else
+ {
+ /* Error parsing TagGroup */
+ g_warning ("The tag list file '%s' is of the wrong type, "
+ "error parsing TagGroup '%s'.",
+ filename, tag_group->name);
+
+ free_tag_group (tag_group);
+ }
+ }
+ }
+ return NULL;
+}
+
+static gint
+groups_cmp (gconstpointer a, gconstpointer b)
+{
+ gchar *g_a = (gchar *)((TagGroup *)a)->name;
+ gchar *g_b = (gchar *)((TagGroup *)b)->name;
+
+ return g_utf8_collate (g_a, g_b);
+}
+
+/*
+ * tags file is localized by intltool-merge below.
+ *
+ * <gedit:TagGroup name="XSLT - Elements">
+ * </gedit:TagGroup>
+ * <gedit:TagGroup xml:lang="am" name="LOCALIZED TEXT">
+ * </gedit:TagGroup>
+ * <gedit:TagGroup xml:lang="ar" name="LOCALIZED TEXT">
+ * </gedit:TagGroup>
+ * .....
+ * <gedit:TagGroup name="XSLT - Functions">
+ * </gedit:TagGroup>
+ * .....
+ * Therefore need to pick up the best lang on the current locale.
+ */
+static TagList*
+lookup_best_lang (TagList *taglist, const gchar *filename,
+ xmlDocPtr doc, xmlNsPtr ns, xmlNodePtr cur)
+{
+
+ TagGroup *best_tag_group = NULL;
+ TagGroup *tag_group;
+ gint best_lanking = -1;
+
+ /*
+ * Walk the tree.
+ *
+ * First level we expect a list TagGroup
+ */
+ cur = cur->xmlChildrenNode;
+
+ while (cur != NULL)
+ {
+ if ((xmlStrcmp (cur->name, (const xmlChar *) "TagGroup")) || (cur->ns != ns))
+ {
+ g_warning ("The tag list file '%s' is of the wrong type, "
+ "was '%s', 'TagGroup' expected.", filename, cur->name);
+ xmlFreeDoc (doc);
+
+ return taglist;
+ }
+ else
+ {
+ const char * const *langs_pointer;
+ gchar *lang;
+ gint cur_lanking;
+ gint i;
+
+ langs_pointer = g_get_language_names ();
+
+ lang = (gchar*) xmlGetProp (cur, (const xmlChar*) "lang");
+ cur_lanking = 1;
+
+ /*
+ * When found a new TagGroup, prepend the best
+ * tag_group to taglist. In the current intltool-merge,
+ * the first section is the default lang NULL.
+ */
+ if (lang == NULL) {
+ if (best_tag_group != NULL) {
+ taglist->tag_groups =
+ g_list_prepend (taglist->tag_groups, best_tag_group);
+ }
+
+ best_tag_group = NULL;
+ best_lanking = -1;
+ }
+
+ /*
+ * If already find the best TagGroup on the current
+ * locale, ignore the logic.
+ */
+ if (best_lanking != -1 && best_lanking <= cur_lanking) {
+ cur = cur->next;
+ continue;
+ }
+
+ /* try to find the best lang */
+ for (i = 0; langs_pointer[i] != NULL; i++)
+ {
+ const gchar *best_lang = langs_pointer[i];
+
+ /*
+ * if launch on C, POSIX locale or does
+ * not find the best lang on the current locale,
+ * this is called.
+ * g_get_language_names returns lang
+ * lists with C locale.
+ */
+ if (lang == NULL &&
+ (!g_ascii_strcasecmp (best_lang, "C") ||
+ !g_ascii_strcasecmp (best_lang, "POSIX")))
+ {
+ tag_group = get_tag_group (filename, doc, ns, cur);
+ if (tag_group != NULL)
+ {
+ if (best_tag_group !=NULL)
+ free_tag_group (best_tag_group);
+ best_lanking = cur_lanking;
+ best_tag_group = tag_group;
+ }
+ }
+
+ /* if it is possible the best lang is not C */
+ else if (lang == NULL)
+ {
+ cur_lanking++;
+ continue;
+ }
+
+ /* if the best lang is found */
+ else if (!g_ascii_strcasecmp (best_lang, lang))
+ {
+ tag_group = get_tag_group (filename, doc, ns, cur);
+ if (tag_group != NULL)
+ {
+ if (best_tag_group !=NULL)
+ free_tag_group (best_tag_group);
+ best_lanking = cur_lanking;
+ best_tag_group = tag_group;
+ }
+ }
+
+ cur_lanking++;
+ }
+
+ if (lang) g_free (lang);
+ } /* End of else */
+
+ cur = cur->next;
+ } /* End of while (cur != NULL) */
+
+ /* Prepend TagGroup to TagList */
+ if (best_tag_group != NULL) {
+ taglist->tag_groups =
+ g_list_prepend (taglist->tag_groups, best_tag_group);
+ }
+
+ taglist->tag_groups = g_list_sort (taglist->tag_groups, groups_cmp);
+
+ return taglist;
+}
+
+static TagList *
+parse_taglist_file (const gchar* filename)
+{
+ xmlDocPtr doc;
+
+ xmlNsPtr ns;
+ xmlNodePtr cur;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Parse file: %s", filename);
+
+ xmlKeepBlanksDefault (0);
+
+ /*
+ * build an XML tree from a the file;
+ */
+ doc = xmlParseFile (filename);
+ if (doc == NULL)
+ {
+ g_warning ("The tag list file '%s' is empty.", filename);
+
+ return taglist;
+ }
+
+ /*
+ * Check the document is of the right kind
+ */
+
+ cur = xmlDocGetRootElement (doc);
+
+ if (cur == NULL)
+ {
+ g_warning ("The tag list file '%s' is empty.", filename);
+ xmlFreeDoc(doc);
+ return taglist;
+ }
+
+ ns = xmlSearchNsByHref (doc, cur,
+ (const xmlChar *) "http://gedit.sourceforge.net/some-location");
+
+ if (ns == NULL)
+ {
+ g_warning ("The tag list file '%s' is of the wrong type, "
+ "gedit namespace not found.", filename);
+ xmlFreeDoc (doc);
+
+ return taglist;
+ }
+
+ if (xmlStrcmp(cur->name, (const xmlChar *) "TagList"))
+ {
+ g_warning ("The tag list file '%s' is of the wrong type, "
+ "root node != TagList.", filename);
+ xmlFreeDoc (doc);
+
+ return taglist;
+ }
+
+ /*
+ * If needed, allocate taglist
+ */
+
+ if (taglist == NULL)
+ taglist = g_new0 (TagList, 1);
+
+ taglist = lookup_best_lang (taglist, filename, doc, ns, cur);
+
+ xmlFreeDoc (doc);
+
+ gedit_debug_message (DEBUG_PLUGINS, "END");
+
+ return taglist;
+}
+
+static void
+free_tag (Tag *tag)
+{
+ /*
+ gedit_debug_message (DEBUG_PLUGINS, "Tag: %s", tag->name);
+ */
+ g_return_if_fail (tag != NULL);
+
+ free (tag->name);
+
+ if (tag->begin != NULL)
+ free (tag->begin);
+
+ if (tag->end != NULL)
+ free (tag->end);
+
+ g_free (tag);
+}
+
+static void
+free_tag_group (TagGroup *tag_group)
+{
+ GList *l;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Tag group: %s", tag_group->name);
+
+ g_return_if_fail (tag_group != NULL);
+
+ free (tag_group->name);
+
+ for (l = tag_group->tags; l != NULL; l = g_list_next (l))
+ {
+ free_tag ((Tag *) l->data);
+ }
+
+ g_list_free (tag_group->tags);
+ g_free (tag_group);
+
+ gedit_debug_message (DEBUG_PLUGINS, "END");
+}
+
+void
+free_taglist (void)
+{
+ GList *l;
+
+ gedit_debug_message (DEBUG_PLUGINS, "ref_count: %d", taglist_ref_count);
+
+ if (taglist == NULL)
+ return;
+
+ g_return_if_fail (taglist_ref_count > 0);
+
+ --taglist_ref_count;
+ if (taglist_ref_count > 0)
+ return;
+
+ for (l = taglist->tag_groups; l != NULL; l = g_list_next (l))
+ {
+ free_tag_group ((TagGroup *) l->data);
+ }
+
+ g_list_free (taglist->tag_groups);
+ g_free (taglist);
+ taglist = NULL;
+
+ gedit_debug_message (DEBUG_PLUGINS, "Really freed");
+}
+
+static TagList *
+parse_taglist_dir (const gchar *dir)
+{
+ GError *error = NULL;
+ GDir *d;
+ const gchar *dirent;
+
+ gedit_debug_message (DEBUG_PLUGINS, "DIR: %s", dir);
+
+ d = g_dir_open (dir, 0, &error);
+ if (!d)
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "%s", error->message);
+ g_error_free (error);
+ return taglist;
+ }
+
+ while ((dirent = g_dir_read_name (d)))
+ {
+ if (g_str_has_suffix (dirent, ".tags") ||
+ g_str_has_suffix (dirent, ".tags.gz"))
+ {
+ gchar *tags_file = g_build_filename (dir, dirent, NULL);
+ parse_taglist_file (tags_file);
+ g_free (tags_file);
+ }
+ }
+
+ g_dir_close (d);
+
+ return taglist;
+}
+
+TagList* create_taglist (const gchar *data_dir)
+{
+ gchar *pdir;
+
+ gedit_debug_message (DEBUG_PLUGINS, "ref_count: %d", taglist_ref_count);
+
+ if (taglist_ref_count > 0)
+ {
+ ++taglist_ref_count;
+
+ return taglist;
+ }
+
+#ifndef G_OS_WIN32
+ const gchar *home;
+ const gchar *envvar;
+
+ /* load user's taglists */
+
+ /* legacy dir */
+ home = g_get_home_dir ();
+ if (home != NULL)
+ {
+ pdir = g_build_filename (home,
+ USER_GEDIT_TAGLIST_PLUGIN_LOCATION_LEGACY,
+ NULL);
+ parse_taglist_dir (pdir);
+ g_free (pdir);
+ }
+
+ /* Support old libmate env var */
+ envvar = g_getenv ("MATE22_USER_DIR");
+ if (envvar != NULL)
+ {
+ pdir = g_build_filename (envvar,
+ USER_GEDIT_TAGLIST_PLUGIN_LOCATION,
+ NULL);
+ parse_taglist_dir (pdir);
+ g_free (pdir);
+ }
+ else if (home != NULL)
+ {
+ pdir = g_build_filename (home,
+ ".mate2",
+ USER_GEDIT_TAGLIST_PLUGIN_LOCATION,
+ NULL);
+ parse_taglist_dir (pdir);
+ g_free (pdir);
+ }
+
+#else
+ pdir = g_build_filename (g_get_user_config_dir (),
+ "gedit",
+ "taglist",
+ NULL);
+ parse_taglist_dir (pdir);
+ g_free (pdir);
+#endif
+
+ /* load system's taglists */
+ parse_taglist_dir (data_dir);
+
+ ++taglist_ref_count;
+ g_return_val_if_fail (taglist_ref_count == 1, taglist);
+
+ return taglist;
+}
diff --git a/plugins/taglist/gedit-taglist-plugin-parser.h b/plugins/taglist/gedit-taglist-plugin-parser.h
new file mode 100755
index 00000000..d008836e
--- /dev/null
+++ b/plugins/taglist/gedit-taglist-plugin-parser.h
@@ -0,0 +1,68 @@
+/*
+ * gedit-taglist-plugin-parser.h
+ * This file is part of gedit
+ *
+ * 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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the gedit Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __GEDIT_TAGLIST_PLUGIN_PARSER_H__
+#define __GEDIT_TAGLIST_PLUGIN_PARSER_H__
+
+#include <libxml/tree.h>
+#include <glib.h>
+
+typedef struct _TagList TagList;
+typedef struct _TagGroup TagGroup;
+typedef struct _Tag Tag;
+
+struct _TagList
+{
+ GList *tag_groups;
+};
+
+struct _TagGroup
+{
+ xmlChar *name;
+
+ GList *tags;
+};
+
+struct _Tag
+{
+ xmlChar *name;
+ xmlChar *begin;
+ xmlChar *end;
+};
+
+/* Note that the taglist is ref counted */
+extern TagList *taglist;
+
+TagList* create_taglist (const gchar *data_dir);
+
+void free_taglist (void);
+
+#endif /* __GEDIT_TAGLIST_PLUGIN_PARSER_H__ */
+
diff --git a/plugins/taglist/gedit-taglist-plugin.c b/plugins/taglist/gedit-taglist-plugin.c
new file mode 100755
index 00000000..081fefcb
--- /dev/null
+++ b/plugins/taglist/gedit-taglist-plugin.c
@@ -0,0 +1,160 @@
+/*
+ * gedit-taglist-plugin.h
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+/*
+ * Modified by the gedit Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gedit-taglist-plugin.h"
+#include "gedit-taglist-plugin-panel.h"
+#include "gedit-taglist-plugin-parser.h"
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+
+#include <gedit/gedit-plugin.h>
+#include <gedit/gedit-debug.h>
+
+#define WINDOW_DATA_KEY "GeditTaglistPluginWindowData"
+
+#define GEDIT_TAGLIST_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GEDIT_TYPE_TAGLIST_PLUGIN, GeditTaglistPluginPrivate))
+
+struct _GeditTaglistPluginPrivate
+{
+ gpointer dummy;
+};
+
+GEDIT_PLUGIN_REGISTER_TYPE_WITH_CODE (GeditTaglistPlugin, gedit_taglist_plugin,
+ gedit_taglist_plugin_panel_register_type (module);
+)
+
+static void
+gedit_taglist_plugin_init (GeditTaglistPlugin *plugin)
+{
+ plugin->priv = GEDIT_TAGLIST_PLUGIN_GET_PRIVATE (plugin);
+
+ gedit_debug_message (DEBUG_PLUGINS, "GeditTaglistPlugin initializing");
+}
+
+static void
+gedit_taglist_plugin_finalize (GObject *object)
+{
+/*
+ GeditTaglistPlugin *plugin = GEDIT_TAGLIST_PLUGIN (object);
+*/
+ gedit_debug_message (DEBUG_PLUGINS, "GeditTaglistPlugin finalizing");
+
+ free_taglist ();
+
+ G_OBJECT_CLASS (gedit_taglist_plugin_parent_class)->finalize (object);
+}
+
+static void
+impl_activate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GeditPanel *side_panel;
+ GtkWidget *taglist_panel;
+ gchar *data_dir;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY) == NULL);
+
+ side_panel = gedit_window_get_side_panel (window);
+
+ data_dir = gedit_plugin_get_data_dir (plugin);
+ taglist_panel = gedit_taglist_plugin_panel_new (window, data_dir);
+ g_free (data_dir);
+
+ gedit_panel_add_item_with_stock_icon (side_panel,
+ taglist_panel,
+ _("Tags"),
+ GTK_STOCK_ADD);
+
+ g_object_set_data (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ taglist_panel);
+}
+
+static void
+impl_deactivate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GeditPanel *side_panel;
+ gpointer data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ side_panel = gedit_window_get_side_panel (window);
+
+ gedit_panel_remove_item (side_panel,
+ GTK_WIDGET (data));
+
+ g_object_set_data (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ NULL);
+}
+
+static void
+impl_update_ui (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ gpointer data;
+ GeditView *view;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ view = gedit_window_get_active_view (window);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (data),
+ (view != NULL) &&
+ gtk_text_view_get_editable (GTK_TEXT_VIEW (view)));
+}
+
+static void
+gedit_taglist_plugin_class_init (GeditTaglistPluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ object_class->finalize = gedit_taglist_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+ plugin_class->update_ui = impl_update_ui;
+
+ g_type_class_add_private (object_class, sizeof (GeditTaglistPluginPrivate));
+}
diff --git a/plugins/taglist/gedit-taglist-plugin.h b/plugins/taglist/gedit-taglist-plugin.h
new file mode 100755
index 00000000..d0444e76
--- /dev/null
+++ b/plugins/taglist/gedit-taglist-plugin.h
@@ -0,0 +1,85 @@
+/*
+ * gedit-taglist-plugin.h
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+/*
+ * Modified by the gedit Team, 2002-2005. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ *
+ * $Id$
+ */
+
+#ifndef __GEDIT_TAGLIST_PLUGIN_H__
+#define __GEDIT_TAGLIST_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_TAGLIST_PLUGIN (gedit_taglist_plugin_get_type ())
+#define GEDIT_TAGLIST_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_TAGLIST_PLUGIN, GeditTaglistPlugin))
+#define GEDIT_TAGLIST_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_TAGLIST_PLUGIN, GeditTaglistPluginClass))
+#define GEDIT_IS_TAGLIST_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_TAGLIST_PLUGIN))
+#define GEDIT_IS_TAGLIST_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_TAGLIST_PLUGIN))
+#define GEDIT_TAGLIST_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_TAGLIST_PLUGIN, GeditTaglistPluginClass))
+
+/* Private structure type */
+typedef struct _GeditTaglistPluginPrivate GeditTaglistPluginPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditTaglistPlugin GeditTaglistPlugin;
+
+struct _GeditTaglistPlugin
+{
+ GeditPlugin parent_instance;
+
+ /*< private >*/
+ GeditTaglistPluginPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditTaglistPluginClass GeditTaglistPluginClass;
+
+struct _GeditTaglistPluginClass
+{
+ GeditPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_taglist_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_TAGLIST_PLUGIN_H__ */
diff --git a/plugins/taglist/taglist.gedit-plugin.desktop.in b/plugins/taglist/taglist.gedit-plugin.desktop.in
new file mode 100755
index 00000000..09d0f2d0
--- /dev/null
+++ b/plugins/taglist/taglist.gedit-plugin.desktop.in
@@ -0,0 +1,8 @@
+[Gedit Plugin]
+Module=taglist
+IAge=2
+_Name=Tag list
+_Description=Provides a method to easily insert commonly used tags/strings into a document without having to type them.
+Authors=Paolo Maggi <[email protected]>
+Copyright=Copyright © 2002-2005 Paolo Maggi
+Website=http://www.gedit.org
diff --git a/plugins/time/Makefile.am b/plugins/time/Makefile.am
new file mode 100755
index 00000000..c532f7d4
--- /dev/null
+++ b/plugins/time/Makefile.am
@@ -0,0 +1,36 @@
+# time plugin
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ $(GEDIT_CFLAGS) \
+ $(WARN_CFLAGS) \
+ $(DISABLE_DEPRECATED_CFLAGS)
+
+plugin_LTLIBRARIES = libtime.la
+
+libtime_la_SOURCES = \
+ gedit-time-plugin.h \
+ gedit-time-plugin.c
+
+libtime_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libtime_la_LIBADD = $(GEDIT_LIBS)
+
+uidir = $(GEDIT_PLUGINS_DATA_DIR)/time
+ui_DATA = \
+ gedit-time-dialog.ui \
+ gedit-time-setup-dialog.ui
+
+plugin_in_files = time.gedit-plugin.desktop.in
+
+%.gedit-plugin: %.gedit-plugin.desktop.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.gedit-plugin.desktop.in=.gedit-plugin)
+
+EXTRA_DIST = $(ui_DATA) $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
+
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/time/gedit-time-dialog.ui b/plugins/time/gedit-time-dialog.ui
new file mode 100755
index 00000000..398e9b98
--- /dev/null
+++ b/plugins/time/gedit-time-dialog.ui
@@ -0,0 +1,297 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <object class="GtkDialog" id="choose_format_dialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Insert Date and Time</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="helpbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="cancelbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="okbutton1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xscale">0</property>
+ <property name="yscale">0</property>
+ <child>
+ <object class="GtkHBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="stock">mate-stock-timer</property>
+ <property name="icon_size">4</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Insert</property>
+ <property name="use_underline">True</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox7">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkVBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="use_sel_format_radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Use the _selected format</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="choice_list">
+ <property name="visible">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox9">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkHBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkRadioButton" id="use_custom_radiobutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Use custom format</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">use_sel_format_radiobutton</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="custom_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">%d/%m/%Y %H:%M:%S</property>
+ <property name="has_frame">True</property>
+ <property name="activates_default">False</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="custom_format_example">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">01/11/2009 17:52:00</property>
+ <property name="use_underline">False</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <attributes>
+ <attribute name="scale" value="0.8"/><!-- PANGO_SCALE_SMALL -->
+ </attributes>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-11">helpbutton1</action-widget>
+ <action-widget response="-6">cancelbutton1</action-widget>
+ <action-widget response="-5">okbutton1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/time/gedit-time-plugin.c b/plugins/time/gedit-time-plugin.c
new file mode 100755
index 00000000..42bf6cb9
--- /dev/null
+++ b/plugins/time/gedit-time-plugin.c
@@ -0,0 +1,1272 @@
+/*
+ * gedit-time-plugin.c
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+/*
+ * Modified by the gedit Team, 2002. See the AUTHORS file for a
+ * list of people on the gedit Team.
+ * See the ChangeLog files for a list of changes.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <time.h>
+
+#include <mateconf/mateconf-client.h>
+
+#include "gedit-time-plugin.h"
+#include <gedit/gedit-help.h>
+
+#include <glib/gi18n-lib.h>
+#include <glib.h>
+#include <gmodule.h>
+
+#include <gedit/gedit-debug.h>
+#include <gedit/gedit-utils.h>
+
+#define GEDIT_TIME_PLUGIN_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ GEDIT_TYPE_TIME_PLUGIN, \
+ GeditTimePluginPrivate))
+
+#define WINDOW_DATA_KEY "GeditTimePluginWindowData"
+#define MENU_PATH "/MenuBar/EditMenu/EditOps_4"
+
+/* mateconf keys */
+#define TIME_BASE_KEY "/apps/gedit-2/plugins/time"
+#define PROMPT_TYPE_KEY TIME_BASE_KEY "/prompt_type"
+#define SELECTED_FORMAT_KEY TIME_BASE_KEY "/selected_format"
+#define CUSTOM_FORMAT_KEY TIME_BASE_KEY "/custom_format"
+
+#define DEFAULT_CUSTOM_FORMAT "%d/%m/%Y %H:%M:%S"
+
+static const gchar *formats[] =
+{
+ "%c",
+ "%x",
+ "%X",
+ "%x %X",
+ "%Y-%m-%d %H:%M:%S",
+ "%a %b %d %H:%M:%S %Z %Y",
+ "%a %b %d %H:%M:%S %Y",
+ "%a %d %b %Y %H:%M:%S %Z",
+ "%a %d %b %Y %H:%M:%S",
+ "%d/%m/%Y",
+ "%d/%m/%y",
+#ifndef G_OS_WIN32
+ "%D", /* This one is not supported on win32 */
+#endif
+ "%A %d %B %Y",
+ "%A %B %d %Y",
+ "%Y-%m-%d",
+ "%d %B %Y",
+ "%B %d, %Y",
+ "%A %b %d",
+ "%H:%M:%S",
+ "%H:%M",
+ "%I:%M:%S %p",
+ "%I:%M %p",
+ "%H.%M.%S",
+ "%H.%M",
+ "%I.%M.%S %p",
+ "%I.%M %p",
+ "%d/%m/%Y %H:%M:%S",
+ "%d/%m/%y %H:%M:%S",
+#if __GLIBC__ >= 2
+ "%a, %d %b %Y %H:%M:%S %z",
+#endif
+ NULL
+};
+
+enum
+{
+ COLUMN_FORMATS = 0,
+ COLUMN_INDEX,
+ NUM_COLUMNS
+};
+
+typedef struct _TimeConfigureDialog TimeConfigureDialog;
+
+struct _TimeConfigureDialog
+{
+ GtkWidget *dialog;
+
+ GtkWidget *list;
+
+ /* Radio buttons to indicate what should be done */
+ GtkWidget *prompt;
+ GtkWidget *use_list;
+ GtkWidget *custom;
+
+ GtkWidget *custom_entry;
+ GtkWidget *custom_format_example;
+
+ /* Info needed for the response handler */
+ GeditTimePlugin *plugin;
+};
+
+typedef struct _ChooseFormatDialog ChooseFormatDialog;
+
+struct _ChooseFormatDialog
+{
+ GtkWidget *dialog;
+
+ GtkWidget *list;
+
+ /* Radio buttons to indicate what should be done */
+ GtkWidget *use_list;
+ GtkWidget *custom;
+
+ GtkWidget *custom_entry;
+ GtkWidget *custom_format_example;
+
+ /* Info needed for the response handler */
+ GtkTextBuffer *buffer;
+ GeditTimePlugin *plugin;
+};
+
+typedef enum
+{
+ PROMPT_SELECTED_FORMAT = 0, /* Popup dialog with list preselected */
+ PROMPT_CUSTOM_FORMAT, /* Popup dialog with entry preselected */
+ USE_SELECTED_FORMAT, /* Use selected format directly */
+ USE_CUSTOM_FORMAT /* Use custom format directly */
+} GeditTimePluginPromptType;
+
+struct _GeditTimePluginPrivate
+{
+ MateConfClient *mateconf_client;
+};
+
+GEDIT_PLUGIN_REGISTER_TYPE(GeditTimePlugin, gedit_time_plugin)
+
+typedef struct
+{
+ GtkActionGroup *action_group;
+ guint ui_id;
+} WindowData;
+
+typedef struct
+{
+ GeditWindow *window;
+ GeditTimePlugin *plugin;
+} ActionData;
+
+static void time_cb (GtkAction *action, ActionData *data);
+
+static const GtkActionEntry action_entries[] =
+{
+ {
+ "InsertDateAndTime",
+ NULL,
+ N_("In_sert Date and Time..."),
+ NULL,
+ N_("Insert current date and time at the cursor position"),
+ G_CALLBACK (time_cb)
+ },
+};
+
+static void
+gedit_time_plugin_init (GeditTimePlugin *plugin)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "GeditTimePlugin initializing");
+
+ plugin->priv = GEDIT_TIME_PLUGIN_GET_PRIVATE (plugin);
+
+ plugin->priv->mateconf_client = mateconf_client_get_default ();
+
+ mateconf_client_add_dir (plugin->priv->mateconf_client,
+ TIME_BASE_KEY,
+ MATECONF_CLIENT_PRELOAD_ONELEVEL,
+ NULL);
+}
+
+static void
+gedit_time_plugin_finalize (GObject *object)
+{
+ GeditTimePlugin *plugin = GEDIT_TIME_PLUGIN (object);
+
+ gedit_debug_message (DEBUG_PLUGINS, "GeditTimePlugin finalizing");
+
+ mateconf_client_suggest_sync (plugin->priv->mateconf_client, NULL);
+
+ g_object_unref (G_OBJECT (plugin->priv->mateconf_client));
+
+ G_OBJECT_CLASS (gedit_time_plugin_parent_class)->finalize (object);
+}
+
+static void
+free_window_data (WindowData *data)
+{
+ g_return_if_fail (data != NULL);
+
+ g_object_unref (data->action_group);
+ g_free (data);
+}
+
+static void
+update_ui_real (GeditWindow *window,
+ WindowData *data)
+{
+ GeditView *view;
+ GtkAction *action;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ view = gedit_window_get_active_view (window);
+
+ gedit_debug_message (DEBUG_PLUGINS, "View: %p", view);
+
+ action = gtk_action_group_get_action (data->action_group,
+ "InsertDateAndTime");
+ gtk_action_set_sensitive (action,
+ (view != NULL) &&
+ gtk_text_view_get_editable (GTK_TEXT_VIEW (view)));
+}
+
+static void
+impl_activate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+ ActionData *action_data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = g_new (WindowData, 1);
+ action_data = g_new (ActionData, 1);
+
+ action_data->plugin = GEDIT_TIME_PLUGIN (plugin);
+ action_data->window = window;
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data->action_group = gtk_action_group_new ("GeditTimePluginActions");
+ gtk_action_group_set_translation_domain (data->action_group,
+ GETTEXT_PACKAGE);
+ gtk_action_group_add_actions_full (data->action_group,
+ action_entries,
+ G_N_ELEMENTS (action_entries),
+ action_data,
+ (GDestroyNotify) g_free);
+
+ gtk_ui_manager_insert_action_group (manager, data->action_group, -1);
+
+ data->ui_id = gtk_ui_manager_new_merge_id (manager);
+
+ g_object_set_data_full (G_OBJECT (window),
+ WINDOW_DATA_KEY,
+ data,
+ (GDestroyNotify) free_window_data);
+
+ gtk_ui_manager_add_ui (manager,
+ data->ui_id,
+ MENU_PATH,
+ "InsertDateAndTime",
+ "InsertDateAndTime",
+ GTK_UI_MANAGER_MENUITEM,
+ FALSE);
+
+ update_ui_real (window, data);
+}
+
+static void
+impl_deactivate (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ GtkUIManager *manager;
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ manager = gedit_window_get_ui_manager (window);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ gtk_ui_manager_remove_ui (manager, data->ui_id);
+ gtk_ui_manager_remove_action_group (manager, data->action_group);
+
+ g_object_set_data (G_OBJECT (window), WINDOW_DATA_KEY, NULL);
+}
+
+static void
+impl_update_ui (GeditPlugin *plugin,
+ GeditWindow *window)
+{
+ WindowData *data;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ data = (WindowData *) g_object_get_data (G_OBJECT (window), WINDOW_DATA_KEY);
+ g_return_if_fail (data != NULL);
+
+ update_ui_real (window, data);
+}
+
+/* whether we should prompt the user or use the specified format */
+static GeditTimePluginPromptType
+get_prompt_type (GeditTimePlugin *plugin)
+{
+ gchar *prompt_type;
+ GeditTimePluginPromptType res;
+
+ prompt_type = mateconf_client_get_string (plugin->priv->mateconf_client,
+ PROMPT_TYPE_KEY,
+ NULL);
+
+ if (prompt_type == NULL)
+ return PROMPT_SELECTED_FORMAT;
+
+ if (strcmp (prompt_type, "USE_SELECTED_FORMAT") == 0)
+ res = USE_SELECTED_FORMAT;
+ else if (strcmp (prompt_type, "USE_CUSTOM_FORMAT") == 0)
+ res = USE_CUSTOM_FORMAT;
+ else if (strcmp (prompt_type, "PROMPT_CUSTOM_FORMAT") == 0)
+ res = PROMPT_CUSTOM_FORMAT;
+ else
+ res = PROMPT_SELECTED_FORMAT;
+
+ g_free (prompt_type);
+
+ return res;
+}
+
+static void
+set_prompt_type (GeditTimePlugin *plugin,
+ GeditTimePluginPromptType prompt_type)
+{
+ const gchar * str;
+
+ if (!mateconf_client_key_is_writable (plugin->priv->mateconf_client,
+ PROMPT_TYPE_KEY,
+ NULL))
+ {
+ return;
+ }
+
+ switch (prompt_type)
+ {
+ case USE_SELECTED_FORMAT:
+ str = "USE_SELECTED_FORMAT";
+ break;
+ case USE_CUSTOM_FORMAT:
+ str = "USE_CUSTOM_FORMAT";
+ break;
+ case PROMPT_CUSTOM_FORMAT:
+ str = "PROMPT_CUSTOM_FORMAT";
+ break;
+ default:
+ str = "PROMPT_SELECTED_FORMAT";
+ }
+
+ mateconf_client_set_string (plugin->priv->mateconf_client,
+ PROMPT_TYPE_KEY,
+ str,
+ NULL);
+}
+
+/* The selected format in the list */
+static gchar *
+get_selected_format (GeditTimePlugin *plugin)
+{
+ gchar *sel_format;
+
+ sel_format = mateconf_client_get_string (plugin->priv->mateconf_client,
+ SELECTED_FORMAT_KEY,
+ NULL);
+
+ return sel_format ? sel_format : g_strdup (formats [0]);
+}
+
+static void
+set_selected_format (GeditTimePlugin *plugin,
+ const gchar *format)
+{
+ g_return_if_fail (format != NULL);
+
+ if (!mateconf_client_key_is_writable (plugin->priv->mateconf_client,
+ SELECTED_FORMAT_KEY,
+ NULL))
+ {
+ return;
+ }
+
+ mateconf_client_set_string (plugin->priv->mateconf_client,
+ SELECTED_FORMAT_KEY,
+ format,
+ NULL);
+}
+
+/* the custom format in the entry */
+static gchar *
+get_custom_format (GeditTimePlugin *plugin)
+{
+ gchar *format;
+
+ format = mateconf_client_get_string (plugin->priv->mateconf_client,
+ CUSTOM_FORMAT_KEY,
+ NULL);
+
+ return format ? format : g_strdup (DEFAULT_CUSTOM_FORMAT);
+}
+
+static void
+set_custom_format (GeditTimePlugin *plugin,
+ const gchar *format)
+{
+ g_return_if_fail (format != NULL);
+
+ if (!mateconf_client_key_is_writable (plugin->priv->mateconf_client,
+ CUSTOM_FORMAT_KEY,
+ NULL))
+ return;
+
+ mateconf_client_set_string (plugin->priv->mateconf_client,
+ CUSTOM_FORMAT_KEY,
+ format,
+ NULL);
+}
+
+static gchar *
+get_time (const gchar* format)
+{
+ gchar *out = NULL;
+ gchar *out_utf8 = NULL;
+ time_t clock;
+ struct tm *now;
+ size_t out_length = 0;
+ gchar *locale_format;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_val_if_fail (format != NULL, NULL);
+
+ if (strlen (format) == 0)
+ return g_strdup (" ");
+
+ locale_format = g_locale_from_utf8 (format, -1, NULL, NULL, NULL);
+ if (locale_format == NULL)
+ return g_strdup (" ");
+
+ clock = time (NULL);
+ now = localtime (&clock);
+
+ do
+ {
+ out_length += 255;
+ out = g_realloc (out, out_length);
+ }
+ while (strftime (out, out_length, locale_format, now) == 0);
+
+ g_free (locale_format);
+
+ if (g_utf8_validate (out, -1, NULL))
+ {
+ out_utf8 = out;
+ }
+ else
+ {
+ out_utf8 = g_locale_to_utf8 (out, -1, NULL, NULL, NULL);
+ g_free (out);
+
+ if (out_utf8 == NULL)
+ out_utf8 = g_strdup (" ");
+ }
+
+ return out_utf8;
+}
+
+static void
+dialog_destroyed (GtkObject *obj, gpointer dialog_pointer)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_free (dialog_pointer);
+
+ gedit_debug_message (DEBUG_PLUGINS, "END");
+}
+
+static GtkTreeModel *
+create_model (GtkWidget *listview,
+ const gchar *sel_format,
+ GeditTimePlugin *plugin)
+{
+ gint i = 0;
+ GtkListStore *store;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ /* create list store */
+ store = gtk_list_store_new (NUM_COLUMNS, G_TYPE_STRING, G_TYPE_INT);
+
+ /* Set tree view model*/
+ gtk_tree_view_set_model (GTK_TREE_VIEW (listview),
+ GTK_TREE_MODEL (store));
+ g_object_unref (G_OBJECT (store));
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview));
+ g_return_val_if_fail (selection != NULL, GTK_TREE_MODEL (store));
+
+ /* there should always be one line selected */
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+
+ /* add data to the list store */
+ while (formats[i] != NULL)
+ {
+ gchar *str;
+
+ str = get_time (formats[i]);
+
+ gedit_debug_message (DEBUG_PLUGINS, "%d : %s", i, str);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ COLUMN_FORMATS, str,
+ COLUMN_INDEX, i,
+ -1);
+ g_free (str);
+
+ if (sel_format && strcmp (formats[i], sel_format) == 0)
+ gtk_tree_selection_select_iter (selection, &iter);
+
+ ++i;
+ }
+
+ /* fall back to select the first iter */
+ if (!gtk_tree_selection_get_selected (selection, NULL, NULL))
+ {
+ gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+
+ return GTK_TREE_MODEL (store);
+}
+
+static void
+scroll_to_selected (GtkTreeView *tree_view)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ model = gtk_tree_view_get_model (tree_view);
+ g_return_if_fail (model != NULL);
+
+ /* Scroll to selected */
+ selection = gtk_tree_view_get_selection (tree_view);
+ g_return_if_fail (selection != NULL);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ GtkTreePath* path;
+
+ path = gtk_tree_model_get_path (model, &iter);
+ g_return_if_fail (path != NULL);
+
+ gtk_tree_view_scroll_to_cell (tree_view,
+ path, NULL, TRUE, 1.0, 0.0);
+ gtk_tree_path_free (path);
+ }
+}
+
+static void
+create_formats_list (GtkWidget *listview,
+ const gchar *sel_format,
+ GeditTimePlugin *plugin)
+{
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (listview != NULL);
+ g_return_if_fail (sel_format != NULL);
+
+ /* the Available formats column */
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (
+ _("Available formats"),
+ cell,
+ "text", COLUMN_FORMATS,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (listview), column);
+
+ /* Create model, it also add model to the tree view */
+ create_model (listview, sel_format, plugin);
+
+ g_signal_connect (listview,
+ "realize",
+ G_CALLBACK (scroll_to_selected),
+ NULL);
+
+ gtk_widget_show (listview);
+}
+
+static void
+updated_custom_format_example (GtkEntry *format_entry,
+ GtkLabel *format_example)
+{
+ const gchar *format;
+ gchar *time;
+ gchar *str;
+ gchar *escaped_time;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ g_return_if_fail (GTK_IS_ENTRY (format_entry));
+ g_return_if_fail (GTK_IS_LABEL (format_example));
+
+ format = gtk_entry_get_text (format_entry);
+
+ time = get_time (format);
+ escaped_time = g_markup_escape_text (time, -1);
+
+ str = g_strdup_printf ("<span size=\"small\">%s</span>", escaped_time);
+
+ gtk_label_set_markup (format_example, str);
+
+ g_free (escaped_time);
+ g_free (time);
+ g_free (str);
+}
+
+static void
+choose_format_dialog_button_toggled (GtkToggleButton *button,
+ ChooseFormatDialog *dialog)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->custom)))
+ {
+ gtk_widget_set_sensitive (dialog->list, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_entry, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, TRUE);
+
+ return;
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list)))
+ {
+ gtk_widget_set_sensitive (dialog->list, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_entry, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, FALSE);
+
+ return;
+ }
+}
+
+static void
+configure_dialog_button_toggled (GtkToggleButton *button, TimeConfigureDialog *dialog)
+{
+ gedit_debug (DEBUG_PLUGINS);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->custom)))
+ {
+ gtk_widget_set_sensitive (dialog->list, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_entry, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, TRUE);
+
+ return;
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list)))
+ {
+ gtk_widget_set_sensitive (dialog->list, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_entry, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, FALSE);
+
+ return;
+ }
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->prompt)))
+ {
+ gtk_widget_set_sensitive (dialog->list, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_entry, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, FALSE);
+
+ return;
+ }
+}
+
+static gint
+get_format_from_list (GtkWidget *listview)
+{
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (listview));
+ g_return_val_if_fail (model != NULL, 0);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (listview));
+ g_return_val_if_fail (selection != NULL, 0);
+
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter))
+ {
+ gint selected_value;
+
+ gtk_tree_model_get (model, &iter, COLUMN_INDEX, &selected_value, -1);
+
+ gedit_debug_message (DEBUG_PLUGINS, "Sel value: %d", selected_value);
+
+ return selected_value;
+ }
+
+ g_return_val_if_reached (0);
+}
+
+static TimeConfigureDialog *
+get_configure_dialog (GeditTimePlugin *plugin)
+{
+ TimeConfigureDialog *dialog = NULL;
+ gchar *data_dir;
+ gchar *ui_file;
+ GtkWidget *content;
+ GtkWidget *viewport;
+ GeditTimePluginPromptType prompt_type;
+ gchar *sf, *cf;
+ GtkWidget *error_widget;
+ gboolean ret;
+ gchar *root_objects[] = {
+ "time_dialog_content",
+ NULL
+ };
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ dialog = g_new0 (TimeConfigureDialog, 1);
+
+ dialog->dialog = gtk_dialog_new_with_buttons (_("Configure insert date/time plugin..."),
+ NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK,
+ GTK_RESPONSE_OK,
+ GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP,
+ NULL);
+
+ /* HIG defaults */
+ gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dialog->dialog)), 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog->dialog))),
+ 2); /* 2 * 5 + 2 = 12 */
+ gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (dialog->dialog))),
+ 5);
+ gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dialog->dialog))), 6);
+
+ g_return_val_if_fail (dialog->dialog != NULL, NULL);
+
+ data_dir = gedit_plugin_get_data_dir (GEDIT_PLUGIN (plugin));
+ ui_file = g_build_filename (data_dir, "gedit-time-setup-dialog.ui", NULL);
+ ret = gedit_utils_get_ui_objects (ui_file,
+ root_objects,
+ &error_widget,
+ "time_dialog_content", &content,
+ "formats_viewport", &viewport,
+ "formats_tree", &dialog->list,
+ "always_prompt", &dialog->prompt,
+ "never_prompt", &dialog->use_list,
+ "use_custom", &dialog->custom,
+ "custom_entry", &dialog->custom_entry,
+ "custom_format_example", &dialog->custom_format_example,
+ NULL);
+
+ g_free (data_dir);
+ g_free (ui_file);
+
+ if (!ret)
+ {
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog->dialog))),
+ error_widget,
+ TRUE, TRUE, 0);
+ gtk_container_set_border_width (GTK_CONTAINER (error_widget), 5);
+
+ gtk_widget_show (error_widget);
+
+ return dialog;
+ }
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog->dialog), FALSE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (dialog->dialog), FALSE);
+
+ sf = get_selected_format (plugin);
+ create_formats_list (dialog->list, sf, plugin);
+ g_free (sf);
+
+ prompt_type = get_prompt_type (plugin);
+
+ cf = get_custom_format (plugin);
+ gtk_entry_set_text (GTK_ENTRY(dialog->custom_entry), cf);
+ g_free (cf);
+
+ if (prompt_type == USE_CUSTOM_FORMAT)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->custom), TRUE);
+
+ gtk_widget_set_sensitive (dialog->list, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_entry, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, TRUE);
+ }
+ else if (prompt_type == USE_SELECTED_FORMAT)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->use_list), TRUE);
+
+ gtk_widget_set_sensitive (dialog->list, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_entry, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, FALSE);
+ }
+ else
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->prompt), TRUE);
+
+ gtk_widget_set_sensitive (dialog->list, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_entry, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, FALSE);
+ }
+
+ updated_custom_format_example (GTK_ENTRY (dialog->custom_entry),
+ GTK_LABEL (dialog->custom_format_example));
+
+ /* setup a window of a sane size. */
+ gtk_widget_set_size_request (GTK_WIDGET (viewport), 10, 200);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog->dialog))),
+ content, FALSE, FALSE, 0);
+ g_object_unref (content);
+ gtk_container_set_border_width (GTK_CONTAINER (content), 5);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog),
+ GTK_RESPONSE_OK);
+
+ g_signal_connect (dialog->custom,
+ "toggled",
+ G_CALLBACK (configure_dialog_button_toggled),
+ dialog);
+ g_signal_connect (dialog->prompt,
+ "toggled",
+ G_CALLBACK (configure_dialog_button_toggled),
+ dialog);
+ g_signal_connect (dialog->use_list,
+ "toggled",
+ G_CALLBACK (configure_dialog_button_toggled),
+ dialog);
+ g_signal_connect (dialog->dialog,
+ "destroy",
+ G_CALLBACK (dialog_destroyed),
+ dialog);
+ g_signal_connect (dialog->custom_entry,
+ "changed",
+ G_CALLBACK (updated_custom_format_example),
+ dialog->custom_format_example);
+
+ return dialog;
+}
+
+static void
+real_insert_time (GtkTextBuffer *buffer,
+ const gchar *the_time)
+{
+ gedit_debug_message (DEBUG_PLUGINS, "Insert: %s", the_time);
+
+ gtk_text_buffer_begin_user_action (buffer);
+
+ gtk_text_buffer_insert_at_cursor (buffer, the_time, -1);
+ gtk_text_buffer_insert_at_cursor (buffer, " ", -1);
+
+ gtk_text_buffer_end_user_action (buffer);
+}
+
+static void
+choose_format_dialog_row_activated (GtkTreeView *list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ ChooseFormatDialog *dialog)
+{
+ gint sel_format;
+ gchar *the_time;
+
+ sel_format = get_format_from_list (dialog->list);
+ the_time = get_time (formats[sel_format]);
+
+ set_prompt_type (dialog->plugin, PROMPT_SELECTED_FORMAT);
+ set_selected_format (dialog->plugin, formats[sel_format]);
+
+ g_return_if_fail (the_time != NULL);
+
+ real_insert_time (dialog->buffer, the_time);
+
+ g_free (the_time);
+}
+
+static ChooseFormatDialog *
+get_choose_format_dialog (GtkWindow *parent,
+ GeditTimePluginPromptType prompt_type,
+ GeditTimePlugin *plugin)
+{
+ ChooseFormatDialog *dialog;
+ gchar *data_dir;
+ gchar *ui_file;
+ GtkWidget *error_widget;
+ gboolean ret;
+ gchar *sf, *cf;
+ GtkWindowGroup *wg = NULL;
+
+ if (parent != NULL)
+ wg = gtk_window_get_group (parent);
+
+ dialog = g_new0 (ChooseFormatDialog, 1);
+
+ data_dir = gedit_plugin_get_data_dir (GEDIT_PLUGIN (plugin));
+ ui_file = g_build_filename (data_dir, "gedit-time-dialog.ui", NULL);
+ ret = gedit_utils_get_ui_objects (ui_file,
+ NULL,
+ &error_widget,
+ "choose_format_dialog", &dialog->dialog,
+ "choice_list", &dialog->list,
+ "use_sel_format_radiobutton", &dialog->use_list,
+ "use_custom_radiobutton", &dialog->custom,
+ "custom_entry", &dialog->custom_entry,
+ "custom_format_example", &dialog->custom_format_example,
+ NULL);
+
+ g_free (data_dir);
+ g_free (ui_file);
+
+ if (!ret)
+ {
+ GtkWidget *err_dialog;
+
+ err_dialog = gtk_dialog_new_with_buttons (NULL,
+ parent,
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ if (wg != NULL)
+ gtk_window_group_add_window (wg, GTK_WINDOW (err_dialog));
+
+ gtk_window_set_resizable (GTK_WINDOW (err_dialog), FALSE);
+ gtk_dialog_set_has_separator (GTK_DIALOG (err_dialog), FALSE);
+ gtk_dialog_set_default_response (GTK_DIALOG (err_dialog), GTK_RESPONSE_OK);
+
+ gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (err_dialog))),
+ error_widget);
+
+ g_signal_connect (G_OBJECT (err_dialog),
+ "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ gtk_widget_show_all (err_dialog);
+
+ return NULL;
+ }
+
+ gtk_window_group_add_window (wg,
+ GTK_WINDOW (dialog->dialog));
+ gtk_window_set_transient_for (GTK_WINDOW (dialog->dialog), parent);
+ gtk_window_set_modal (GTK_WINDOW (dialog->dialog), TRUE);
+
+ sf = get_selected_format (plugin);
+ create_formats_list (dialog->list, sf, plugin);
+ g_free (sf);
+
+ cf = get_custom_format (plugin);
+ gtk_entry_set_text (GTK_ENTRY(dialog->custom_entry), cf);
+ g_free (cf);
+
+ updated_custom_format_example (GTK_ENTRY (dialog->custom_entry),
+ GTK_LABEL (dialog->custom_format_example));
+
+ if (prompt_type == PROMPT_CUSTOM_FORMAT)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->custom), TRUE);
+
+ gtk_widget_set_sensitive (dialog->list, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_entry, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, TRUE);
+ }
+ else if (prompt_type == PROMPT_SELECTED_FORMAT)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->use_list), TRUE);
+
+ gtk_widget_set_sensitive (dialog->list, TRUE);
+ gtk_widget_set_sensitive (dialog->custom_entry, FALSE);
+ gtk_widget_set_sensitive (dialog->custom_format_example, FALSE);
+ }
+ else
+ {
+ g_return_val_if_reached (NULL);
+ }
+
+ /* setup a window of a sane size. */
+ gtk_widget_set_size_request (dialog->list, 10, 200);
+
+ gtk_dialog_set_default_response (GTK_DIALOG (dialog->dialog),
+ GTK_RESPONSE_OK);
+
+ g_signal_connect (dialog->custom,
+ "toggled",
+ G_CALLBACK (choose_format_dialog_button_toggled),
+ dialog);
+ g_signal_connect (dialog->use_list,
+ "toggled",
+ G_CALLBACK (choose_format_dialog_button_toggled),
+ dialog);
+ g_signal_connect (dialog->dialog,
+ "destroy",
+ G_CALLBACK (dialog_destroyed),
+ dialog);
+ g_signal_connect (dialog->custom_entry,
+ "changed",
+ G_CALLBACK (updated_custom_format_example),
+ dialog->custom_format_example);
+ g_signal_connect (dialog->list,
+ "row_activated",
+ G_CALLBACK (choose_format_dialog_row_activated),
+ dialog);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog->dialog), FALSE);
+
+ return dialog;
+}
+
+static void
+choose_format_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ ChooseFormatDialog *dialog)
+{
+ switch (response)
+ {
+ case GTK_RESPONSE_HELP:
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_HELP");
+ gedit_help_display (GTK_WINDOW (widget),
+ NULL,
+ "gedit-insert-date-time-plugin");
+ break;
+ }
+ case GTK_RESPONSE_OK:
+ {
+ gchar *the_time;
+
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK");
+
+ /* Get the user's chosen format */
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list)))
+ {
+ gint sel_format;
+
+ sel_format = get_format_from_list (dialog->list);
+ the_time = get_time (formats[sel_format]);
+
+ set_prompt_type (dialog->plugin, PROMPT_SELECTED_FORMAT);
+ set_selected_format (dialog->plugin, formats[sel_format]);
+ }
+ else
+ {
+ const gchar *format;
+
+ format = gtk_entry_get_text (GTK_ENTRY (dialog->custom_entry));
+ the_time = get_time (format);
+
+ set_prompt_type (dialog->plugin, PROMPT_CUSTOM_FORMAT);
+ set_custom_format (dialog->plugin, format);
+ }
+
+ g_return_if_fail (the_time != NULL);
+
+ real_insert_time (dialog->buffer, the_time);
+ g_free (the_time);
+
+ gtk_widget_destroy (dialog->dialog);
+ break;
+ }
+ case GTK_RESPONSE_CANCEL:
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CANCEL");
+ gtk_widget_destroy (dialog->dialog);
+ }
+}
+
+static void
+time_cb (GtkAction *action,
+ ActionData *data)
+{
+ GtkTextBuffer *buffer;
+ gchar *the_time = NULL;
+ GeditTimePluginPromptType prompt_type;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ buffer = GTK_TEXT_BUFFER (gedit_window_get_active_document (data->window));
+ g_return_if_fail (buffer != NULL);
+
+ prompt_type = get_prompt_type (data->plugin);
+
+ if (prompt_type == USE_CUSTOM_FORMAT)
+ {
+ gchar *cf = get_custom_format (data->plugin);
+ the_time = get_time (cf);
+ g_free (cf);
+ }
+ else if (prompt_type == USE_SELECTED_FORMAT)
+ {
+ gchar *sf = get_selected_format (data->plugin);
+ the_time = get_time (sf);
+ g_free (sf);
+ }
+ else
+ {
+ ChooseFormatDialog *dialog;
+
+ dialog = get_choose_format_dialog (GTK_WINDOW (data->window),
+ prompt_type,
+ data->plugin);
+ if (dialog != NULL)
+ {
+ dialog->buffer = buffer;
+ dialog->plugin = data->plugin;
+
+ g_signal_connect (dialog->dialog,
+ "response",
+ G_CALLBACK (choose_format_dialog_response_cb),
+ dialog);
+
+ gtk_widget_show (GTK_WIDGET (dialog->dialog));
+ }
+
+ return;
+ }
+
+ g_return_if_fail (the_time != NULL);
+
+ real_insert_time (buffer, the_time);
+
+ g_free (the_time);
+}
+
+static void
+ok_button_pressed (TimeConfigureDialog *dialog)
+{
+ gint sel_format;
+ const gchar *custom_format;
+
+ gedit_debug (DEBUG_PLUGINS);
+
+ sel_format = get_format_from_list (dialog->list);
+
+ custom_format = gtk_entry_get_text (GTK_ENTRY (dialog->custom_entry));
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->custom)))
+ {
+ set_prompt_type (dialog->plugin, USE_CUSTOM_FORMAT);
+ set_custom_format (dialog->plugin, custom_format);
+ }
+ else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->use_list)))
+ {
+ set_prompt_type (dialog->plugin, USE_SELECTED_FORMAT);
+ set_selected_format (dialog->plugin, formats [sel_format]);
+ }
+ else
+ {
+ /* Default to prompt the user with the list selected */
+ set_prompt_type (dialog->plugin, PROMPT_SELECTED_FORMAT);
+ }
+
+ gedit_debug_message (DEBUG_PLUGINS, "Sel: %d", sel_format);
+}
+
+static void
+configure_dialog_response_cb (GtkWidget *widget,
+ gint response,
+ TimeConfigureDialog *dialog)
+{
+ switch (response)
+ {
+ case GTK_RESPONSE_HELP:
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_HELP");
+
+ gedit_help_display (GTK_WINDOW (dialog),
+ NULL,
+ "gedit-date-time-configure");
+ break;
+ }
+ case GTK_RESPONSE_OK:
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK");
+
+ ok_button_pressed (dialog);
+
+ gtk_widget_destroy (dialog->dialog);
+ break;
+ }
+ case GTK_RESPONSE_CANCEL:
+ {
+ gedit_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CANCEL");
+ gtk_widget_destroy (dialog->dialog);
+ }
+ }
+}
+
+static GtkWidget *
+impl_create_configure_dialog (GeditPlugin *plugin)
+{
+ TimeConfigureDialog *dialog;
+
+ dialog = get_configure_dialog (GEDIT_TIME_PLUGIN (plugin));
+
+ dialog->plugin = GEDIT_TIME_PLUGIN (plugin);
+
+ g_signal_connect (dialog->dialog,
+ "response",
+ G_CALLBACK (configure_dialog_response_cb),
+ dialog);
+
+ return GTK_WIDGET (dialog->dialog);
+}
+
+static void
+gedit_time_plugin_class_init (GeditTimePluginClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GeditPluginClass *plugin_class = GEDIT_PLUGIN_CLASS (klass);
+
+ object_class->finalize = gedit_time_plugin_finalize;
+
+ plugin_class->activate = impl_activate;
+ plugin_class->deactivate = impl_deactivate;
+ plugin_class->update_ui = impl_update_ui;
+
+ plugin_class->create_configure_dialog = impl_create_configure_dialog;
+
+ g_type_class_add_private (object_class, sizeof (GeditTimePluginPrivate));
+}
diff --git a/plugins/time/gedit-time-plugin.h b/plugins/time/gedit-time-plugin.h
new file mode 100755
index 00000000..6d598264
--- /dev/null
+++ b/plugins/time/gedit-time-plugin.h
@@ -0,0 +1,78 @@
+/*
+ * gedit-time-plugin.h
+ *
+ * Copyright (C) 2002-2005 - Paolo Maggi
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * $Id$
+ */
+
+#ifndef __GEDIT_TIME_PLUGIN_H__
+#define __GEDIT_TIME_PLUGIN_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gedit/gedit-plugin.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GEDIT_TYPE_TIME_PLUGIN (gedit_time_plugin_get_type ())
+#define GEDIT_TIME_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_TIME_PLUGIN, GeditTimePlugin))
+#define GEDIT_TIME_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_TIME_PLUGIN, GeditTimePluginClass))
+#define GEDIT_IS_TIME_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_TIME_PLUGIN))
+#define GEDIT_IS_TIME_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_TIME_PLUGIN))
+#define GEDIT_TIME_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_TIME_PLUGIN, GeditTimePluginClass))
+
+/* Private structure type */
+typedef struct _GeditTimePluginPrivate GeditTimePluginPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GeditTimePlugin GeditTimePlugin;
+
+struct _GeditTimePlugin
+{
+ GeditPlugin parent_instance;
+
+ /*< private >*/
+ GeditTimePluginPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GeditTimePluginClass GeditTimePluginClass;
+
+struct _GeditTimePluginClass
+{
+ GeditPluginClass parent_class;
+};
+
+/*
+ * Public methods
+ */
+GType gedit_time_plugin_get_type (void) G_GNUC_CONST;
+
+/* All the plugins must implement this function */
+G_MODULE_EXPORT GType register_gedit_plugin (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __GEDIT_TIME_PLUGIN_H__ */
diff --git a/plugins/time/gedit-time-setup-dialog.ui b/plugins/time/gedit-time-setup-dialog.ui
new file mode 100755
index 00000000..46fb5b9d
--- /dev/null
+++ b/plugins/time/gedit-time-setup-dialog.ui
@@ -0,0 +1,330 @@
+<?xml version="1.0"?>
+<!--*- mode: xml -*-->
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <object class="GtkDialog" id="time_dialog">
+ <property name="title" translatable="yes">Configure date/time plugin</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="has_separator">False</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">8</property>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-help</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-cancel</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button4">
+ <property name="visible">True</property>
+ <property name="can_default">True</property>
+ <property name="can_focus">True</property>
+ <property name="label">gtk-ok</property>
+ <property name="use_stock">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="time_dialog_content">
+ <property name="border_width">10</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">When inserting date/time...</property>
+ <property name="use_underline">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="always_prompt">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Prompt for a format</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkRadioButton" id="never_prompt">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Use the _selected format</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">always_prompt</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> </property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="formats_viewport">
+ <property name="visible">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="shadow_type">GTK_SHADOW_IN</property>
+ <property name="window_placement">GTK_CORNER_TOP_LEFT</property>
+ <child>
+ <object class="GtkTreeView" id="formats_tree">
+ <property name="visible">True</property>
+ <property name="headers_visible">False</property>
+ <property name="rules_hint">False</property>
+ <property name="reorderable">False</property>
+ <property name="enable_search">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkVBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkRadioButton" id="use_custom">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">_Use custom format</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">always_prompt</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="custom_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes" comments="Translators: Use the more common date format in your locale">%d/%m/%Y %H:%M:%S</property>
+ <property name="has_frame">True</property>
+ <property name="activates_default">False</property>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">2</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="custom_format_example">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes" comments="Translators: This example should follow the date format defined in the entry above">01/11/2009 17:52:00</property>
+ <property name="use_underline">False</property>
+ <property name="justify">GTK_JUSTIFY_RIGHT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <attributes>
+ <attribute name="scale" value="0.8"/><!-- PANGO_SCALE_SMALL -->
+ </attributes>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button1</action-widget>
+ <action-widget response="0">button3</action-widget>
+ <action-widget response="0">button4</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/plugins/time/time.gedit-plugin.desktop.in b/plugins/time/time.gedit-plugin.desktop.in
new file mode 100755
index 00000000..ba06854a
--- /dev/null
+++ b/plugins/time/time.gedit-plugin.desktop.in
@@ -0,0 +1,8 @@
+[Gedit Plugin]
+Module=time
+IAge=2
+_Name=Insert Date/Time
+_Description=Inserts current date and time at the cursor position.
+Authors=Paolo Maggi <[email protected]>;Lee Mallabone <[email protected]>
+Copyright=Copyright © 2002-2005 Paolo Maggi
+Website=http://www.gedit.org