[gedit-plugins/gnome-2-32] Add synctex plugin.



commit 857e328cd5d8305c1b0610ead5e000088989ae83
Author: Jose Aliste <jaliste src gnome org>
Date:   Sun Aug 8 12:32:33 2010 -0400

    Add synctex plugin.

 README                                             |    3 +-
 configure.ac                                       |    9 +-
 plugins/synctex/Makefile.am                        |   15 ++
 plugins/synctex/synctex.gedit-plugin.desktop.in.in |   11 +
 plugins/synctex/synctex/Makefile.am                |   12 +
 plugins/synctex/synctex/__init__.py                |    1 +
 plugins/synctex/synctex/evince_dbus.py             |  117 ++++++++++
 plugins/synctex/synctex/synctex.py                 |  230 ++++++++++++++++++++
 8 files changed, 394 insertions(+), 4 deletions(-)
---
diff --git a/README b/README
index 7a642ee..ef61d15 100644
--- a/README
+++ b/README
@@ -1,7 +1,7 @@
 General Information
 ===================
 
-This is version 2.30.0 of gedit-plugins. gedit-plugins are a set of plugins
+This is version 2.31.1 of gedit-plugins. gedit-plugins are a set of plugins
 for gedit.
 
 Installation
@@ -31,5 +31,6 @@ showtabbar		Add a menu entry to show/hide the tabbar.
 smartspaces		Forget you're not using tabulations.
 terminal		Embed a terminal in the bottom pane.
 wordcompletion		Word completion using the completion framework.
+synctex			SyncTeX synchronization of TeX files and PDF output.
 
 all			All of the above plugins
diff --git a/configure.ac b/configure.ac
index 04e1a98..6e07dc0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -89,9 +89,9 @@ ALL_PLUGINS="bookmarks showtabbar charmap drawspaces wordcompletion"
 USEFUL_PLUGINS="bookmarks showtabbar charmap drawspaces wordcompletion"
 DEFAULT_PLUGINS="bookmarks showtabbar charmap drawspaces wordcompletion"
 
-PYTHON_ALL_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal"
-PYTHON_USEFUL_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal"
-PYTHON_DEFAULT_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal"
+PYTHON_ALL_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal synctex"
+PYTHON_USEFUL_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal synctex"
+PYTHON_DEFAULT_PLUGINS="bracketcompletion codecomment colorpicker commander joinlines multiedit sessionsaver smartspaces terminal synctex"
 
 DIST_PLUGINS="$ALL_PLUGINS $PYTHON_ALL_PLUGINS"
 
@@ -446,6 +446,9 @@ plugins/terminal/Makefile
 plugins/terminal/terminal.gedit-plugin.desktop.in
 plugins/wordcompletion/Makefile
 plugins/wordcompletion/wordcompletion.gedit-plugin.desktop.in
+plugins/synctex/Makefile
+plugins/synctex/synctex/Makefile
+plugins/synctex/synctex.gedit-plugin.desktop.in
 po/Makefile.in])
 
 AC_OUTPUT
diff --git a/plugins/synctex/Makefile.am b/plugins/synctex/Makefile.am
new file mode 100644
index 0000000..fbf28bb
--- /dev/null
+++ b/plugins/synctex/Makefile.am
@@ -0,0 +1,15 @@
+# SyncTeX Plugin
+SUBDIRS = synctex
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)
+
+plugin_in_files = synctex.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/synctex/synctex.gedit-plugin.desktop.in.in b/plugins/synctex/synctex.gedit-plugin.desktop.in.in
new file mode 100644
index 0000000..94d1d56
--- /dev/null
+++ b/plugins/synctex/synctex.gedit-plugin.desktop.in.in
@@ -0,0 +1,11 @@
+[Gedit Plugin]
+Loader=python
+Module=synctex
+IAge=1
+_Name=SyncTeX
+_Description=Synchronize between LaTeX and PDF with gedit and evince.
+Icon=gedit-plugin
+Authors=José Aliste <jaliste src gnome org>
+Copyright=Copyright © 2010 José Aliste
+Website=http://www.gedit.org
+Name= VERSION@
diff --git a/plugins/synctex/synctex/Makefile.am b/plugins/synctex/synctex/Makefile.am
new file mode 100644
index 0000000..a61e475
--- /dev/null
+++ b/plugins/synctex/synctex/Makefile.am
@@ -0,0 +1,12 @@
+# Synctex Plugin
+#
+plugindir = $(GEDIT_PLUGINS_LIBS_DIR)/synctex
+plugin_PYTHON =		\
+	__init__.py	\
+	synctex.py	\
+	evince_dbus.py
+
+CLEANFILES =
+DISTCLEANFILES =
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/synctex/synctex/__init__.py b/plugins/synctex/synctex/__init__.py
new file mode 100644
index 0000000..92e6fba
--- /dev/null
+++ b/plugins/synctex/synctex/__init__.py
@@ -0,0 +1 @@
+from synctex import SynctexPlugin
diff --git a/plugins/synctex/synctex/evince_dbus.py b/plugins/synctex/synctex/evince_dbus.py
new file mode 100644
index 0000000..bf0d128
--- /dev/null
+++ b/plugins/synctex/synctex/evince_dbus.py
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# This file is part of the Gedit Synctex plugin.
+#
+# Copyright (C) 2010 Jose Aliste <jose aliste gmail com>
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public Licence as published by the Free Software
+# Foundation; either version 2 of the Licence, 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 Licence for more
+# details.
+#
+# You should have received a copy of the GNU General Public Licence along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+import dbus, subprocess, time
+from traceback import print_exc
+
+RUNNING, CLOSED = range(2)
+
+class EvinceWindowProxy:
+	"""A DBUS proxy for an Evince Window."""
+	daemon = None
+	bus = None
+	def __init__(self, uri, spawn = False):
+		self.uri = uri
+		self.spawn = spawn
+		self.status = CLOSED
+		self.source_handler = None
+		try:
+			if EvinceWindowProxy.bus is None:
+				EvinceWindowProxy.bus = dbus.SessionBus()
+			if EvinceWindowProxy.daemon is None:
+				EvinceWindowProxy.daemon = EvinceWindowProxy.bus.get_object('org.gnome.evince.Daemon',
+							   '/org/gnome/evince/Daemon')
+			self._get_dbus_name(False)
+		except dbus.DBusException:
+			print_exc()
+
+	def _get_dbus_name(self, spawn):
+		try:
+			self.dbus_name = self.daemon.FindDocument(self.uri,spawn, dbus_interface = "org.gnome.evince.Daemon")
+			if self.dbus_name != '':
+				self.status = RUNNING
+				self.window = EvinceWindowProxy.bus.get_object(self.dbus_name, '/org/gnome/evince/Window/0')
+				self.window.connect_to_signal("Closed", self.on_window_close, dbus_interface="org.gnome.evince.Window")
+				self.window.connect_to_signal("SyncSource", self.on_sync_source, dbus_interface="org.gnome.evince.Window")
+		except dbus.DBusException:
+			print_exc()
+	def set_source_handler (self, source_handler):
+		self.source_handler = source_handler
+
+	def on_window_close(self):
+		self.status = CLOSED
+		self.window = None
+
+	def on_sync_source(self, input_file, source_link):
+		if self.source_handler is not None:
+			self.source_handler(input_file, source_link)
+
+	def name_owner_changed_cb(self, service_name, old_owner, new_owner):
+		if service_name == self.dbus_name and new_owner == '': 
+			self.status = CLOSED
+
+	def SyncView(self, input_file, data):
+		if self.status == CLOSED:
+			print "Evince is closed"
+			if self.spawn:
+				print "Spawning evince"
+				self._get_dbus_name(True)
+		# if self.status is still closed, it means there is a
+		# problem running evince.
+		if self.status == CLOSED: 
+			return False
+		self.window.SyncView(input_file, data, dbus_interface="org.gnome.evince.Window")
+		return False
+
+## This file can be used as a script to support forward search and backward search in vim.
+## It should be easy to adapt to other editors. 
+##  evince_dbus  pdf_file  line_source input_file
+if __name__ == '__main__':
+	import dbus.mainloop.glib, gobject, glib, sys, os
+	
+	def print_usage():
+		print '''
+The usage is evince_dbus output_file line_number input_file from the directory of output_file.
+'''
+		sys.exit(1)
+
+	if len(sys.argv)!=4:
+		print_usage()
+	try:
+		line_number = int(sys.argv[2])
+	except ValueError:
+		print_usage()
+	
+	output_file = sys.argv[1]
+	input_file  = sys.argv[3]
+	path_output  = os.getcwd() + '/' + output_file
+	path_input   = os.getcwd() + '/' + input_file
+
+	if not os.path.isfile(path_output):
+		print_usage()
+
+	dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+	bus = dbus.SessionBus()
+
+	a = EvinceWindowProxy(bus, 'file://' + path_output, True )
+	glib.timeout_add(1000, a.SyncView, path_input, (line_number,1))
+	loop = gobject.MainLoop()
+	loop.run() 
diff --git a/plugins/synctex/synctex/synctex.py b/plugins/synctex/synctex/synctex.py
new file mode 100644
index 0000000..8779bd7
--- /dev/null
+++ b/plugins/synctex/synctex/synctex.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-   
+
+#  synctex.py - Synctex support with Gedit and Evince.
+#  
+#  Copyright (C) 2010 - José Aliste <jose aliste gmail com>
+#  
+#  This program is free software; you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#   
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#   
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software
+#  Foundation, Inc., 59 Temple Place, Suite 330,
+#  Boston, MA 02111-1307, USA.
+
+import gtk, gedit, gio , gtk.gdk as gdk
+from gettext import gettext as _
+from evince_dbus import EvinceWindowProxy
+import dbus.mainloop.glib
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+
+ui_str = """<ui>
+  <menubar name="MenuBar">
+    <menu name="ToolsMenu" action="Tools">
+      <placeholder name="ToolsOps_2">
+        <separator/>
+        <menuitem name="ExamplePy" action="SynctexForwardSearch"/>
+      </placeholder>
+    </menu>
+  </menubar>
+</ui>
+"""
+
+VIEW_DATA_KEY = "SynctexPluginViewData"
+WINDOW_DATA_KEY = "SynctexPluginWindowData"
+
+
+class SynctexViewHelper:
+
+    mime_types = ['text/x-tex'];
+
+    def __init__(self, view, window, tab):
+        self._view = view
+        self._window = window
+        self._tab = tab
+        self._doc = view.get_buffer()
+        self.window_proxy = None
+        self._handlers = [
+            self._doc.connect('saved', self.on_saved_or_loaded),
+            self._doc.connect('loaded', self.on_saved_or_loaded)
+        ]
+        self.active = False
+        self.last_iters = None
+        self.uri = self._doc.get_uri()
+        self.mime_type = self._doc.get_mime_type()
+        self.update_uri_mime_type()
+        self._highlight_tag = self._doc.create_tag()
+        self._highlight_tag.set_property("background", "#dddddd")
+
+    def on_button_release(self, view, event):
+        if event.button == 1 and event.state & gdk.CONTROL_MASK:
+            self.sync_view()
+    def on_saved_or_loaded(self, doc, data):
+        self.update_uri_mime_type()
+
+    def on_cursor_moved(self, cur):
+        self._unhighlight()
+
+    def deactivate(self):
+        for h in self._handlers:
+            self._doc.disconnect(h)
+
+    def update_uri_mime_type(self):
+        uri = self._doc.get_uri()
+        if uri is not None and uri != self.uri:
+            self._window.get_data(WINDOW_DATA_KEY).view_dict[uri] = self
+            self.uri = uri
+        if self.uri is not None:
+            self.file = gio.File(self.uri)
+        self.mime_type = self._doc.get_mime_type()
+        self.update_active()
+
+    def _highlight(self):
+        iter = self._doc.get_iter_at_mark(self._doc.get_insert())
+        end_iter = iter.copy()
+        end_iter.forward_to_line_end()
+        self._doc.apply_tag(self._highlight_tag, iter, end_iter)
+        self.last_iters = [iter, end_iter];
+
+    def _unhighlight(self):
+        if self.last_iters is not None:
+            self._doc.remove_tag(self._highlight_tag, self.last_iters[0],self.last_iters[1])
+        self.last_iters = None
+
+    def goto_line (self, line):
+        self._doc.goto_line(line) 
+        self._view.scroll_to_cursor()
+        self._window.set_active_tab(self._tab)
+        self._highlight()
+
+    def source_view_handler(self, input_file, source_link):
+        if self.file.get_basename() == input_file:
+            self.goto_line(source_link[0] - 1)
+        else:
+            uri_input = self.file.get_parent().get_child(input_file).get_uri()
+            view_dict = self._window.get_data(WINDOW_DATA_KEY).view_dict
+            if uri_input in view_dict:
+                view_dict[uri_input].goto_line(source_link[0] - 1)
+            else:
+                self._window.create_tab_from_uri(uri_input,
+                                                 None, source_link[0]-1, False, True)
+        self._window.present()
+
+    def sync_view(self):
+        if self.active:
+            cursor_iter =  self._doc.get_iter_at_mark(self._doc.get_insert())
+            line = cursor_iter.get_line() + 1
+            col = cursor_iter.get_line_offset()
+            self.window_proxy.SyncView(self.file.get_path(), (line, col))
+
+    def update_active(self):
+        # Activate the plugin only if the doc is a LaTeX file. 
+        self.active = (self.mime_type in self.mime_types)
+
+        if self.active and self.window_proxy is None:
+            if self.uri.endswith(".tex"):
+                self._active_handlers = [
+                        self._doc.connect('cursor-moved', self.on_cursor_moved),
+                        self._view.connect('button-release-event',self.on_button_release)]
+
+                self._window.get_data(WINDOW_DATA_KEY)._action_group.set_sensitive(True)
+                uri_output = self.uri[:-3] + "pdf"
+                self.window_proxy = EvinceWindowProxy (uri_output, True)
+                self.window_proxy.set_source_handler (self.source_view_handler)
+        elif not self.active and self.window_proxy is not None:
+            # destroy the evince window proxy.
+            self._doc.disconnect(self._active_handlers[0])
+            self._view.disconnect(self._active_handlers[1])
+            self._window.get_data(WINDOW_DATA_KEY)._action_group.get_sensitive(False)
+            self.window_proxy = None
+
+
+class SynctexWindowHelper:
+    def __init__(self, plugin, window):
+        self._window = window
+        self._plugin = plugin
+        self._insert_menu()
+        self.view_dict = {}
+
+        for view in window.get_views():
+            self.add_helper(view)
+
+        self.handlers = [
+            window.connect("tab-added", lambda w, t: self.add_helper(t.get_view(),w, t)),
+            window.connect("tab-removed", lambda w, t: self.remove_helper(t.get_view())),
+            window.connect("active-tab-changed", self.on_active_tab_changed)
+        ]
+    def on_active_tab_changed(self, window,  tab):
+        view_helper = tab.get_view().get_data(VIEW_DATA_KEY)
+        if view_helper is None:
+            active = False
+        else:
+            active = view_helper.active 
+        self._action_group.set_sensitive(active)
+
+
+    def add_helper(self, view, window, tab):
+        helper = SynctexViewHelper(view, window, tab)
+        if helper.uri is not None:
+            self.view_dict[helper.uri] = helper
+        view.set_data (VIEW_DATA_KEY, helper)
+
+    def remove_helper(self, view):
+        helper = view.get_data(VIEW_DATA_KEY)
+        if helper.uri is not None:
+            del self.view_dict[helper.uri]
+        helper.deactivate()
+        view.set_data(VIEW_DATA_KEY, None)
+
+    def __del__(self):
+        self._window = None
+        self._plugin = None
+
+    def deactivate(self):
+        for h in self.handlers:
+            self._window.disconnect(h)
+
+    def _insert_menu(self):
+        # Get the GtkUIManager
+        manager = self._window.get_ui_manager()
+
+        # Create a new action group
+        self._action_group = gtk.ActionGroup("ExamplePyPluginActions")
+        self._action_group.add_actions([("SynctexForwardSearch", None, _("Forward Search"),
+                                         None, _("Forward Search"),
+                                         self.forward_search_cb)])
+
+        # Insert the action group
+        manager.insert_action_group(self._action_group, -1)
+
+        # Merge the UI
+        self._ui_id = manager.add_ui_from_string(ui_str)
+
+    def forward_search_cb(self, action):
+        self._window.get_active_view().get_data(VIEW_DATA_KEY).sync_view()
+
+class SynctexPlugin(gedit.Plugin):
+
+    def __init__(self):
+        gedit.Plugin.__init__(self)
+
+    def activate(self, window):
+        helper = SynctexWindowHelper(self, window)
+        window.set_data(WINDOW_DATA_KEY, helper)
+
+    def deactivate(self, window):
+        window.get_data(WINDOW_DATA_KEY).deactivate()
+        window.set_data(WINDOW_DATA_KEY, None)
+
+    def update_ui(self, window):
+        pass
+# ex:ts=4:et:



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]