summaryrefslogtreecommitdiff
path: root/plugins/externaltools
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/externaltools
downloadpluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.bz2
pluma-528c1e5ff51e213936e800fc5a9a25da99c0bdf2.tar.xz
initial
Diffstat (limited to 'plugins/externaltools')
-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
26 files changed, 3776 insertions, 0 deletions
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>