[gedit-latex] Begin Fixing style of py files.
- From: Jose Aliste <jaliste src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gedit-latex] Begin Fixing style of py files.
- Date: Tue, 28 Jun 2011 22:21:41 +0000 (UTC)
commit a8a29c3d4269c10deb3cc46ff66c15f63696ec39
Author: Josà Aliste <jaliste src gnome org>
Date: Tue Jun 28 18:10:51 2011 -0400
Begin Fixing style of py files.
latex/__init__.py | 4 +-
latex/base/__init__.py | 2565 +++++++++++++++++++--------------------
latex/base/completion.py | 1084 +++++++++---------
latex/base/config.py | 220 ++--
latex/base/decorators.py | 346 +++---
latex/base/job.py | 314 +++---
latex/base/resources.py | 106 +-
latex/base/templates.py | 1130 +++++++++---------
latex/base/windowactivatable.py | 1196 +++++++++---------
latex/bibtex/__init__.py | 2 +-
latex/bibtex/actions.py | 48 +-
latex/bibtex/cache.py | 161 ++--
latex/bibtex/completion.py | 176 ++--
latex/bibtex/dialogs.py | 320 +++---
latex/bibtex/editor.py | 328 +++---
latex/bibtex/model.py | 141 ++--
latex/bibtex/parser.py | 1042 ++++++++--------
latex/bibtex/validator.py | 117 +-
latex/bibtex/views.py | 502 ++++----
latex/issues.py | 233 ++--
latex/latex/__init__.py | 148 ++--
latex/latex/actions.py | 836 +++++++-------
latex/latex/archive.py | 156 ++--
latex/latex/cache.py | 243 ++--
latex/latex/completion.py | 696 ++++++------
latex/latex/dialogs.py | 2214 +++++++++++++++++-----------------
latex/latex/editor.py | 744 ++++++------
latex/latex/environment.py | 638 +++++-----
latex/latex/expander.py | 134 +-
latex/latex/inversesearch.py | 105 +-
latex/latex/lexer.py | 576 +++++-----
latex/latex/listing.py | 85 +-
latex/latex/model.py | 488 ++++----
latex/latex/outline.py | 439 ++++----
latex/latex/parser.py | 1321 ++++++++++----------
latex/latex/preview.py | 256 ++--
latex/latex/validator.py | 412 ++++----
latex/latex/views.py | 664 +++++-----
latex/outline.py | 586 +++++-----
latex/preferences/__init__.py | 64 +-
latex/preferences/dialog.py | 958 ++++++++--------
latex/preferences/tools.py | 398 +++---
latex/relpath.py | 122 +-
latex/tools/__init__.py | 444 ++++----
latex/tools/postprocess.py | 381 +++---
latex/tools/util.py | 179 ++--
latex/tools/views.py | 330 +++---
latex/util.py | 382 +++---
latex/views.py | 348 +++---
49 files changed, 12184 insertions(+), 12198 deletions(-)
---
diff --git a/latex/__init__.py b/latex/__init__.py
index 8fb0921..dd78ffc 100644
--- a/latex/__init__.py
+++ b/latex/__init__.py
@@ -11,11 +11,11 @@
#
# 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
+# 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
-from base.windowactivatable import LaTeXWindowActivatable
+from base.windowactivatable import LaTeXWindowActivatable
\ No newline at end of file
diff --git a/latex/base/__init__.py b/latex/base/__init__.py
index 0c7b603..bc93a85 100644
--- a/latex/base/__init__.py
+++ b/latex/base/__init__.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -29,283 +29,283 @@ from gi.repository import Gtk, Gdk
class View(object):
- """
- Base class for a view
- """
-
- _log = getLogger("View")
-
- # TODO: this doesn't belong to the interface of base
- # TODO: call destroy()
-
- SCOPE_WINDOW, SCOPE_EDITOR = 0, 1
-
- #
- # these should be overriden by subclasses
- #
-
- # a label string used for this view
- label = ""
-
- # an icon for this view (Gtk.Image or a stock_id string)
- icon = None
-
- # the scope of this View:
- # SCOPE_WINDOW: the View is created with the window and the same instance is passed to every Editor
- # SCOPE_EDITOR: the View is created with the Editor and destroyed with it
- scope = SCOPE_WINDOW
-
- def init(self, context):
- """
- To be overridden
- """
-
- def destroy(self):
- """
- To be overridden
- """
-
- def __del__(self):
- self._log.debug("Properly destroyed %s" % self)
+ """
+ Base class for a view
+ """
+
+ _log = getLogger("View")
+
+ # TODO: this doesn't belong to the interface of base
+ # TODO: call destroy()
+
+ SCOPE_WINDOW, SCOPE_EDITOR = 0, 1
+
+ #
+ # these should be overriden by subclasses
+ #
+
+ # a label string used for this view
+ label = ""
+
+ # an icon for this view (Gtk.Image or a stock_id string)
+ icon = None
+
+ # the scope of this View:
+ # SCOPE_WINDOW: the View is created with the window and the same instance is passed to every Editor
+ # SCOPE_EDITOR: the View is created with the Editor and destroyed with it
+ scope = SCOPE_WINDOW
+
+ def init(self, context):
+ """
+ To be overridden
+ """
+
+ def destroy(self):
+ """
+ To be overridden
+ """
+
+ def __del__(self):
+ self._log.debug("Properly destroyed %s" % self)
class SideView(View, Gtk.VBox):
- """
- """
- def __init__(self, context):
- GObject.GObject.__init__(self)
-
- self._context = context
- self._initialized = False
-
- # connect to expose event and init() on first expose
- self._expose_handler = self.connect("draw", self._on_expose_event)
-
- def _on_expose_event(self, *args):
- """
- The View has been exposed for the first time
- """
- self._do_init()
-
- def _do_init(self):
- self.disconnect(self._expose_handler)
- self.init(self._context)
- self.show_all()
- self._initialized = True
-
- def assure_init(self):
- """
- This may be called by the subclassing instance to assure that the View
- has been initialized.
-
- This is necessary because methods of the instance may be called before
- init() as the View is initialized on the first exposure.
- """
- if not self._initialized:
- self._do_init()
-
- def destroy(self):
- if not self._initialized:
- self.disconnect(self._expose_handler)
- Gtk.VBox.destroy(self)
- self._context = None
+ """
+ """
+ def __init__(self, context):
+ GObject.GObject.__init__(self)
+
+ self._context = context
+ self._initialized = False
+
+ # connect to expose event and init() on first expose
+ self._expose_handler = self.connect("draw", self._on_expose_event)
+
+ def _on_expose_event(self, *args):
+ """
+ The View has been exposed for the first time
+ """
+ self._do_init()
+
+ def _do_init(self):
+ self.disconnect(self._expose_handler)
+ self.init(self._context)
+ self.show_all()
+ self._initialized = True
+
+ def assure_init(self):
+ """
+ This may be called by the subclassing instance to assure that the View
+ has been initialized.
+
+ This is necessary because methods of the instance may be called before
+ init() as the View is initialized on the first exposure.
+ """
+ if not self._initialized:
+ self._do_init()
+
+ def destroy(self):
+ if not self._initialized:
+ self.disconnect(self._expose_handler)
+ Gtk.VBox.destroy(self)
+ self._context = None
class BottomView(View, Gtk.HBox):
- """
- """
- def __init__(self, context):
- GObject.GObject.__init__(self)
-
- self._context = context
- self._initialized = False
-
- # connect to expose event and init() on first expose
- self._expose_handler = self.connect("draw", self._on_expose_event)
-
- def _on_expose_event(self, *args):
- """
- The View has been exposed for the first time
- """
- self._do_init()
-
- def _do_init(self):
- self.disconnect(self._expose_handler)
- self.init(self._context)
- self.show_all()
- self._initialized = True
-
- def assure_init(self):
- """
- This may be called by the subclassing instance to assure that the View
- has been initialized.
-
- This is necessary because methods of the instance may be called before
- init() as the View is initialized on the first exposure.
- """
- if not self._initialized:
- self._do_init()
-
- def destroy(self):
- if not self._initialized:
- self.disconnect(self._expose_handler)
- Gtk.HBox.destroy(self)
- self._context = None
+ """
+ """
+ def __init__(self, context):
+ GObject.GObject.__init__(self)
+
+ self._context = context
+ self._initialized = False
+
+ # connect to expose event and init() on first expose
+ self._expose_handler = self.connect("draw", self._on_expose_event)
+
+ def _on_expose_event(self, *args):
+ """
+ The View has been exposed for the first time
+ """
+ self._do_init()
+
+ def _do_init(self):
+ self.disconnect(self._expose_handler)
+ self.init(self._context)
+ self.show_all()
+ self._initialized = True
+
+ def assure_init(self):
+ """
+ This may be called by the subclassing instance to assure that the View
+ has been initialized.
+
+ This is necessary because methods of the instance may be called before
+ init() as the View is initialized on the first exposure.
+ """
+ if not self._initialized:
+ self._do_init()
+
+ def destroy(self):
+ if not self._initialized:
+ self.disconnect(self._expose_handler)
+ Gtk.HBox.destroy(self)
+ self._context = None
class Template(object):
- """
- This one is exposed and should be used by the 'real' plugin code
- """
- def __init__(self, expression):
- self._expression = expression
-
- @property
- def expression(self):
- return self._expression
-
- def __str__(self):
- return self._expression
+ """
+ This one is exposed and should be used by the 'real' plugin code
+ """
+ def __init__(self, expression):
+ self._expression = expression
+
+ @property
+ def expression(self):
+ return self._expression
+
+ def __str__(self):
+ return self._expression
from gi.repository import GObject
class GeditLaTeXPlugin_MenuToolAction(Gtk.Action):
- __gtype_name__ = "GeditLaTeXPlugin_MenuToolAction"
+ __gtype_name__ = "GeditLaTeXPlugin_MenuToolAction"
- def do_create_tool_item(self):
- return Gtk.MenuToolButton()
+ def do_create_tool_item(self):
+ return Gtk.MenuToolButton()
class Action(object):
- """
- """
-
- menu_tool_action = False # if True a MenuToolAction is created and hooked for this action
- # instead of Gtk.Action
-
- extensions = [None] # a list of file extensions for which this action should be enabled
- # [None] indicates that this action is to be enabled for all extensions
-
- def __init__(self, *args, **kwargs):
- pass
-
- def hook(self, action_group, window_context):
- """
- Create an internal action object (Gtk.Action or MenuToolAction), listen to it and
- hook it in an action group
-
- @param action_group: a Gtk.ActionGroup object
- @param window_context: a WindowContext object to pass when this action is activated
- """
- if self.menu_tool_action:
- action_clazz = GeditLaTeXPlugin_MenuToolAction
- else:
- action_clazz = Gtk.Action
- self._internal_action = action_clazz(self.__class__.__name__, self.label, self.tooltip, self.stock_id)
- self._handler = self._internal_action.connect("activate", lambda gtk_action, action: action.activate(window_context), self)
- action_group.add_action_with_accel(self._internal_action, self.accelerator)
-
- @property
- def label(self):
- raise NotImplementedError
-
- @property
- def stock_id(self):
- raise NotImplementedError
-
- @property
- def accelerator(self):
- raise NotImplementedError
-
- @property
- def tooltip(self):
- raise NotImplementedError
-
- def activate(self, context):
- """
- @param context: the current WindowContext instance
- """
- raise NotImplementedError
-
- def unhook(self, action_group):
- self._internal_action.disconnect(self._handler)
- action_group.remove_action(self._internal_action)
-
- #~ def __del__(self):
- #~ print "Properly destroyed Action %s" % self
+ """
+ """
+
+ menu_tool_action = False # if True a MenuToolAction is created and hooked for this action
+ # instead of Gtk.Action
+
+ extensions = [None] # a list of file extensions for which this action should be enabled
+ # [None] indicates that this action is to be enabled for all extensions
+
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def hook(self, action_group, window_context):
+ """
+ Create an internal action object (Gtk.Action or MenuToolAction), listen to it and
+ hook it in an action group
+
+ @param action_group: a Gtk.ActionGroup object
+ @param window_context: a WindowContext object to pass when this action is activated
+ """
+ if self.menu_tool_action:
+ action_clazz = GeditLaTeXPlugin_MenuToolAction
+ else:
+ action_clazz = Gtk.Action
+ self._internal_action = action_clazz(self.__class__.__name__, self.label, self.tooltip, self.stock_id)
+ self._handler = self._internal_action.connect("activate", lambda gtk_action, action: action.activate(window_context), self)
+ action_group.add_action_with_accel(self._internal_action, self.accelerator)
+
+ @property
+ def label(self):
+ raise NotImplementedError
+
+ @property
+ def stock_id(self):
+ raise NotImplementedError
+
+ @property
+ def accelerator(self):
+ raise NotImplementedError
+
+ @property
+ def tooltip(self):
+ raise NotImplementedError
+
+ def activate(self, context):
+ """
+ @param context: the current WindowContext instance
+ """
+ raise NotImplementedError
+
+ def unhook(self, action_group):
+ self._internal_action.disconnect(self._handler)
+ action_group.remove_action(self._internal_action)
+
+ #~ def __del__(self):
+ #~ print "Properly destroyed Action %s" % self
class ICompletionHandler(object):
- """
- This should be implemented for each language or 'proposal source'
- """
- @property
- def trigger_keys(self):
- """
- @return: a list of gdk key codes that trigger completion
- """
- raise NotImplementedError
-
- @property
- def prefix_delimiters(self):
- """
- @return: a list of characters that delimit the prefix on the left
- """
- raise NotImplementedError
-
- def complete(self, prefix):
- """
- @return: a list of objects extending Proposal
- """
- raise NotImplementedError
+ """
+ This should be implemented for each language or 'proposal source'
+ """
+ @property
+ def trigger_keys(self):
+ """
+ @return: a list of gdk key codes that trigger completion
+ """
+ raise NotImplementedError
+
+ @property
+ def prefix_delimiters(self):
+ """
+ @return: a list of characters that delimit the prefix on the left
+ """
+ raise NotImplementedError
+
+ def complete(self, prefix):
+ """
+ @return: a list of objects extending Proposal
+ """
+ raise NotImplementedError
class Proposal(object):
- """
- A proposal for completion
- """
- @property
- def source(self):
- """
- @return: a subclass of Source to be inserted on activation
- """
- raise NotImplementedError
-
- @property
- def label(self):
- """
- @return: a string (may be pango markup) to be shown in proposals popup
- """
- raise NotImplementedError
-
- @property
- def details(self):
- """
- @return: a widget to be shown in details popup
- """
- raise NotImplementedError
-
- @property
- def icon(self):
- """
- @return: an instance of GdkPixbuf.Pixbuf
- """
- raise NotImplementedError
-
- @property
- def overlap(self):
- """
- @return: the number of overlapping characters from the beginning of the
- proposal and the prefix it was generated for
- """
- raise NotImplementedError
-
- def __cmp__(self, other):
- """
- Compare this proposal to another one
- """
- return cmp(self.label.lower(), other.label.lower())
+ """
+ A proposal for completion
+ """
+ @property
+ def source(self):
+ """
+ @return: a subclass of Source to be inserted on activation
+ """
+ raise NotImplementedError
+
+ @property
+ def label(self):
+ """
+ @return: a string (may be pango markup) to be shown in proposals popup
+ """
+ raise NotImplementedError
+
+ @property
+ def details(self):
+ """
+ @return: a widget to be shown in details popup
+ """
+ raise NotImplementedError
+
+ @property
+ def icon(self):
+ """
+ @return: an instance of GdkPixbuf.Pixbuf
+ """
+ raise NotImplementedError
+
+ @property
+ def overlap(self):
+ """
+ @return: the number of overlapping characters from the beginning of the
+ proposal and the prefix it was generated for
+ """
+ raise NotImplementedError
+
+ def __cmp__(self, other):
+ """
+ Compare this proposal to another one
+ """
+ return cmp(self.label.lower(), other.label.lower())
import re
@@ -317,681 +317,681 @@ from .templates import TemplateDelegate
class Editor(object):
- """
- The base class for editors. This manages
- - the subclass life-cycle
- - the marker framework
- - change monitoring
- - drag'n'drop support
- """
-
- __log = getLogger("Editor")
-
-
- class Marker(object):
- """
- Markers refer to and highlight a range of text in the TextBuffer decorated by
- an Editor. They are used for highlighting issues.
-
- Each Marker instance stores two Gtk.TextMark objects refering to the start and
- end of the text range.
- """
- def __init__(self, left_mark, right_mark, id, type):
- """
- @param left_mark: a Gtk.TextMark
- @param right_mark: a Gtk.TextMark
- @param id: a unique string
- @param type: a marker type string
- """
- self.left_mark = left_mark
- self.right_mark = right_mark
- self.type = type
- self.id = id
-
-
- class MarkerTypeRecord(object):
- """
- This used for managing Marker types
- """
- def __init__(self, tag, anonymous):
- """
- @param tag: a Gtk.TextTag
- """
- self.tag = tag
- self.anonymous = anonymous
- self.markers = []
-
-
- __PATTERN_INDENT = re.compile("[ \t]+")
-
-
- # A list of file extensions
- #
- # If one or more files with one of these extensions is dragged and dropped on the editor,
- # the Editor.drag_drop_received method is called. An empty list disables the dnd support.
- dnd_extensions = []
-
-
- def __init__(self, tab_decorator, file):
- self._tab_decorator = tab_decorator
- self._file = file
- self._text_buffer = tab_decorator.tab.get_document()
- self._text_view = tab_decorator.tab.get_view()
-
- # create template delegate
- self._template_delegate = TemplateDelegate(self)
-
- # hook completion handlers of the subclassing Editor
- completion_handlers = self.completion_handlers
- if len(completion_handlers):
- self._completion_distributor = CompletionDistributor(self, completion_handlers)
- else:
- self._completion_distributor = None
-
- #
- # init marker framework
- #
-
- # needed for cleanup
- self._tags = []
- self._marker_types = {} # {marker type -> MarkerTypeRecord object}
- self._markers = {} # { marker id -> marker object }
-
-
- self._window_context = self._tab_decorator._window_decorator._window_context
- self._window_context.create_editor_views(self, file)
-
-
- self._offset = None # used by move_cursor
-
- self.__view_signal_handlers = [
- self._text_view.connect("button-press-event", self.__on_button_pressed),
- self._text_view.connect("key-release-event", self.__on_key_released),
- self._text_view.connect("button-release-event", self.__on_button_released) ]
-
- self.__buffer_change_timestamp = time.time()
- self.__buffer_signal_handlers = [
- self._text_buffer.connect("changed", self.__on_buffer_changed) ]
-
- # dnd support
- if len(self.dnd_extensions) > 0:
- self.__view_signal_handlers.append(
- self._text_view.connect("drag-data-received", self.__on_drag_data_received))
-
- # start life-cycle for subclass
- self.init(file, self._window_context)
-
- def __on_buffer_changed(self, text_buffer):
- """
- Store the timestamp of the last buffer change
- """
- self.__buffer_change_timestamp = time.time()
-
- def __on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
- """
- The drag destination received the data from the drag operation
-
- @param widget: the widget that received the signal
- @param context: the Gdk.DragContext
- @param x: the X position of the drop
- @param y: the Y position of the drop
- @param data: a Gtk.SelectionData object
- @param info: an integer ID for the drag
- @param timestamp: the time of the drag event
- """
- self.__log.debug("drag-data-received")
-
- files = []
- match = False
-
- for uri in data.get_uris():
- file = File(uri)
- files.append(file)
- if file.extension.lower() in self.dnd_extensions:
- match = True
-
- if match:
- self.drag_drop_received(files)
-
- def __on_key_released(self, *args):
- """
- This helps to call 'move_cursor'
- """
- offset = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert()).get_offset()
- if offset != self._offset:
- self._offset = offset
- self.on_cursor_moved(offset)
-
- def __on_button_released(self, *args):
- """
- This helps to call 'move_cursor'
- """
- offset = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert()).get_offset()
- if offset != self._offset:
- self._offset = offset
- self.on_cursor_moved(offset)
-
- def __on_button_pressed(self, text_view, event):
- """
- Mouse button has been pressed on the TextView
- """
- if event.button == 3: # right button
- x, y = text_view.get_pointer()
- x, y = text_view.window_to_buffer_coords(Gtk.TextWindowType.WIDGET, x, y)
- it = text_view.get_iter_at_location(x, y)
-
- self.__log.debug("Right button pressed at offset %s" % it.get_offset())
-
- #
- # find Marker at this position
- #
- while True:
- for mark in it.get_marks():
- name = mark.get_name()
-
- self.__log.debug("Found TextMark '%s' at offset %s" % (name, it.get_offset()))
-
- if name:
- if name in self._markers.keys():
- marker = self._markers[name]
- return self.on_marker_activated(marker, event)
- else:
- self.__log.warning("No marker found for TextMark '%s'" % name)
- else:
- # FIXME: this is not safe - use another symbol for right boundaries!
- self.__log.debug("Unnamed TextMark found, outside of any Markers")
- return
-
- # move left by one char and continue
- if not it.backward_char():
- # start of buffer reached
- return
-
- elif event.button == 1 and event.get_state() & Gdk.ModifierType.CONTROL_MASK:
- x, y = text_view.get_pointer()
- x, y = text_view.window_to_buffer_coords(Gtk.TextWindowType.WIDGET, x, y)
- it = text_view.get_iter_at_location(x, y)
- # notify subclass
- self._ctrl_left_clicked(it)
-
- def _ctrl_left_clicked(self, it):
- """
- Left-clicked on the editor with Ctrl modifier key pressed
- @param it: the Gtk.TextIter at the clicked position
- """
-
- @property
- def file(self):
- return self._file
-
- @property
- def tab_decorator(self):
- return self._tab_decorator
-
- def delete_at_cursor(self, offset):
- """
- Delete characters relative to the cursor position:
-
- offset < 0 delete characters from offset to cursor
- offset > 0 delete characters from cursor to offset
- """
- start = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
- end = self._text_buffer.get_iter_at_offset(start.get_offset() + offset)
- self._text_buffer.delete(start, end)
-
- # methods/properties to be used/overridden by the subclass
-
- @property
- def extensions(self):
- """
- Return a list of extensions for which this Editor is to be activated
- """
- raise NotImplementedError
-
- def drag_drop_received(self, files):
- """
- To be overridden
-
- @param files: a list of File objects dropped on the Editor
- """
- pass
-
- @property
- def initial_timestamp(self):
- """
- Return an initial reference timestamp (this just has to be smaller than
- every value returned by current_timestamp)
- """
- return 0
-
- @property
- def current_timestamp(self):
- """
- Return the current timestamp for buffer change recognition
- """
- return time.time()
-
- def content_changed(self, reference_timestamp):
- """
- Return True if the content of this Editor has changed since a given
- reference timestamp (this must be a timestamp as returned by current_timestamp)
- """
- return self.__buffer_change_timestamp > reference_timestamp
-
- @property
- def charset(self):
- """
- Return the character set used by this Editor
- """
- return self._text_buffer.get_encoding().get_charset()
-
- @property
- def content(self):
- """
- Return the string contained in the TextBuffer
- """
- return self._text_buffer.get_text(self._text_buffer.get_start_iter(),
- self._text_buffer.get_end_iter(), False).decode(self.charset)
-
- @property
- def content_at_left_of_cursor(self):
- """
- Only return the content at left of the cursor
- """
- end_iter = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
- return self._text_buffer.get_text(self._text_buffer.get_start_iter(),
- end_iter, False).decode(self.charset)
-
- def insert(self, source):
- """
- This may be overridden to catch special types like LaTeXSource
- """
- self.__log.debug("insert(%s)" % source)
-
- if type(source) is Template:
- self._template_delegate.insert(source)
- else:
- self._text_buffer.insert_at_cursor(str(source))
-
- # grab the focus again (necessary e.g. after symbol insert)
- self._text_view.grab_focus()
-
- def insert_at_offset(self, offset, string, scroll=False):
- """
- Insert a string at a certain offset
-
- @param offset: a positive int
- @param string: a str
- @param scroll: if True the view is scrolled to the insert position
- """
- iter = self._text_buffer.get_iter_at_offset(offset)
- self._text_buffer.insert(iter, str(string))
-
- if scroll:
- self._text_view.scroll_to_iter(iter, .25, False, 0.5, 0.5)
-
- # grab the focus again (necessary e.g. after symbol insert)
- self._text_view.grab_focus()
-
- def append(self, string):
- """
- Append some source (only makes sense with simple string) and scroll to it
-
- @param string: a str
- """
- self._text_buffer.insert(self._text_buffer.get_end_iter(), str(string))
- self._text_view.scroll_to_iter(self._text_buffer.get_end_iter(), .25, False, 0.5, 0.5)
-
- # grab the focus again (necessary e.g. after symbol insert)
- self._text_view.grab_focus()
-
- @property
- def indentation(self):
- """
- Return the indentation string of the line at the cursor
- """
- i_start = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
- i_start.set_line_offset(0)
-
- i_end = i_start.copy()
- i_end.forward_to_line_end()
- string = self._text_buffer.get_text(i_start, i_end, False)
-
- match = self.__PATTERN_INDENT.match(string)
- if match:
- return match.group()
- else:
- return ""
-
- def select(self, start_offset, end_offset):
- """
- Select a range of text and scroll the view to the right position
- """
- # select
- it_start = self._text_buffer.get_iter_at_offset(start_offset)
- it_end = self._text_buffer.get_iter_at_offset(end_offset)
- self._text_buffer.select_range(it_start, it_end)
- # scroll
- self._text_view.scroll_to_iter(it_end, .25, False, 0.5, 0.5)
-
- def select_lines(self, start_line, end_line=None):
- """
- Select a range of lines in the text
-
- @param start_line: the first line to select (counting from 0)
- @param end_line: the last line to select (if None only the first line is selected)
- """
- it_start = self._text_buffer.get_iter_at_line(start_line)
- if end_line:
- it_end = self._text_buffer.get_iter_at_line(end_line)
- else:
- it_end = it_start.copy()
- it_end.forward_to_line_end()
- # select
- self._text_buffer.select_range(it_start, it_end)
- # scroll
- self._text_view.scroll_to_iter(it_end, .25, False, 0.5, 0.5)
-
- #
- # markers are used for highlighting (anonymous)
- #
-
- def register_marker_type(self, marker_type, background_color, anonymous=True):
- """
- @param marker_type: a string
- @param background_color: a hex color
- @param anonymous: markers of an anonymous type may not be activated and do not get a unique ID
- """
- assert not marker_type in self._marker_types.keys()
-
- # create Gtk.TextTag
- tag = self._text_buffer.create_tag(marker_type, background=background_color)
-
- self._tags.append(tag)
-
- # create a MarkerTypeRecord for this type
- self._marker_types[marker_type] = self.MarkerTypeRecord(tag, anonymous)
-
- def create_marker(self, marker_type, start_offset, end_offset):
- """
- Mark a section of the text
-
- @param marker_type: type string
- @return: a Marker object if the type is not anonymous or None otherwise
- """
-
- # check offsets
- if start_offset < 0:
- self.__log.error("create_marker(): start offset out of range (%s < 0)" % start_offset)
- return
-
- buffer_end_offset = self._text_buffer.get_end_iter().get_offset()
-
- if end_offset > buffer_end_offset:
- self.__log.error("create_marker(): end offset out of range (%s > %s)" % (end_offset, buffer_end_offset))
-
-
- type_record = self._marker_types[marker_type]
-
- # hightlight
- left = self._text_buffer.get_iter_at_offset(start_offset)
- right = self._text_buffer.get_iter_at_offset(end_offset)
- self._text_buffer.apply_tag_by_name(marker_type, left, right)
-
- if type_record.anonymous:
- # create TextMarks
- left_mark = self._text_buffer.create_mark(None, left, True)
- right_mark = self._text_buffer.create_mark(None, right, False)
-
- # create Marker object
- marker = self.Marker(left_mark, right_mark, None, marker_type)
-
- # store Marker
- type_record.markers.append(marker)
-
- return None
- else:
- # create unique marker id
- id = str(uuid1())
-
- # create Marker object and put into map
- left_mark = self._text_buffer.create_mark(id, left, True)
- right_mark = self._text_buffer.create_mark(None, right, False)
- marker = self.Marker(left_mark, right_mark, id, marker_type)
-
- # store Marker
- self._markers[id] = marker
- type_record.markers.append(marker)
-
- return marker
-
- def remove_marker(self, marker):
- """
- @param marker: the Marker object to remove
- """
- # create TextIters from TextMarks
- left_iter = self._text_buffer.get_iter_at_mark(marker.left_mark)
- right_iter = self._text_buffer.get_iter_at_mark(marker.right_mark)
-
- # remove TextTag
- type_record = self._marker_types[marker.type]
- self._text_buffer.remove_tag(type_record.tag, left_iter, right_iter)
-
- # remove TextMarks
- self._text_buffer.delete_mark(marker.left_mark)
- self._text_buffer.delete_mark(marker.right_mark)
-
- # remove Marker from MarkerTypeRecord
- i = type_record.markers.index(marker)
- del type_record.markers[i]
-
- # remove from id map
- del self._markers[marker.id]
-
- def remove_markers(self, marker_type):
- """
- Remove all markers of a certain type
- """
- type_record = self._marker_types[marker_type]
-
- for marker in type_record.markers:
- assert not marker.left_mark.get_deleted()
- assert not marker.right_mark.get_deleted()
-
- # create TextIters from TextMarks
- left_iter = self._text_buffer.get_iter_at_mark(marker.left_mark)
- right_iter = self._text_buffer.get_iter_at_mark(marker.right_mark)
-
- # remove TextTag
- self._text_buffer.remove_tag(type_record.tag, left_iter, right_iter)
-
- # remove TextMarks
- self._text_buffer.delete_mark(marker.left_mark)
- self._text_buffer.delete_mark(marker.right_mark)
-
- if not type_record.anonymous:
- # remove Marker from id map
- del self._markers[marker.id]
-
- # remove markers from MarkerTypeRecord
- type_record.markers = []
-
- def replace_marker_content(self, marker, content):
- # get TextIters
- left = self._text_buffer.get_iter_at_mark(marker.left_mark)
- right = self._text_buffer.get_iter_at_mark(marker.right_mark)
-
- # replace
- self._text_buffer.delete(left, right)
- left = self._text_buffer.get_iter_at_mark(marker.left_mark)
- self._text_buffer.insert(left, content)
-
- # remove Marker
- self.remove_marker(marker)
-
- def on_marker_activated(self, marker, event):
- """
- A marker has been activated
-
- To be overridden
-
- @param id: id of the activated marker
- @param event: the GdkEvent of the mouse click (for raising context menus)
- """
-
- @property
- def completion_handlers(self):
- """
- To be overridden
-
- @return: a list of objects implementing CompletionHandler
- """
- return []
-
- def init(self, file, context):
- """
- @param file: File object
- @param context: WindowContext object
- """
-
- def on_save(self):
- """
- The file has been saved to its original location
- """
-
- def on_cursor_moved(self, offset):
- """
- The cursor has moved
- """
-
- def destroy(self):
- """
- The edited file has been closed or saved as another file
- """
- self.__log.debug("destroy")
-
- # disconnect signal handlers
- for handler in self.__view_signal_handlers:
- self._text_view.disconnect(handler)
-
- for handler in self.__buffer_signal_handlers:
- self._text_buffer.disconnect(handler)
-
- # delete the tags that were created for markers
- table = self._text_buffer.get_tag_table()
- for tag in self._tags:
- table.remove(tag)
-
- # destroy the template delegate
- self._template_delegate.destroy()
- del self._template_delegate
-
- # destroy the views associated to this editor
- for i in self._window_context.editor_scope_views[self]:
- self._window_context.editor_scope_views[self][i].destroy()
- del self._window_context.editor_scope_views[self]
-
- # unreference the tab decorator
- del self._tab_decorator
-
- # destroy the completion distributor
- if self._completion_distributor != None:
- self._completion_distributor.destroy()
- del self._completion_distributor
-
- # unreference the window context
- del self._window_context
-
- def __del__(self):
- self._log.debug("Properly destroyed %s" % self)
+ """
+ The base class for editors. This manages
+ - the subclass life-cycle
+ - the marker framework
+ - change monitoring
+ - drag'n'drop support
+ """
+
+ __log = getLogger("Editor")
+
+
+ class Marker(object):
+ """
+ Markers refer to and highlight a range of text in the TextBuffer decorated by
+ an Editor. They are used for highlighting issues.
+
+ Each Marker instance stores two Gtk.TextMark objects refering to the start and
+ end of the text range.
+ """
+ def __init__(self, left_mark, right_mark, id, type):
+ """
+ @param left_mark: a Gtk.TextMark
+ @param right_mark: a Gtk.TextMark
+ @param id: a unique string
+ @param type: a marker type string
+ """
+ self.left_mark = left_mark
+ self.right_mark = right_mark
+ self.type = type
+ self.id = id
+
+
+ class MarkerTypeRecord(object):
+ """
+ This used for managing Marker types
+ """
+ def __init__(self, tag, anonymous):
+ """
+ @param tag: a Gtk.TextTag
+ """
+ self.tag = tag
+ self.anonymous = anonymous
+ self.markers = []
+
+
+ __PATTERN_INDENT = re.compile("[ \t]+")
+
+
+ # A list of file extensions
+ #
+ # If one or more files with one of these extensions is dragged and dropped on the editor,
+ # the Editor.drag_drop_received method is called. An empty list disables the dnd support.
+ dnd_extensions = []
+
+
+ def __init__(self, tab_decorator, file):
+ self._tab_decorator = tab_decorator
+ self._file = file
+ self._text_buffer = tab_decorator.tab.get_document()
+ self._text_view = tab_decorator.tab.get_view()
+
+ # create template delegate
+ self._template_delegate = TemplateDelegate(self)
+
+ # hook completion handlers of the subclassing Editor
+ completion_handlers = self.completion_handlers
+ if len(completion_handlers):
+ self._completion_distributor = CompletionDistributor(self, completion_handlers)
+ else:
+ self._completion_distributor = None
+
+ #
+ # init marker framework
+ #
+
+ # needed for cleanup
+ self._tags = []
+ self._marker_types = {} # {marker type -> MarkerTypeRecord object}
+ self._markers = {} # { marker id -> marker object }
+
+
+ self._window_context = self._tab_decorator._window_decorator._window_context
+ self._window_context.create_editor_views(self, file)
+
+
+ self._offset = None # used by move_cursor
+
+ self.__view_signal_handlers = [
+ self._text_view.connect("button-press-event", self.__on_button_pressed),
+ self._text_view.connect("key-release-event", self.__on_key_released),
+ self._text_view.connect("button-release-event", self.__on_button_released) ]
+
+ self.__buffer_change_timestamp = time.time()
+ self.__buffer_signal_handlers = [
+ self._text_buffer.connect("changed", self.__on_buffer_changed) ]
+
+ # dnd support
+ if len(self.dnd_extensions) > 0:
+ self.__view_signal_handlers.append(
+ self._text_view.connect("drag-data-received", self.__on_drag_data_received))
+
+ # start life-cycle for subclass
+ self.init(file, self._window_context)
+
+ def __on_buffer_changed(self, text_buffer):
+ """
+ Store the timestamp of the last buffer change
+ """
+ self.__buffer_change_timestamp = time.time()
+
+ def __on_drag_data_received(self, widget, context, x, y, data, info, timestamp):
+ """
+ The drag destination received the data from the drag operation
+
+ @param widget: the widget that received the signal
+ @param context: the Gdk.DragContext
+ @param x: the X position of the drop
+ @param y: the Y position of the drop
+ @param data: a Gtk.SelectionData object
+ @param info: an integer ID for the drag
+ @param timestamp: the time of the drag event
+ """
+ self.__log.debug("drag-data-received")
+
+ files = []
+ match = False
+
+ for uri in data.get_uris():
+ file = File(uri)
+ files.append(file)
+ if file.extension.lower() in self.dnd_extensions:
+ match = True
+
+ if match:
+ self.drag_drop_received(files)
+
+ def __on_key_released(self, *args):
+ """
+ This helps to call 'move_cursor'
+ """
+ offset = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert()).get_offset()
+ if offset != self._offset:
+ self._offset = offset
+ self.on_cursor_moved(offset)
+
+ def __on_button_released(self, *args):
+ """
+ This helps to call 'move_cursor'
+ """
+ offset = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert()).get_offset()
+ if offset != self._offset:
+ self._offset = offset
+ self.on_cursor_moved(offset)
+
+ def __on_button_pressed(self, text_view, event):
+ """
+ Mouse button has been pressed on the TextView
+ """
+ if event.button == 3: # right button
+ x, y = text_view.get_pointer()
+ x, y = text_view.window_to_buffer_coords(Gtk.TextWindowType.WIDGET, x, y)
+ it = text_view.get_iter_at_location(x, y)
+
+ self.__log.debug("Right button pressed at offset %s" % it.get_offset())
+
+ #
+ # find Marker at this position
+ #
+ while True:
+ for mark in it.get_marks():
+ name = mark.get_name()
+
+ self.__log.debug("Found TextMark '%s' at offset %s" % (name, it.get_offset()))
+
+ if name:
+ if name in self._markers.keys():
+ marker = self._markers[name]
+ return self.on_marker_activated(marker, event)
+ else:
+ self.__log.warning("No marker found for TextMark '%s'" % name)
+ else:
+ # FIXME: this is not safe - use another symbol for right boundaries!
+ self.__log.debug("Unnamed TextMark found, outside of any Markers")
+ return
+
+ # move left by one char and continue
+ if not it.backward_char():
+ # start of buffer reached
+ return
+
+ elif event.button == 1 and event.get_state() & Gdk.ModifierType.CONTROL_MASK:
+ x, y = text_view.get_pointer()
+ x, y = text_view.window_to_buffer_coords(Gtk.TextWindowType.WIDGET, x, y)
+ it = text_view.get_iter_at_location(x, y)
+ # notify subclass
+ self._ctrl_left_clicked(it)
+
+ def _ctrl_left_clicked(self, it):
+ """
+ Left-clicked on the editor with Ctrl modifier key pressed
+ @param it: the Gtk.TextIter at the clicked position
+ """
+
+ @property
+ def file(self):
+ return self._file
+
+ @property
+ def tab_decorator(self):
+ return self._tab_decorator
+
+ def delete_at_cursor(self, offset):
+ """
+ Delete characters relative to the cursor position:
+
+ offset < 0 delete characters from offset to cursor
+ offset > 0 delete characters from cursor to offset
+ """
+ start = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
+ end = self._text_buffer.get_iter_at_offset(start.get_offset() + offset)
+ self._text_buffer.delete(start, end)
+
+ # methods/properties to be used/overridden by the subclass
+
+ @property
+ def extensions(self):
+ """
+ Return a list of extensions for which this Editor is to be activated
+ """
+ raise NotImplementedError
+
+ def drag_drop_received(self, files):
+ """
+ To be overridden
+
+ @param files: a list of File objects dropped on the Editor
+ """
+ pass
+
+ @property
+ def initial_timestamp(self):
+ """
+ Return an initial reference timestamp (this just has to be smaller than
+ every value returned by current_timestamp)
+ """
+ return 0
+
+ @property
+ def current_timestamp(self):
+ """
+ Return the current timestamp for buffer change recognition
+ """
+ return time.time()
+
+ def content_changed(self, reference_timestamp):
+ """
+ Return True if the content of this Editor has changed since a given
+ reference timestamp (this must be a timestamp as returned by current_timestamp)
+ """
+ return self.__buffer_change_timestamp > reference_timestamp
+
+ @property
+ def charset(self):
+ """
+ Return the character set used by this Editor
+ """
+ return self._text_buffer.get_encoding().get_charset()
+
+ @property
+ def content(self):
+ """
+ Return the string contained in the TextBuffer
+ """
+ return self._text_buffer.get_text(self._text_buffer.get_start_iter(),
+ self._text_buffer.get_end_iter(), False).decode(self.charset)
+
+ @property
+ def content_at_left_of_cursor(self):
+ """
+ Only return the content at left of the cursor
+ """
+ end_iter = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
+ return self._text_buffer.get_text(self._text_buffer.get_start_iter(),
+ end_iter, False).decode(self.charset)
+
+ def insert(self, source):
+ """
+ This may be overridden to catch special types like LaTeXSource
+ """
+ self.__log.debug("insert(%s)" % source)
+
+ if type(source) is Template:
+ self._template_delegate.insert(source)
+ else:
+ self._text_buffer.insert_at_cursor(str(source))
+
+ # grab the focus again (necessary e.g. after symbol insert)
+ self._text_view.grab_focus()
+
+ def insert_at_offset(self, offset, string, scroll=False):
+ """
+ Insert a string at a certain offset
+
+ @param offset: a positive int
+ @param string: a str
+ @param scroll: if True the view is scrolled to the insert position
+ """
+ iter = self._text_buffer.get_iter_at_offset(offset)
+ self._text_buffer.insert(iter, str(string))
+
+ if scroll:
+ self._text_view.scroll_to_iter(iter, .25, False, 0.5, 0.5)
+
+ # grab the focus again (necessary e.g. after symbol insert)
+ self._text_view.grab_focus()
+
+ def append(self, string):
+ """
+ Append some source (only makes sense with simple string) and scroll to it
+
+ @param string: a str
+ """
+ self._text_buffer.insert(self._text_buffer.get_end_iter(), str(string))
+ self._text_view.scroll_to_iter(self._text_buffer.get_end_iter(), .25, False, 0.5, 0.5)
+
+ # grab the focus again (necessary e.g. after symbol insert)
+ self._text_view.grab_focus()
+
+ @property
+ def indentation(self):
+ """
+ Return the indentation string of the line at the cursor
+ """
+ i_start = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
+ i_start.set_line_offset(0)
+
+ i_end = i_start.copy()
+ i_end.forward_to_line_end()
+ string = self._text_buffer.get_text(i_start, i_end, False)
+
+ match = self.__PATTERN_INDENT.match(string)
+ if match:
+ return match.group()
+ else:
+ return ""
+
+ def select(self, start_offset, end_offset):
+ """
+ Select a range of text and scroll the view to the right position
+ """
+ # select
+ it_start = self._text_buffer.get_iter_at_offset(start_offset)
+ it_end = self._text_buffer.get_iter_at_offset(end_offset)
+ self._text_buffer.select_range(it_start, it_end)
+ # scroll
+ self._text_view.scroll_to_iter(it_end, .25, False, 0.5, 0.5)
+
+ def select_lines(self, start_line, end_line=None):
+ """
+ Select a range of lines in the text
+
+ @param start_line: the first line to select (counting from 0)
+ @param end_line: the last line to select (if None only the first line is selected)
+ """
+ it_start = self._text_buffer.get_iter_at_line(start_line)
+ if end_line:
+ it_end = self._text_buffer.get_iter_at_line(end_line)
+ else:
+ it_end = it_start.copy()
+ it_end.forward_to_line_end()
+ # select
+ self._text_buffer.select_range(it_start, it_end)
+ # scroll
+ self._text_view.scroll_to_iter(it_end, .25, False, 0.5, 0.5)
+
+ #
+ # markers are used for highlighting (anonymous)
+ #
+
+ def register_marker_type(self, marker_type, background_color, anonymous=True):
+ """
+ @param marker_type: a string
+ @param background_color: a hex color
+ @param anonymous: markers of an anonymous type may not be activated and do not get a unique ID
+ """
+ assert not marker_type in self._marker_types.keys()
+
+ # create Gtk.TextTag
+ tag = self._text_buffer.create_tag(marker_type, background=background_color)
+
+ self._tags.append(tag)
+
+ # create a MarkerTypeRecord for this type
+ self._marker_types[marker_type] = self.MarkerTypeRecord(tag, anonymous)
+
+ def create_marker(self, marker_type, start_offset, end_offset):
+ """
+ Mark a section of the text
+
+ @param marker_type: type string
+ @return: a Marker object if the type is not anonymous or None otherwise
+ """
+
+ # check offsets
+ if start_offset < 0:
+ self.__log.error("create_marker(): start offset out of range (%s < 0)" % start_offset)
+ return
+
+ buffer_end_offset = self._text_buffer.get_end_iter().get_offset()
+
+ if end_offset > buffer_end_offset:
+ self.__log.error("create_marker(): end offset out of range (%s > %s)" % (end_offset, buffer_end_offset))
+
+
+ type_record = self._marker_types[marker_type]
+
+ # hightlight
+ left = self._text_buffer.get_iter_at_offset(start_offset)
+ right = self._text_buffer.get_iter_at_offset(end_offset)
+ self._text_buffer.apply_tag_by_name(marker_type, left, right)
+
+ if type_record.anonymous:
+ # create TextMarks
+ left_mark = self._text_buffer.create_mark(None, left, True)
+ right_mark = self._text_buffer.create_mark(None, right, False)
+
+ # create Marker object
+ marker = self.Marker(left_mark, right_mark, None, marker_type)
+
+ # store Marker
+ type_record.markers.append(marker)
+
+ return None
+ else:
+ # create unique marker id
+ id = str(uuid1())
+
+ # create Marker object and put into map
+ left_mark = self._text_buffer.create_mark(id, left, True)
+ right_mark = self._text_buffer.create_mark(None, right, False)
+ marker = self.Marker(left_mark, right_mark, id, marker_type)
+
+ # store Marker
+ self._markers[id] = marker
+ type_record.markers.append(marker)
+
+ return marker
+
+ def remove_marker(self, marker):
+ """
+ @param marker: the Marker object to remove
+ """
+ # create TextIters from TextMarks
+ left_iter = self._text_buffer.get_iter_at_mark(marker.left_mark)
+ right_iter = self._text_buffer.get_iter_at_mark(marker.right_mark)
+
+ # remove TextTag
+ type_record = self._marker_types[marker.type]
+ self._text_buffer.remove_tag(type_record.tag, left_iter, right_iter)
+
+ # remove TextMarks
+ self._text_buffer.delete_mark(marker.left_mark)
+ self._text_buffer.delete_mark(marker.right_mark)
+
+ # remove Marker from MarkerTypeRecord
+ i = type_record.markers.index(marker)
+ del type_record.markers[i]
+
+ # remove from id map
+ del self._markers[marker.id]
+
+ def remove_markers(self, marker_type):
+ """
+ Remove all markers of a certain type
+ """
+ type_record = self._marker_types[marker_type]
+
+ for marker in type_record.markers:
+ assert not marker.left_mark.get_deleted()
+ assert not marker.right_mark.get_deleted()
+
+ # create TextIters from TextMarks
+ left_iter = self._text_buffer.get_iter_at_mark(marker.left_mark)
+ right_iter = self._text_buffer.get_iter_at_mark(marker.right_mark)
+
+ # remove TextTag
+ self._text_buffer.remove_tag(type_record.tag, left_iter, right_iter)
+
+ # remove TextMarks
+ self._text_buffer.delete_mark(marker.left_mark)
+ self._text_buffer.delete_mark(marker.right_mark)
+
+ if not type_record.anonymous:
+ # remove Marker from id map
+ del self._markers[marker.id]
+
+ # remove markers from MarkerTypeRecord
+ type_record.markers = []
+
+ def replace_marker_content(self, marker, content):
+ # get TextIters
+ left = self._text_buffer.get_iter_at_mark(marker.left_mark)
+ right = self._text_buffer.get_iter_at_mark(marker.right_mark)
+
+ # replace
+ self._text_buffer.delete(left, right)
+ left = self._text_buffer.get_iter_at_mark(marker.left_mark)
+ self._text_buffer.insert(left, content)
+
+ # remove Marker
+ self.remove_marker(marker)
+
+ def on_marker_activated(self, marker, event):
+ """
+ A marker has been activated
+
+ To be overridden
+
+ @param id: id of the activated marker
+ @param event: the GdkEvent of the mouse click (for raising context menus)
+ """
+
+ @property
+ def completion_handlers(self):
+ """
+ To be overridden
+
+ @return: a list of objects implementing CompletionHandler
+ """
+ return []
+
+ def init(self, file, context):
+ """
+ @param file: File object
+ @param context: WindowContext object
+ """
+
+ def on_save(self):
+ """
+ The file has been saved to its original location
+ """
+
+ def on_cursor_moved(self, offset):
+ """
+ The cursor has moved
+ """
+
+ def destroy(self):
+ """
+ The edited file has been closed or saved as another file
+ """
+ self.__log.debug("destroy")
+
+ # disconnect signal handlers
+ for handler in self.__view_signal_handlers:
+ self._text_view.disconnect(handler)
+
+ for handler in self.__buffer_signal_handlers:
+ self._text_buffer.disconnect(handler)
+
+ # delete the tags that were created for markers
+ table = self._text_buffer.get_tag_table()
+ for tag in self._tags:
+ table.remove(tag)
+
+ # destroy the template delegate
+ self._template_delegate.destroy()
+ del self._template_delegate
+
+ # destroy the views associated to this editor
+ for i in self._window_context.editor_scope_views[self]:
+ self._window_context.editor_scope_views[self][i].destroy()
+ del self._window_context.editor_scope_views[self]
+
+ # unreference the tab decorator
+ del self._tab_decorator
+
+ # destroy the completion distributor
+ if self._completion_distributor != None:
+ self._completion_distributor.destroy()
+ del self._completion_distributor
+
+ # unreference the window context
+ del self._window_context
+
+ def __del__(self):
+ self._log.debug("Properly destroyed %s" % self)
class WindowContext(object):
- """
- The WindowContext is passed to Editors and is used to
- * retrieve View instances
- * activate a specific Editor instance
- * retrieve the currently active Editor
-
- This also creates and destroys the View instances.
- """
-
- _log = getLogger("WindowContext")
-
- def __init__(self, window_decorator, editor_scope_view_classes):
- """
- @param window_decorator: the GeditWindowDecorator this context corresponds to
- @param editor_scope_view_classes: a map from extension to list of View classes
- """
- self._window_decorator = window_decorator
- self._editor_scope_view_classes = editor_scope_view_classes
-
- self.window_scope_views = {} # maps view ids to View objects
- self.editor_scope_views = {} # maps Editor object to a map from ID to View object
-
- self._log.debug("init")
-
- def create_editor_views(self, editor, file):
- """
- Create instances of the editor specific Views for a given Editor instance
- and File
-
- Called by Editor base class
- """
- self.editor_scope_views[editor] = {}
- try:
- for id, clazz in self._editor_scope_view_classes[file.extension].iteritems():
- # create View instance and add it to the map
- self.editor_scope_views[editor][id] = clazz(self, editor)
-
- self._log.debug("Created view " + id)
- except KeyError:
- self._log.debug("No views for %s" % file.extension)
-
- ###
- # public interface
-
- @property
- def active_editor(self):
- """
- Return the active Editor instance
- """
- return self._window_decorator._active_tab_decorator.editor
-
- def activate_editor(self, file):
- """
- Activate the Editor containing a given File or open a new tab for it
-
- @param file: a File object
-
- @raise AssertError: if the file is no File object
- """
- assert type(file) is File
-
- self._window_decorator.activate_tab(file)
-
- def find_view(self, editor, view_id):
- """
- Return a View object
- """
- try:
- return self.editor_scope_views[editor][view_id]
- except KeyError:
- return self.window_scope_views[view_id]
-
- def set_action_enabled(self, action_id, enabled):
- """
- Enable/disable an IAction object
- """
- self._window_decorator._action_group.get_action(action_id).set_sensitive(enabled)
-
- def destroy(self):
- # unreference the window decorator
- del self._window_decorator
-
- def __del__(self):
- self._log.debug("Properly destroyed %s" % self)
-
+ """
+ The WindowContext is passed to Editors and is used to
+ * retrieve View instances
+ * activate a specific Editor instance
+ * retrieve the currently active Editor
+
+ This also creates and destroys the View instances.
+ """
+
+ _log = getLogger("WindowContext")
+
+ def __init__(self, window_decorator, editor_scope_view_classes):
+ """
+ @param window_decorator: the GeditWindowDecorator this context corresponds to
+ @param editor_scope_view_classes: a map from extension to list of View classes
+ """
+ self._window_decorator = window_decorator
+ self._editor_scope_view_classes = editor_scope_view_classes
+
+ self.window_scope_views = {} # maps view ids to View objects
+ self.editor_scope_views = {} # maps Editor object to a map from ID to View object
+
+ self._log.debug("init")
+
+ def create_editor_views(self, editor, file):
+ """
+ Create instances of the editor specific Views for a given Editor instance
+ and File
+
+ Called by Editor base class
+ """
+ self.editor_scope_views[editor] = {}
+ try:
+ for id, clazz in self._editor_scope_view_classes[file.extension].iteritems():
+ # create View instance and add it to the map
+ self.editor_scope_views[editor][id] = clazz(self, editor)
+
+ self._log.debug("Created view " + id)
+ except KeyError:
+ self._log.debug("No views for %s" % file.extension)
+
+ ###
+ # public interface
+
+ @property
+ def active_editor(self):
+ """
+ Return the active Editor instance
+ """
+ return self._window_decorator._active_tab_decorator.editor
+
+ def activate_editor(self, file):
+ """
+ Activate the Editor containing a given File or open a new tab for it
+
+ @param file: a File object
+
+ @raise AssertError: if the file is no File object
+ """
+ assert type(file) is File
+
+ self._window_decorator.activate_tab(file)
+
+ def find_view(self, editor, view_id):
+ """
+ Return a View object
+ """
+ try:
+ return self.editor_scope_views[editor][view_id]
+ except KeyError:
+ return self.window_scope_views[view_id]
+
+ def set_action_enabled(self, action_id, enabled):
+ """
+ Enable/disable an IAction object
+ """
+ self._window_decorator._action_group.get_action(action_id).set_sensitive(enabled)
+
+ def destroy(self):
+ # unreference the window decorator
+ del self._window_decorator
+
+ def __del__(self):
+ self._log.debug("Properly destroyed %s" % self)
+
from os import remove
import os.path
@@ -1005,366 +1005,363 @@ import urllib
import urlparse
def fixurl(url):
- r"""From http://stackoverflow.com/questions/804336/best-way-to-convert-a-unicode-url-to-ascii-utf-8-percent-escaped-in-python/805166#805166 .
- Was named canonurl(). Comments added to the original are prefixed with ##.
-
- Return the canonical, ASCII-encoded form of a UTF-8 encoded URL, or ''
- if the URL looks invalid.
-
- >>> canonurl(' ')
- ''
- >>> canonurl('www.google.com')
- 'http://www.google.com/'
- >>> canonurl('bad-utf8.com/path\xff/file')
- ''
- >>> canonurl('svn://blah.com/path/file')
- 'svn://blah.com/path/file'
- >>> canonurl('1234://badscheme.com')
- ''
- >>> canonurl('bad$scheme://google.com')
- ''
- >>> canonurl('site.badtopleveldomain')
- ''
- >>> canonurl('site.com:badport')
- ''
- >>> canonurl('http://123.24.8.240/blah')
- 'http://123.24.8.240/blah'
- >>> canonurl('http://123.24.8.240:1234/blah?q#f')
- 'http://123.24.8.240:1234/blah?q#f'
- >>> canonurl('\xe2\x9e\xa1.ws') # tinyarro.ws
- 'http://xn--hgi.ws/'
- >>> canonurl(' http://www.google.com:80/path/file;params?query#fragment ')
- 'http://www.google.com:80/path/file;params?query#fragment'
- >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5')
- 'http://xn--hgi.ws/%E2%99%A5'
- >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5/pa%2Fth')
- 'http://xn--hgi.ws/%E2%99%A5/pa/th'
- >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5/pa%2Fth;par%2Fams?que%2Fry=a&b=c')
- 'http://xn--hgi.ws/%E2%99%A5/pa/th;par/ams?que/ry=a&b=c'
- >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5?\xe2\x99\xa5#\xe2\x99\xa5')
- 'http://xn--hgi.ws/%E2%99%A5?%E2%99%A5#%E2%99%A5'
- >>> canonurl('http://\xe2\x9e\xa1.ws/%e2%99%a5?%E2%99%A5#%E2%99%A5')
- 'http://xn--hgi.ws/%E2%99%A5?%E2%99%A5#%E2%99%A5'
- >>> canonurl('http://badutf8pcokay.com/%FF?%FE#%FF')
- 'http://badutf8pcokay.com/%FF?%FE#%FF'
- >>> len(canonurl('google.com/' + 'a' * 16384))
- 4096
- """
- # strip spaces at the ends and ensure it's prefixed with 'scheme://'
- url = url.strip()
- if not url:
- return ''
- if not urlparse.urlsplit(url).scheme:
- ## We usually deal with local files here
- url = 'file://' + url
- ## url = 'http://' + url
-
- # turn it into Unicode
- try:
- url = unicode(url, 'utf-8')
- except Exception, exc: #UnicodeDecodeError, exc:
- ## It often happens that the url is already "python unicode" encoded
- if not str(exc) == "decoding Unicode is not supported":
- return '' # bad UTF-8 chars in URL
- ## If the exception is indeed "decoding Unicode is not supported"
- ## this generally means that url is already unicode encoded,
- ## so we can just continue (see http://www.red-mercury.com/blog/eclectic-tech/python-mystery-of-the-day/ )
-
- # parse the URL into its components
- parsed = urlparse.urlsplit(url)
- scheme, netloc, path, query, fragment = parsed
-
- # ensure scheme is a letter followed by letters, digits, and '+-.' chars
- if not re.match(r'[a-z][-+.a-z0-9]*$', scheme, flags=re.I):
- return ''
- scheme = str(scheme)
-
- ## We mostly deal with local files here, and the following check
- ## would exclude all local files, so we drop it.
- # ensure domain and port are valid, eg: sub.domain.<1-to-6-TLD-chars>[:port]
- #~ match = re.match(r'(.+\.[a-z0-9]{1,6})(:\d{1,5})?$', netloc, flags=re.I)
- #~ if not match:
- #~ print "return 4"
- #~ return ''
- #~ domain, port = match.groups()
- #~ netloc = domain + (port if port else '')
- netloc = netloc.encode('idna')
-
- # ensure path is valid and convert Unicode chars to %-encoded
- if not path:
- path = '/' # eg: 'http://google.com' -> 'http://google.com/'
- path = urllib.quote(urllib.unquote(path.encode('utf-8')), safe='/;')
-
- # ensure query is valid
- query = urllib.quote(urllib.unquote(query.encode('utf-8')), safe='=&?/')
-
- # ensure fragment is valid
- fragment = urllib.quote(urllib.unquote(fragment.encode('utf-8')))
-
- # piece it all back together, truncating it to a maximum of 4KB
- url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
- return url[:4096]
+ r"""From http://stackoverflow.com/questions/804336/best-way-to-convert-a-unicode-url-to-ascii-utf-8-percent-escaped-in-python/805166#805166 .
+ Was named canonurl(). Comments added to the original are prefixed with ##.
+
+ Return the canonical, ASCII-encoded form of a UTF-8 encoded URL, or ''
+ if the URL looks invalid.
+
+ >>> canonurl(' ')
+ ''
+ >>> canonurl('www.google.com')
+ 'http://www.google.com/'
+ >>> canonurl('bad-utf8.com/path\xff/file')
+ ''
+ >>> canonurl('svn://blah.com/path/file')
+ 'svn://blah.com/path/file'
+ >>> canonurl('1234://badscheme.com')
+ ''
+ >>> canonurl('bad$scheme://google.com')
+ ''
+ >>> canonurl('site.badtopleveldomain')
+ ''
+ >>> canonurl('site.com:badport')
+ ''
+ >>> canonurl('http://123.24.8.240/blah')
+ 'http://123.24.8.240/blah'
+ >>> canonurl('http://123.24.8.240:1234/blah?q#f')
+ 'http://123.24.8.240:1234/blah?q#f'
+ >>> canonurl('\xe2\x9e\xa1.ws') # tinyarro.ws
+ 'http://xn--hgi.ws/'
+ >>> canonurl(' http://www.google.com:80/path/file;params?query#fragment ')
+ 'http://www.google.com:80/path/file;params?query#fragment'
+ >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5')
+ 'http://xn--hgi.ws/%E2%99%A5'
+ >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5/pa%2Fth')
+ 'http://xn--hgi.ws/%E2%99%A5/pa/th'
+ >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5/pa%2Fth;par%2Fams?que%2Fry=a&b=c')
+ 'http://xn--hgi.ws/%E2%99%A5/pa/th;par/ams?que/ry=a&b=c'
+ >>> canonurl('http://\xe2\x9e\xa1.ws/\xe2\x99\xa5?\xe2\x99\xa5#\xe2\x99\xa5')
+ 'http://xn--hgi.ws/%E2%99%A5?%E2%99%A5#%E2%99%A5'
+ >>> canonurl('http://\xe2\x9e\xa1.ws/%e2%99%a5?%E2%99%A5#%E2%99%A5')
+ 'http://xn--hgi.ws/%E2%99%A5?%E2%99%A5#%E2%99%A5'
+ >>> canonurl('http://badutf8pcokay.com/%FF?%FE#%FF')
+ 'http://badutf8pcokay.com/%FF?%FE#%FF'
+ >>> len(canonurl('google.com/' + 'a' * 16384))
+ 4096
+ """
+ # strip spaces at the ends and ensure it's prefixed with 'scheme://'
+ url = url.strip()
+ if not url:
+ return ''
+ if not urlparse.urlsplit(url).scheme:
+ ## We usually deal with local files here
+ url = 'file://' + url
+ ## url = 'http://' + url
+
+ # turn it into Unicode
+ try:
+ url = unicode(url, 'utf-8')
+ except Exception, exc: #UnicodeDecodeError, exc:
+ ## It often happens that the url is already "python unicode" encoded
+ if not str(exc) == "decoding Unicode is not supported":
+ return '' # bad UTF-8 chars in URL
+ ## If the exception is indeed "decoding Unicode is not supported"
+ ## this generally means that url is already unicode encoded,
+ ## so we can just continue (see http://www.red-mercury.com/blog/eclectic-tech/python-mystery-of-the-day/ )
+
+ # parse the URL into its components
+ parsed = urlparse.urlsplit(url)
+ scheme, netloc, path, query, fragment = parsed
+
+ # ensure scheme is a letter followed by letters, digits, and '+-.' chars
+ if not re.match(r'[a-z][-+.a-z0-9]*$', scheme, flags=re.I):
+ return ''
+ scheme = str(scheme)
+
+ ## We mostly deal with local files here, and the following check
+ ## would exclude all local files, so we drop it.
+ # ensure domain and port are valid, eg: sub.domain.<1-to-6-TLD-chars>[:port]
+ #~ match = re.match(r'(.+\.[a-z0-9]{1,6})(:\d{1,5})?$', netloc, flags=re.I)
+ #~ if not match:
+ #~ print "return 4"
+ #~ return ''
+ #~ domain, port = match.groups()
+ #~ netloc = domain + (port if port else '')
+ netloc = netloc.encode('idna')
+
+ # ensure path is valid and convert Unicode chars to %-encoded
+ if not path:
+ path = '/' # eg: 'http://google.com' -> 'http://google.com/'
+ path = urllib.quote(urllib.unquote(path.encode('utf-8')), safe='/;')
+
+ # ensure query is valid
+ query = urllib.quote(urllib.unquote(query.encode('utf-8')), safe='=&?/')
+
+ # ensure fragment is valid
+ fragment = urllib.quote(urllib.unquote(fragment.encode('utf-8')))
+
+ # piece it all back together, truncating it to a maximum of 4KB
+ url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
+ return url[:4096]
class File(object):
- """
- This is an object-oriented wrapper for all the os.* stuff. A File object
- represents the reference to a file.
- """
-
- # TODO: use Gio.File as underlying implementation
-
- @staticmethod
- def create_from_relative_path(relative_path, working_directory):
- """
- Create a File from a path relative to some working directory.
-
- File.create_from_relative_path('../sub/file.txt', '/home/michael/base') == File('/home/michael/sub/file.txt')
-
- @param relative_path: a relative path, e.g. '../../dir/myfile.txt'
- @param working_directory: an absolute directory to be used as the starting point for the relative path
- """
- absolute_path = os.path.abspath(os.path.join(working_directory, relative_path))
- return File(absolute_path)
-
- @staticmethod
- def is_absolute(path):
- return os.path.isabs(path)
-
- __log = getLogger("File")
-
- _DEFAULT_SCHEME = "file://"
-
- def __init__(self, uri):
- """
- @param uri: any URI, URL or local filename
- """
- if uri is None:
- raise ValueError("URI must not be None")
-
- self._uri = urlparse.urlparse(uri)
- if len(self._uri.scheme) == 0:
- # prepend default scheme if missing
- self._uri = urlparse.urlparse("%s%s" % (self._DEFAULT_SCHEME, uri))
-
- def create(self, content=None):
- """
- Create a the File in the file system
- """
- f = open(self.path, "w")
- if content is not None:
- f.write(content)
- f.close()
-
- @property
- def path(self):
- """
- Returns '/home/user/image.jpg' for 'file:///home/user/image.jpg'
- """
- return urllib.url2pathname(self._uri.path)
-
- @property
- def extension(self):
- """
- Returns '.jpg' for 'file:///home/user/image.jpg'
- """
- return os.path.splitext(self.path)[1]
-
- @property
- def shortname(self):
- """
- Returns '/home/user/image' for 'file:///home/user/image.jpg'
- """
- return os.path.splitext(self.path)[0]
-
- @property
- def basename(self):
- """
- Returns 'image.jpg' for 'file:///home/user/image.jpg'
- """
- return os.path.basename(self.path)
-
- @property
- def shortbasename(self):
- """
- Returns 'image' for 'file:///home/user/image.jpg'
- """
- return os.path.splitext(os.path.basename(self.path))[0]
-
- @property
- def dirname(self):
- """
- Returns '/home/user' for 'file:///home/user/image.jpg'
- """
- return os.path.dirname(self.path)
-
- @property
- def uri(self):
- # TODO: urllib.quote doesn't support utf-8
- return fixurl(self._uri.geturl())
-
- @property
- def exists(self):
- return os.path.exists(self.path)
-
- @property
- def mtime(self):
- if self.exists:
- return os.path.getmtime(self.path)
- else:
- raise IOError("File not found")
-
- def find_neighbors(self, extension):
- """
- Find other files in the directory of this one having
- a certain extension
-
- @param extension: a file extension pattern like '.tex' or '.*'
- """
-
- # TODO: glob is quite expensive, find a simpler way for this
-
- try:
- filenames = glob("%s/*%s" % (self.dirname, extension))
- neighbors = [File(filename) for filename in filenames]
- return neighbors
-
- except Exception, e:
- # as seen in Bug #2002630 the glob() call compiles a regex and so we must be prepared
- # for an exception from that because the shortname may contain regex characters
-
- # TODO: a more robust solution would be an escape() method for re
-
- self.__log.debug("find_neighbors: %s" % e)
-
- return []
-
- @property
- def siblings(self):
- """
- Find other files in the directory of this one having the same
- basename. This means for a file '/dir/a.doc' this method returns
- [ '/dir/a.tmp', '/dir/a.sh' ]
- """
- siblings = []
- try:
- filenames = glob("%s.*" % self.shortname)
- siblings = [File(filename) for filename in filenames]
- except Exception, e:
- # as seen in Bug #2002630 the glob() call compiles a regex and so we must be prepared
- # for an exception from that because the shortname may contain regex characters
-
- # TODO: a more robust solution would be an escape() method for re
-
- self.__log.debug("find_siblings: %s" % e)
- return siblings
-
- def relativize(self, base, allow_up_level=False):
- """
- Relativize the path of this File against a base directory. That means that e.g.
- File("/home/user/doc.tex").relativize("/home") == "user/doc.tex"
-
- If up-level references are NOT allowed but necessary (e.g. base='/a/b/c', path='/a/b/d')
- then the absolute path is returned.
-
- @param base: the base directory to relativize against
- @param allow_up_level: allow up-level references (../../) or not
- """
- if allow_up_level:
- # TODO: os.path.relpath from Python 2.6 does the job
-
- return relpath(base, self.path)
- else:
- # TODO: why do we need this?
-
- # relative path must be 'below' base path
- if len(base) >= len(self.path):
- return self.path
- if self.path[:len(base)] == base:
- # bases match, return relative part
- return self.path[len(base)+1:]
- return self.path
-
- def relativize_shortname(self, base):
- """
- Relativize the path of this File and return only the shortname of the resulting
- relative path. That means that e.g.
- File("/home/user/doc.tex").relativize_shortname("/home") == "user/doc"
-
- This is just a convenience method.
-
- @param base: the base directory to relativize against
- """
- relative_path = self.relativize(base)
- return os.path.splitext(relative_path)[0]
-
- def delete(self):
- """
- Delete the File from the file system
-
- @raise OSError:
- """
- if self.exists:
- remove(self.path)
- else:
- raise IOError("File not found")
-
- def __eq__(self, other):
- """
- Override == operator
- """
- try:
- return self.uri == other.uri
- except AttributeError: # no File object passed or None
- # returning NotImplemented is bad because we have to
- # compare None with File
- return False
-
- def __ne__(self, other):
- """
- Override != operator
- """
- return not self.__eq__(other)
-
- def __str__(self):
- return self.uri
-
- def __cmp__(self, other):
- try:
- return self.basename.__cmp__(other.basename)
- except AttributeError: # no File object passed or None
- # returning NotImplemented is bad because we have to
- # compare None with File
- return False
+ """
+ This is an object-oriented wrapper for all the os.* stuff. A File object
+ represents the reference to a file.
+ """
+
+ # TODO: use Gio.File as underlying implementation
+
+ @staticmethod
+ def create_from_relative_path(relative_path, working_directory):
+ """
+ Create a File from a path relative to some working directory.
+
+ File.create_from_relative_path('../sub/file.txt', '/home/michael/base') == File('/home/michael/sub/file.txt')
+
+ @param relative_path: a relative path, e.g. '../../dir/myfile.txt'
+ @param working_directory: an absolute directory to be used as the starting point for the relative path
+ """
+ absolute_path = os.path.abspath(os.path.join(working_directory, relative_path))
+ return File(absolute_path)
+
+ @staticmethod
+ def is_absolute(path):
+ return os.path.isabs(path)
+
+ __log = getLogger("File")
+
+ _DEFAULT_SCHEME = "file://"
+
+ def __init__(self, uri):
+ """
+ @param uri: any URI, URL or local filename
+ """
+ if uri is None:
+ raise ValueError("URI must not be None")
+
+ self._uri = urlparse.urlparse(uri)
+ if len(self._uri.scheme) == 0:
+ # prepend default scheme if missing
+ self._uri = urlparse.urlparse("%s%s" % (self._DEFAULT_SCHEME, uri))
+
+ def create(self, content=None):
+ """
+ Create a the File in the file system
+ """
+ f = open(self.path, "w")
+ if content is not None:
+ f.write(content)
+ f.close()
+
+ @property
+ def path(self):
+ """
+ Returns '/home/user/image.jpg' for 'file:///home/user/image.jpg'
+ """
+ return urllib.url2pathname(self._uri.path)
+
+ @property
+ def extension(self):
+ """
+ Returns '.jpg' for 'file:///home/user/image.jpg'
+ """
+ return os.path.splitext(self.path)[1]
+
+ @property
+ def shortname(self):
+ """
+ Returns '/home/user/image' for 'file:///home/user/image.jpg'
+ """
+ return os.path.splitext(self.path)[0]
+
+ @property
+ def basename(self):
+ """
+ Returns 'image.jpg' for 'file:///home/user/image.jpg'
+ """
+ return os.path.basename(self.path)
+
+ @property
+ def shortbasename(self):
+ """
+ Returns 'image' for 'file:///home/user/image.jpg'
+ """
+ return os.path.splitext(os.path.basename(self.path))[0]
+
+ @property
+ def dirname(self):
+ """
+ Returns '/home/user' for 'file:///home/user/image.jpg'
+ """
+ return os.path.dirname(self.path)
+
+ @property
+ def uri(self):
+ # TODO: urllib.quote doesn't support utf-8
+ return fixurl(self._uri.geturl())
+
+ @property
+ def exists(self):
+ return os.path.exists(self.path)
+
+ @property
+ def mtime(self):
+ if self.exists:
+ return os.path.getmtime(self.path)
+ else:
+ raise IOError("File not found")
+
+ def find_neighbors(self, extension):
+ """
+ Find other files in the directory of this one having
+ a certain extension
+
+ @param extension: a file extension pattern like '.tex' or '.*'
+ """
+
+ # TODO: glob is quite expensive, find a simpler way for this
+
+ try:
+ filenames = glob("%s/*%s" % (self.dirname, extension))
+ neighbors = [File(filename) for filename in filenames]
+ return neighbors
+
+ except Exception, e:
+ # as seen in Bug #2002630 the glob() call compiles a regex and so we must be prepared
+ # for an exception from that because the shortname may contain regex characters
+
+ # TODO: a more robust solution would be an escape() method for re
+
+ self.__log.debug("find_neighbors: %s" % e)
+
+ return []
+
+ @property
+ def siblings(self):
+ """
+ Find other files in the directory of this one having the same
+ basename. This means for a file '/dir/a.doc' this method returns
+ [ '/dir/a.tmp', '/dir/a.sh' ]
+ """
+ siblings = []
+ try:
+ filenames = glob("%s.*" % self.shortname)
+ siblings = [File(filename) for filename in filenames]
+ except Exception, e:
+ # as seen in Bug #2002630 the glob() call compiles a regex and so we must be prepared
+ # for an exception from that because the shortname may contain regex characters
+
+ # TODO: a more robust solution would be an escape() method for re
+
+ self.__log.debug("find_siblings: %s" % e)
+ return siblings
+
+ def relativize(self, base, allow_up_level=False):
+ """
+ Relativize the path of this File against a base directory. That means that e.g.
+ File("/home/user/doc.tex").relativize("/home") == "user/doc.tex"
+
+ If up-level references are NOT allowed but necessary (e.g. base='/a/b/c', path='/a/b/d')
+ then the absolute path is returned.
+
+ @param base: the base directory to relativize against
+ @param allow_up_level: allow up-level references (../../) or not
+ """
+ if allow_up_level:
+ # TODO: os.path.relpath from Python 2.6 does the job
+
+ return relpath(base, self.path)
+ else:
+ # TODO: why do we need this?
+
+ # relative path must be 'below' base path
+ if len(base) >= len(self.path):
+ return self.path
+ if self.path[:len(base)] == base:
+ # bases match, return relative part
+ return self.path[len(base)+1:]
+ return self.path
+
+ def relativize_shortname(self, base):
+ """
+ Relativize the path of this File and return only the shortname of the resulting
+ relative path. That means that e.g.
+ File("/home/user/doc.tex").relativize_shortname("/home") == "user/doc"
+
+ This is just a convenience method.
+
+ @param base: the base directory to relativize against
+ """
+ relative_path = self.relativize(base)
+ return os.path.splitext(relative_path)[0]
+
+ def delete(self):
+ """
+ Delete the File from the file system
+
+ @raise OSError:
+ """
+ if self.exists:
+ remove(self.path)
+ else:
+ raise IOError("File not found")
+
+ def __eq__(self, other):
+ """
+ Override == operator
+ """
+ try:
+ return self.uri == other.uri
+ except AttributeError: # no File object passed or None
+ # returning NotImplemented is bad because we have to
+ # compare None with File
+ return False
+
+ def __ne__(self, other):
+ """
+ Override != operator
+ """
+ return not self.__eq__(other)
+
+ def __str__(self):
+ return self.uri
+
+ def __cmp__(self, other):
+ try:
+ return self.basename.__cmp__(other.basename)
+ except AttributeError: # no File object passed or None
+ # returning NotImplemented is bad because we have to
+ # compare None with File
+ return False
class Folder(File):
-
- # FIXME: a Folder is NOT a subclass of a File, both are a subclass of some AbstractFileSystemObject,
- # this is just a quick hack
- #
- # FIXME: but basically a Folder is a File so this class should not be needed
-
- __log = getLogger("Folder")
-
- @property
- def files(self):
- """
- Return File objects for all files in this Folder
- """
- try:
- filenames = glob("%s/*" % (self.path))
- files = [File(filename) for filename in filenames]
- return files
-
- except Exception, e:
- # as seen in Bug #2002630 the glob() call compiles a regex and so we must be prepared
- # for an exception from that because the shortname may contain regex characters
-
- # TODO: a more robust solution would be an escape() method for re
-
- self.__log.debug("files: %s" % e)
-
- return []
-
-
-
+
+ # FIXME: a Folder is NOT a subclass of a File, both are a subclass of some AbstractFileSystemObject,
+ # this is just a quick hack
+ #
+ # FIXME: but basically a Folder is a File so this class should not be needed
+
+ __log = getLogger("Folder")
+
+ @property
+ def files(self):
+ """
+ Return File objects for all files in this Folder
+ """
+ try:
+ filenames = glob("%s/*" % (self.path))
+ files = [File(filename) for filename in filenames]
+ return files
+
+ except Exception, e:
+ # as seen in Bug #2002630 the glob() call compiles a regex and so we must be prepared
+ # for an exception from that because the shortname may contain regex characters
+
+ # TODO: a more robust solution would be an escape() method for re
+
+ self.__log.debug("files: %s" % e)
+
+ return []
diff --git a/latex/base/completion.py b/latex/base/completion.py
index 7398820..474964c 100644
--- a/latex/base/completion.py
+++ b/latex/base/completion.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -29,550 +29,550 @@ from ..preferences import Preferences
class ProposalPopup(Gtk.Window):
- """
- Popup showing a list of proposals. This is implemented as a singleton
- as it doesn't make sense to have multiple popups around.
- """
-
- _log = getLogger("ProposalPopup")
-
- _POPUP_WIDTH = 300
- _POPUP_HEIGHT = 200
- _SPACE = 0
-
- def __new__(cls):
- if not '_instance' in cls.__dict__:
- cls._instance = Gtk.Window.__new__(cls)
- return cls._instance
-
- def __init__(self):
- if not '_ready' in dir(self):
- Gtk.Window.__init__(self,type=Gtk.WindowType.POPUP)
- #self, Gtk.WindowType.POPUP)
-
- self._store = Gtk.ListStore(str, object, GdkPixbuf.Pixbuf) # markup, Proposal instance
-
- self._view = Gtk.TreeView(model=self._store)
-
- # pack the icon and text cells in one column to avoid the column separator
- column = Gtk.TreeViewColumn()
- pixbuf_renderer = Gtk.CellRendererPixbuf()
- column.pack_start(pixbuf_renderer, False)
- column.add_attribute(pixbuf_renderer, "pixbuf", 2)
-
- text_renderer = Gtk.CellRendererText()
- column.pack_start(text_renderer, True)
- column.add_attribute(text_renderer, "markup", 0)
-
- self._view.append_column(column)
-
-# self._view.insert_column_with_attributes(-1, "", Gtk.CellRendererPixbuf(), pixbuf=2)
-# self._view.insert_column_with_attributes(-1, "", Gtk.CellRendererText(), markup=0)
-
- self._view.set_enable_search(False)
- self._view.set_headers_visible(False)
-
- scr = Gtk.ScrolledWindow()
- scr.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
- scr.add(self._view)
- scr.set_size_request(self._POPUP_WIDTH, self._POPUP_HEIGHT)
-
- frame = Gtk.Frame()
- frame.set_shadow_type(Gtk.ShadowType.OUT)
- frame.add(scr)
-
- self.add(frame)
-
- self._details_popup = DetailsPopup()
-
- self._ready = True
-
- @property
- def selected_proposal(self):
- """
- Returns the currently selected proposal
- """
- store, it = self._view.get_selection().get_selected()
- return store.get_value(it, 1)
-
- def activate(self, proposals, text_view):
- """
- Load proposals, move to the cursor position and show
- """
- self._set_proposals(proposals)
- self._move_to_cursor(text_view)
-
- self.show_all()
-
- self._update_details_popup()
-
- def deactivate(self):
- """
- Hide this popup and the DetailsPopup
- """
- self._details_popup.deactivate()
- self.hide()
-
- def _set_proposals(self, proposals):
- """
- Loads proposals into the popup
- """
- # sort
- proposals.sort()
-
- # load
- self._store.clear()
- for proposal in proposals:
- self._store.append([proposal.label, proposal, proposal.icon])
-
- self._view.set_cursor(Gtk.TreePath.new_from_string("0"),None, False)
-
- def navigate(self, key):
- """
- Moves the selection in the view according to key
- """
- if key == "Up":
- d = -1
- elif key == "Down":
- d = 1
- elif key == "Page_Up":
- d = -5
- elif key == "Page_Down":
- d = 5
- else:
- return
-
- path = self._view.get_cursor()[0]
- index = path[0]
- max = self._store.iter_n_children(None)
-
- index += d
-
- if index < 0:
- index = 0
- elif index >= max:
- index = max - 1
-
- self._view.set_cursor(index)
-
- self._update_details_popup()
-
- def _get_cursor_pos(self, text_view):
- """
- Retrieve the current absolute position of the cursor in a TextView
- """
- buffer = text_view.get_buffer()
- location = text_view.get_iter_location(buffer.get_iter_at_mark(buffer.get_insert()))
-
- winX, winY = text_view.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, location.x, location.y)
-
- win = text_view.get_window(Gtk.TextWindowType.WIDGET)
- ignore, xx, yy = win.get_origin()
-
- x = winX + xx
- y = winY + yy + location.height + self._SPACE
-
- return (x, y)
-
- def _update_details_popup(self):
- """
- Move and show the DetailsPopup if the currently selected proposal
- contains details.
- """
- try:
- index = self._view.get_cursor()[0][0]
- proposal = self._store[index][1]
-
-# self._log.debug("proposal.details: " + str(proposal.details))
-
- if proposal.details is None:
- self._details_popup.hide()
- return
-
- # move
- x, y = self.get_position()
- width = self.get_size()[0]
- path, column = self._view.get_cursor()
- rect = self._view.get_cell_area(path, column)
-
- self._details_popup.move(x + width + 2, y + rect.y)
-
- # activate
- self._details_popup.activate(proposal.details)
-
- except Exception, e:
- self._log.error(e)
-
- def _move_to_cursor(self, text_view):
- """
- Move the popup to the current location of the cursor
- """
- sw = Gdk.Screen.width()
- sh = Gdk.Screen.height()
-
- x, y = self._get_cursor_pos(text_view)
-
- w, h = self.get_size()
-
- if x + w > sw:
- x = sw - w - 4
-
- if y + h > sh:
- # get the height of a character
- layout = text_view.create_pango_layout("a")
- ytext = layout.get_pixel_size()[1]
- y = y - ytext - h
-
- self.move(x, y)
+ """
+ Popup showing a list of proposals. This is implemented as a singleton
+ as it doesn't make sense to have multiple popups around.
+ """
+
+ _log = getLogger("ProposalPopup")
+
+ _POPUP_WIDTH = 300
+ _POPUP_HEIGHT = 200
+ _SPACE = 0
+
+ def __new__(cls):
+ if not '_instance' in cls.__dict__:
+ cls._instance = Gtk.Window.__new__(cls)
+ return cls._instance
+
+ def __init__(self):
+ if not '_ready' in dir(self):
+ Gtk.Window.__init__(self,type=Gtk.WindowType.POPUP)
+ #self, Gtk.WindowType.POPUP)
+
+ self._store = Gtk.ListStore(str, object, GdkPixbuf.Pixbuf) # markup, Proposal instance
+
+ self._view = Gtk.TreeView(model=self._store)
+
+ # pack the icon and text cells in one column to avoid the column separator
+ column = Gtk.TreeViewColumn()
+ pixbuf_renderer = Gtk.CellRendererPixbuf()
+ column.pack_start(pixbuf_renderer, False)
+ column.add_attribute(pixbuf_renderer, "pixbuf", 2)
+
+ text_renderer = Gtk.CellRendererText()
+ column.pack_start(text_renderer, True)
+ column.add_attribute(text_renderer, "markup", 0)
+
+ self._view.append_column(column)
+
+# self._view.insert_column_with_attributes(-1, "", Gtk.CellRendererPixbuf(), pixbuf=2)
+# self._view.insert_column_with_attributes(-1, "", Gtk.CellRendererText(), markup=0)
+
+ self._view.set_enable_search(False)
+ self._view.set_headers_visible(False)
+
+ scr = Gtk.ScrolledWindow()
+ scr.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ scr.add(self._view)
+ scr.set_size_request(self._POPUP_WIDTH, self._POPUP_HEIGHT)
+
+ frame = Gtk.Frame()
+ frame.set_shadow_type(Gtk.ShadowType.OUT)
+ frame.add(scr)
+
+ self.add(frame)
+
+ self._details_popup = DetailsPopup()
+
+ self._ready = True
+
+ @property
+ def selected_proposal(self):
+ """
+ Returns the currently selected proposal
+ """
+ store, it = self._view.get_selection().get_selected()
+ return store.get_value(it, 1)
+
+ def activate(self, proposals, text_view):
+ """
+ Load proposals, move to the cursor position and show
+ """
+ self._set_proposals(proposals)
+ self._move_to_cursor(text_view)
+
+ self.show_all()
+
+ self._update_details_popup()
+
+ def deactivate(self):
+ """
+ Hide this popup and the DetailsPopup
+ """
+ self._details_popup.deactivate()
+ self.hide()
+
+ def _set_proposals(self, proposals):
+ """
+ Loads proposals into the popup
+ """
+ # sort
+ proposals.sort()
+
+ # load
+ self._store.clear()
+ for proposal in proposals:
+ self._store.append([proposal.label, proposal, proposal.icon])
+
+ self._view.set_cursor(Gtk.TreePath.new_from_string("0"),None, False)
+
+ def navigate(self, key):
+ """
+ Moves the selection in the view according to key
+ """
+ if key == "Up":
+ d = -1
+ elif key == "Down":
+ d = 1
+ elif key == "Page_Up":
+ d = -5
+ elif key == "Page_Down":
+ d = 5
+ else:
+ return
+
+ path = self._view.get_cursor()[0]
+ index = path[0]
+ max = self._store.iter_n_children(None)
+
+ index += d
+
+ if index < 0:
+ index = 0
+ elif index >= max:
+ index = max - 1
+
+ self._view.set_cursor(index)
+
+ self._update_details_popup()
+
+ def _get_cursor_pos(self, text_view):
+ """
+ Retrieve the current absolute position of the cursor in a TextView
+ """
+ buffer = text_view.get_buffer()
+ location = text_view.get_iter_location(buffer.get_iter_at_mark(buffer.get_insert()))
+
+ winX, winY = text_view.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, location.x, location.y)
+
+ win = text_view.get_window(Gtk.TextWindowType.WIDGET)
+ ignore, xx, yy = win.get_origin()
+
+ x = winX + xx
+ y = winY + yy + location.height + self._SPACE
+
+ return (x, y)
+
+ def _update_details_popup(self):
+ """
+ Move and show the DetailsPopup if the currently selected proposal
+ contains details.
+ """
+ try:
+ index = self._view.get_cursor()[0][0]
+ proposal = self._store[index][1]
+
+# self._log.debug("proposal.details: " + str(proposal.details))
+
+ if proposal.details is None:
+ self._details_popup.hide()
+ return
+
+ # move
+ x, y = self.get_position()
+ width = self.get_size()[0]
+ path, column = self._view.get_cursor()
+ rect = self._view.get_cell_area(path, column)
+
+ self._details_popup.move(x + width + 2, y + rect.y)
+
+ # activate
+ self._details_popup.activate(proposal.details)
+
+ except Exception, e:
+ self._log.error(e)
+
+ def _move_to_cursor(self, text_view):
+ """
+ Move the popup to the current location of the cursor
+ """
+ sw = Gdk.Screen.width()
+ sh = Gdk.Screen.height()
+
+ x, y = self._get_cursor_pos(text_view)
+
+ w, h = self.get_size()
+
+ if x + w > sw:
+ x = sw - w - 4
+
+ if y + h > sh:
+ # get the height of a character
+ layout = text_view.create_pango_layout("a")
+ ytext = layout.get_pixel_size()[1]
+ y = y - ytext - h
+
+ self.move(x, y)
class DetailsPopup(Gtk.Window):
- """
- A popup showing additional information at the right of the currently
- selected proposal in the ProposalPopup.
-
- This is used to display details of a BibTeX entry or the result of
- a template.
- """
-
- def __init__(self):
- Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP)
-
- self._color = Preferences().get("light-foreground-color")
-
- self._label = Gtk.Label()
- self._label.set_use_markup(True)
- self._label.set_alignment(0, .5)
-
- self._frame = Gtk.Frame()
- self._frame.set_shadow_type(Gtk.ShadowType.OUT)
- #self._frame.set_border_width(3)
- self._frame.add(self._label)
-
- self.add(self._frame)
-
- def activate(self, details):
- """
- Create widget(s) for the given details and show the popup
- """
- # remove the old child widget if present
- child = self._frame.get_child()
- if child:
- self._frame.remove(child)
- child.destroy()
-
- # create a child widget depending on the type of details
- if type(details) is list:
- # table data
- table = Gtk.Table()
- table.set_border_width(5)
- table.set_col_spacings(5)
- rc = 0
- for row in details:
- cc = 0
- for column in row:
- if cc == 0:
- # first column
- label = Gtk.Label("<span color='%s'>%s</span>" % (self._color, column))
- else:
- label = Gtk.Label(label=column)
- label.set_use_markup(True)
- if cc == 0:
- # 1st column is right aligned
- label.set_alignment(1.0, 0.5)
- else:
- # others are left aligned
- label.set_alignment(0.0, 0.5)
- table.attach(label, cc, cc + 1, rc, rc + 1)
- cc += 1
- rc += 1
- self._frame.add(table)
-
- else:
- # markup text
- label = Gtk.Label(label=details)
- label.set_use_markup(True)
- self._frame.add(label)
-
- self.show_all()
-
- # force a recompute of the window size
- self.resize(1, 1)
-
- def deactivate(self):
- """
- Hide the popup
- """
- self.hide()
+ """
+ A popup showing additional information at the right of the currently
+ selected proposal in the ProposalPopup.
+
+ This is used to display details of a BibTeX entry or the result of
+ a template.
+ """
+
+ def __init__(self):
+ Gtk.Window.__init__(self, type=Gtk.WindowType.POPUP)
+
+ self._color = Preferences().get("light-foreground-color")
+
+ self._label = Gtk.Label()
+ self._label.set_use_markup(True)
+ self._label.set_alignment(0, .5)
+
+ self._frame = Gtk.Frame()
+ self._frame.set_shadow_type(Gtk.ShadowType.OUT)
+ #self._frame.set_border_width(3)
+ self._frame.add(self._label)
+
+ self.add(self._frame)
+
+ def activate(self, details):
+ """
+ Create widget(s) for the given details and show the popup
+ """
+ # remove the old child widget if present
+ child = self._frame.get_child()
+ if child:
+ self._frame.remove(child)
+ child.destroy()
+
+ # create a child widget depending on the type of details
+ if type(details) is list:
+ # table data
+ table = Gtk.Table()
+ table.set_border_width(5)
+ table.set_col_spacings(5)
+ rc = 0
+ for row in details:
+ cc = 0
+ for column in row:
+ if cc == 0:
+ # first column
+ label = Gtk.Label("<span color='%s'>%s</span>" % (self._color, column))
+ else:
+ label = Gtk.Label(label=column)
+ label.set_use_markup(True)
+ if cc == 0:
+ # 1st column is right aligned
+ label.set_alignment(1.0, 0.5)
+ else:
+ # others are left aligned
+ label.set_alignment(0.0, 0.5)
+ table.attach(label, cc, cc + 1, rc, rc + 1)
+ cc += 1
+ rc += 1
+ self._frame.add(table)
+
+ else:
+ # markup text
+ label = Gtk.Label(label=details)
+ label.set_use_markup(True)
+ self._frame.add(label)
+
+ self.show_all()
+
+ # force a recompute of the window size
+ self.resize(1, 1)
+
+ def deactivate(self):
+ """
+ Hide the popup
+ """
+ self.hide()
class CompletionDistributor(object):
- """
- This forms the lower end of the completion mechanism and hosts one
- or more CompletionHandlers
- """
-
-
- # TODO: clearify and simplify states here!
- # TODO: auto-close (maybe...)
-
-
- _log = getLogger("CompletionDistributor")
-
- _MAX_PREFIX_LENGTH = 100
-
- # completion delay in ms
- _DELAY = 500
-
- _STATE_IDLE, _STATE_CTRL_PRESSED, _STATE_ACTIVE = 0, 1, 2
-
- # keys that abort completion
- _ABORT_KEYS = [ "Escape", "Left", "Right", "Home", "End", "space", "Tab" ]
-
- # keys that are used to navigate in the popup
- _NAVIGATION_KEYS = [ "Up", "Down", "Page_Up", "Page_Down" ]
-
- # some characters have key constants that differ from their value
- _SPECIAL_KEYS = {"@" : "at"}
-
- def __init__(self, editor, handlers):
- """
- @param editor: the instance of Editor this CompletionDistributor should observe
- @param handlers: a list of ICompletionHandler instances
- """
-
- self._log.debug("init")
-
- self._handlers = handlers # we already get objects
-
- self._editor = editor
- self._text_buffer = editor.tab_decorator.tab.get_document()
- self._text_view = editor.tab_decorator.tab.get_view()
-
- self._state = self._STATE_IDLE
- self._timer = None
-
- # collect trigger keys from all handlers
- self._trigger_keys = []
- for handler in self._handlers:
- for key in handler.trigger_keys:
- if key in self._SPECIAL_KEYS.keys():
- self._trigger_keys.append(self._SPECIAL_KEYS[key])
- else:
- self._trigger_keys.append(key)
-
- # TODO: is it right to instatiate this here?
- self._popup = ProposalPopup()
-
- # connect to signals
- self._signal_handlers = [
- self._text_view.connect("key-press-event", self._on_key_pressed),
- self._text_view.connect_after("key-release-event", self._on_key_released),
- self._text_view.connect("button-press-event", self._on_button_pressed),
- self._text_view.connect("focus-out-event", self._on_focus_out)
- ]
-
- def _on_key_pressed(self, view, event):
- """
- """
- key = Gdk.keyval_name(event.keyval)
-
- if self._state == self._STATE_IDLE:
- if key == "Control_L" or key == "Control_R":
- self._state = self._STATE_CTRL_PRESSED
-
- elif key in self._trigger_keys:
- self._start_timer()
- return False
-
- elif self._state == self._STATE_ACTIVE:
- if key == "Return":
- # select proposal
-
- self._abort()
-
- proposal = self._popup.selected_proposal
- self._select_proposal(proposal)
-
- # TODO: self._autoClose(proposalSelected=True)
-
- # returning True stops the signal
- return True
-
- elif key in self._ABORT_KEYS:
- self._abort()
-
- elif key in self._NAVIGATION_KEYS:
- self._popup.navigate(key)
-
- # returning True stops the signal
- return True
-
- elif self._state == self._STATE_CTRL_PRESSED:
- if key == "space":
- self._state = self._STATE_IDLE
- self._complete()
- return True
- else:
- self._state = self._STATE_IDLE
-
- self._stop_timer()
-
- def _on_key_released(self, view, event):
- """
- """
- key = Gdk.keyval_name(event.keyval)
-
-# # trigger auto close on "}"
-# if key == "braceright":
-# # TODO: self._autoClose(braceTyped=True)
-# pass
-
-
- if self._state == self._STATE_ACTIVE:
- if key in self._NAVIGATION_KEYS or key in ["Control_L", "Control_R", "space"]:
- # returning True stops the signal
- return True
- else:
- # user is typing on with active popup...
-
- # TODO: we should check here if the cursor has moved
- # or better if the buffer has changed
-
- self._complete()
-
- def _on_button_pressed(self, view, event):
- self._abort()
-
- def _on_focus_out(self, view, event):
- self._abort()
-
- def _start_timer(self):
- """
- Start a timer for completion
- """
- # stop eventually running timer
- self._stop_timer()
- # start timer
- self._timer = GObject.timeout_add(self._DELAY, self._timer_callback)
-
- def _stop_timer(self):
- """
- Stop the timer if it has been started
- """
- if self._timer is not None:
- GObject.source_remove(self._timer)
- self._timer = None
-
- def _timer_callback(self):
- """
- Timeout
- """
- self._complete()
-
- return False # do not call again
-
- def _complete(self):
- all_proposals = []
-
- for handler in self._handlers:
- delimiters = handler.prefix_delimiters
-
- prefix = self._find_prefix(delimiters)
- if prefix:
-# if handler.strip_delimiter:
-# prefix = prefix[1:]
-
- proposals = handler.complete(prefix)
- assert type(proposals) is list
-
- all_proposals.extend(proposals)
-
- if len(all_proposals):
- self._popup.activate(all_proposals, self._text_view)
- self._state = self._STATE_ACTIVE
- else:
- self._abort()
-
- def _find_prefix(self, delimiters):
- """
- Find the start of the surrounding command and return the text
- from there to the cursor position.
-
- This is the prefix forming the basis for LaTeX completion.
- """
- it_right = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
- it_left = it_right.copy()
-
- # go back by one character (insert iter points to the char at the right of
- # the cursor!)
- if not it_left.backward_char():
- self._log.debug("_find_prefix: start of buffer reached")
- return None
-
- # move left until 'the left-most of a sequence of delimiters'
- #
- # e.g. if 'x' is a delimiter and 'abcxxx' is at left of the cursor, we
- # recognize 'xxx' as the prefix instead of only 'x'
- delim_found = False
- delim_char = None
-
- i = 0
- while i < self._MAX_PREFIX_LENGTH:
- c = it_left.get_char()
-
- if delim_found:
- if c != delim_char:
- # a delimiter has been found and the preceding character
- # is different from it
- break
- else:
- if c in delimiters:
- # a delimiter has been found
- delim_found = True
- delim_char = c
-
- if not it_left.backward_char():
- self._log.debug("_find_prefix: start of buffer reached")
- return None
-
- i += 1
-
- # to recognize the left-most delimiter, we have moved one char
- # too much
- it_left.forward_char()
-
- if i == self._MAX_PREFIX_LENGTH:
- self._log.debug("_find_prefix: prefix too long")
- return None
-
- prefix = self._text_buffer.get_text(it_left, it_right, False)
-
- return prefix
-
- def _select_proposal(self, proposal):
- """
- Insert the source contained in the activated proposal
- """
- self._editor.delete_at_cursor(- proposal.overlap)
- self._editor.insert(proposal.source)
-
- def _abort(self):
- """
- Abort completion
- """
- if self._state == self._STATE_ACTIVE:
- self._popup.deactivate()
- self._state = self._STATE_IDLE
-
- def destroy(self):
- # unreference the editor (very important! cyclic reference)
- del self._editor
-
- # disconnect text view events
- for handler in self._signal_handlers:
- self._text_view.disconnect(handler)
-
- def __del__(self):
- self._log.debug("Properly destroyed %s" % self)
-
+ """
+ This forms the lower end of the completion mechanism and hosts one
+ or more CompletionHandlers
+ """
+
+
+ # TODO: clearify and simplify states here!
+ # TODO: auto-close (maybe...)
+
+
+ _log = getLogger("CompletionDistributor")
+
+ _MAX_PREFIX_LENGTH = 100
+
+ # completion delay in ms
+ _DELAY = 500
+
+ _STATE_IDLE, _STATE_CTRL_PRESSED, _STATE_ACTIVE = 0, 1, 2
+
+ # keys that abort completion
+ _ABORT_KEYS = [ "Escape", "Left", "Right", "Home", "End", "space", "Tab" ]
+
+ # keys that are used to navigate in the popup
+ _NAVIGATION_KEYS = [ "Up", "Down", "Page_Up", "Page_Down" ]
+
+ # some characters have key constants that differ from their value
+ _SPECIAL_KEYS = {"@" : "at"}
+
+ def __init__(self, editor, handlers):
+ """
+ @param editor: the instance of Editor this CompletionDistributor should observe
+ @param handlers: a list of ICompletionHandler instances
+ """
+
+ self._log.debug("init")
+
+ self._handlers = handlers # we already get objects
+
+ self._editor = editor
+ self._text_buffer = editor.tab_decorator.tab.get_document()
+ self._text_view = editor.tab_decorator.tab.get_view()
+
+ self._state = self._STATE_IDLE
+ self._timer = None
+
+ # collect trigger keys from all handlers
+ self._trigger_keys = []
+ for handler in self._handlers:
+ for key in handler.trigger_keys:
+ if key in self._SPECIAL_KEYS.keys():
+ self._trigger_keys.append(self._SPECIAL_KEYS[key])
+ else:
+ self._trigger_keys.append(key)
+
+ # TODO: is it right to instatiate this here?
+ self._popup = ProposalPopup()
+
+ # connect to signals
+ self._signal_handlers = [
+ self._text_view.connect("key-press-event", self._on_key_pressed),
+ self._text_view.connect_after("key-release-event", self._on_key_released),
+ self._text_view.connect("button-press-event", self._on_button_pressed),
+ self._text_view.connect("focus-out-event", self._on_focus_out)
+ ]
+
+ def _on_key_pressed(self, view, event):
+ """
+ """
+ key = Gdk.keyval_name(event.keyval)
+
+ if self._state == self._STATE_IDLE:
+ if key == "Control_L" or key == "Control_R":
+ self._state = self._STATE_CTRL_PRESSED
+
+ elif key in self._trigger_keys:
+ self._start_timer()
+ return False
+
+ elif self._state == self._STATE_ACTIVE:
+ if key == "Return":
+ # select proposal
+
+ self._abort()
+
+ proposal = self._popup.selected_proposal
+ self._select_proposal(proposal)
+
+ # TODO: self._autoClose(proposalSelected=True)
+
+ # returning True stops the signal
+ return True
+
+ elif key in self._ABORT_KEYS:
+ self._abort()
+
+ elif key in self._NAVIGATION_KEYS:
+ self._popup.navigate(key)
+
+ # returning True stops the signal
+ return True
+
+ elif self._state == self._STATE_CTRL_PRESSED:
+ if key == "space":
+ self._state = self._STATE_IDLE
+ self._complete()
+ return True
+ else:
+ self._state = self._STATE_IDLE
+
+ self._stop_timer()
+
+ def _on_key_released(self, view, event):
+ """
+ """
+ key = Gdk.keyval_name(event.keyval)
+
+# # trigger auto close on "}"
+# if key == "braceright":
+# # TODO: self._autoClose(braceTyped=True)
+# pass
+
+
+ if self._state == self._STATE_ACTIVE:
+ if key in self._NAVIGATION_KEYS or key in ["Control_L", "Control_R", "space"]:
+ # returning True stops the signal
+ return True
+ else:
+ # user is typing on with active popup...
+
+ # TODO: we should check here if the cursor has moved
+ # or better if the buffer has changed
+
+ self._complete()
+
+ def _on_button_pressed(self, view, event):
+ self._abort()
+
+ def _on_focus_out(self, view, event):
+ self._abort()
+
+ def _start_timer(self):
+ """
+ Start a timer for completion
+ """
+ # stop eventually running timer
+ self._stop_timer()
+ # start timer
+ self._timer = GObject.timeout_add(self._DELAY, self._timer_callback)
+
+ def _stop_timer(self):
+ """
+ Stop the timer if it has been started
+ """
+ if self._timer is not None:
+ GObject.source_remove(self._timer)
+ self._timer = None
+
+ def _timer_callback(self):
+ """
+ Timeout
+ """
+ self._complete()
+
+ return False # do not call again
+
+ def _complete(self):
+ all_proposals = []
+
+ for handler in self._handlers:
+ delimiters = handler.prefix_delimiters
+
+ prefix = self._find_prefix(delimiters)
+ if prefix:
+# if handler.strip_delimiter:
+# prefix = prefix[1:]
+
+ proposals = handler.complete(prefix)
+ assert type(proposals) is list
+
+ all_proposals.extend(proposals)
+
+ if len(all_proposals):
+ self._popup.activate(all_proposals, self._text_view)
+ self._state = self._STATE_ACTIVE
+ else:
+ self._abort()
+
+ def _find_prefix(self, delimiters):
+ """
+ Find the start of the surrounding command and return the text
+ from there to the cursor position.
+
+ This is the prefix forming the basis for LaTeX completion.
+ """
+ it_right = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
+ it_left = it_right.copy()
+
+ # go back by one character (insert iter points to the char at the right of
+ # the cursor!)
+ if not it_left.backward_char():
+ self._log.debug("_find_prefix: start of buffer reached")
+ return None
+
+ # move left until 'the left-most of a sequence of delimiters'
+ #
+ # e.g. if 'x' is a delimiter and 'abcxxx' is at left of the cursor, we
+ # recognize 'xxx' as the prefix instead of only 'x'
+ delim_found = False
+ delim_char = None
+
+ i = 0
+ while i < self._MAX_PREFIX_LENGTH:
+ c = it_left.get_char()
+
+ if delim_found:
+ if c != delim_char:
+ # a delimiter has been found and the preceding character
+ # is different from it
+ break
+ else:
+ if c in delimiters:
+ # a delimiter has been found
+ delim_found = True
+ delim_char = c
+
+ if not it_left.backward_char():
+ self._log.debug("_find_prefix: start of buffer reached")
+ return None
+
+ i += 1
+
+ # to recognize the left-most delimiter, we have moved one char
+ # too much
+ it_left.forward_char()
+
+ if i == self._MAX_PREFIX_LENGTH:
+ self._log.debug("_find_prefix: prefix too long")
+ return None
+
+ prefix = self._text_buffer.get_text(it_left, it_right, False)
+
+ return prefix
+
+ def _select_proposal(self, proposal):
+ """
+ Insert the source contained in the activated proposal
+ """
+ self._editor.delete_at_cursor(- proposal.overlap)
+ self._editor.insert(proposal.source)
+
+ def _abort(self):
+ """
+ Abort completion
+ """
+ if self._state == self._STATE_ACTIVE:
+ self._popup.deactivate()
+ self._state = self._STATE_IDLE
+
+ def destroy(self):
+ # unreference the editor (very important! cyclic reference)
+ del self._editor
+
+ # disconnect text view events
+ for handler in self._signal_handlers:
+ self._text_view.disconnect(handler)
+
+ def __del__(self):
+ self._log.debug("Properly destroyed %s" % self)
+
diff --git a/latex/base/config.py b/latex/base/config.py
index 5db59c9..692a148 100644
--- a/latex/base/config.py
+++ b/latex/base/config.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -25,114 +25,114 @@ base.config
# ui definition
UI = """
- <ui>
- <menubar name="MenuBar">
- <menu name="FileMenu" action="File">
- <placeholder name="FileOps_1">
- <menuitem action="LaTeXNewAction" />
- </placeholder>
- <placeholder name="FileOps_3">
- <menuitem action="LaTeXSaveAsTemplateAction" />
- </placeholder>
- </menu>
- <placeholder name="ExtraMenu_1">
- <menu action="LaTeXMenuAction">
- <menuitem action="LaTeXChooseMasterAction" />
- <separator />
- <menuitem action="LaTeXGraphicsAction" />
- <menuitem action="LaTeXTableAction" />
- <menuitem action="LaTeXListingAction" />
- <menuitem action="LaTeXUseBibliographyAction" />
- <separator />
- <menuitem action="LaTeXCloseEnvironmentAction" />
- <separator />
- <menuitem action="LaTeXBuildImageAction" />
- </menu>
- <menu action="BibTeXMenuAction">
- <menuitem action="BibTeXNewEntryAction" />
- </menu>
- </placeholder>
- </menubar>
- <toolbar name="LaTeXToolbar">
- <toolitem action="LaTeXFontFamilyAction">
- <menu action="LaTeXFontFamilyMenuAction">
- <menuitem action="LaTeXBoldAction" />
- <menuitem action="LaTeXItalicAction" />
- <menuitem action="LaTeXEmphasizeAction" />
- <menuitem action="LaTeXUnderlineAction" />
- <menuitem action="LaTeXSmallCapitalsAction" />
- <menuitem action="LaTeXRomanAction" />
- <menuitem action="LaTeXSansSerifAction" />
- <menuitem action="LaTeXTypewriterAction" />
- <separator />
- <menuitem action="LaTeXBlackboardBoldAction" />
- <menuitem action="LaTeXCaligraphyAction" />
- <menuitem action="LaTeXFrakturAction" />
- </menu>
- </toolitem>
- <toolitem action="LaTeXJustifyLeftAction" />
- <toolitem action="LaTeXJustifyCenterAction" />
- <toolitem action="LaTeXJustifyRightAction" />
- <separator />
- <toolitem action="LaTeXItemizeAction" />
- <toolitem action="LaTeXEnumerateAction" />
- <toolitem action="LaTeXDescriptionAction" />
- <separator />
- <toolitem action="LaTeXStructureAction">
- <menu action="LaTeXStructureMenuAction">
- <menuitem action="LaTeXPartAction" />
- <menuitem action="LaTeXChapterAction" />
- <separator />
- <menuitem action="LaTeXSectionAction" />
- <menuitem action="LaTeXSubsectionAction" />
- <menuitem action="LaTeXParagraphAction" />
- <menuitem action="LaTeXSubparagraphAction" />
- </menu>
- </toolitem>
- <separator />
- <toolitem action="LaTeXMathAction">
- <menu action="LaTeXMathMenuAction">
- <menuitem action="LaTeXMathAction" />
- <menuitem action="LaTeXDisplayMathAction" />
- <menuitem action="LaTeXEquationAction" />
- <menuitem action="LaTeXUnEqnArrayAction" />
- <menuitem action="LaTeXEqnArrayAction" />
- </menu>
- </toolitem>
- <separator />
- <toolitem action="LaTeXGraphicsAction" />
- <toolitem action="LaTeXTableAction" />
- <toolitem action="LaTeXListingAction" />
- <toolitem action="LaTeXUseBibliographyAction" />
- <separator />
- <toolitem action="LaTeXBuildImageAction" />
- </toolbar>
- </ui>"""
+ <ui>
+ <menubar name="MenuBar">
+ <menu name="FileMenu" action="File">
+ <placeholder name="FileOps_1">
+ <menuitem action="LaTeXNewAction" />
+ </placeholder>
+ <placeholder name="FileOps_3">
+ <menuitem action="LaTeXSaveAsTemplateAction" />
+ </placeholder>
+ </menu>
+ <placeholder name="ExtraMenu_1">
+ <menu action="LaTeXMenuAction">
+ <menuitem action="LaTeXChooseMasterAction" />
+ <separator />
+ <menuitem action="LaTeXGraphicsAction" />
+ <menuitem action="LaTeXTableAction" />
+ <menuitem action="LaTeXListingAction" />
+ <menuitem action="LaTeXUseBibliographyAction" />
+ <separator />
+ <menuitem action="LaTeXCloseEnvironmentAction" />
+ <separator />
+ <menuitem action="LaTeXBuildImageAction" />
+ </menu>
+ <menu action="BibTeXMenuAction">
+ <menuitem action="BibTeXNewEntryAction" />
+ </menu>
+ </placeholder>
+ </menubar>
+ <toolbar name="LaTeXToolbar">
+ <toolitem action="LaTeXFontFamilyAction">
+ <menu action="LaTeXFontFamilyMenuAction">
+ <menuitem action="LaTeXBoldAction" />
+ <menuitem action="LaTeXItalicAction" />
+ <menuitem action="LaTeXEmphasizeAction" />
+ <menuitem action="LaTeXUnderlineAction" />
+ <menuitem action="LaTeXSmallCapitalsAction" />
+ <menuitem action="LaTeXRomanAction" />
+ <menuitem action="LaTeXSansSerifAction" />
+ <menuitem action="LaTeXTypewriterAction" />
+ <separator />
+ <menuitem action="LaTeXBlackboardBoldAction" />
+ <menuitem action="LaTeXCaligraphyAction" />
+ <menuitem action="LaTeXFrakturAction" />
+ </menu>
+ </toolitem>
+ <toolitem action="LaTeXJustifyLeftAction" />
+ <toolitem action="LaTeXJustifyCenterAction" />
+ <toolitem action="LaTeXJustifyRightAction" />
+ <separator />
+ <toolitem action="LaTeXItemizeAction" />
+ <toolitem action="LaTeXEnumerateAction" />
+ <toolitem action="LaTeXDescriptionAction" />
+ <separator />
+ <toolitem action="LaTeXStructureAction">
+ <menu action="LaTeXStructureMenuAction">
+ <menuitem action="LaTeXPartAction" />
+ <menuitem action="LaTeXChapterAction" />
+ <separator />
+ <menuitem action="LaTeXSectionAction" />
+ <menuitem action="LaTeXSubsectionAction" />
+ <menuitem action="LaTeXParagraphAction" />
+ <menuitem action="LaTeXSubparagraphAction" />
+ </menu>
+ </toolitem>
+ <separator />
+ <toolitem action="LaTeXMathAction">
+ <menu action="LaTeXMathMenuAction">
+ <menuitem action="LaTeXMathAction" />
+ <menuitem action="LaTeXDisplayMathAction" />
+ <menuitem action="LaTeXEquationAction" />
+ <menuitem action="LaTeXUnEqnArrayAction" />
+ <menuitem action="LaTeXEqnArrayAction" />
+ </menu>
+ </toolitem>
+ <separator />
+ <toolitem action="LaTeXGraphicsAction" />
+ <toolitem action="LaTeXTableAction" />
+ <toolitem action="LaTeXListingAction" />
+ <toolitem action="LaTeXUseBibliographyAction" />
+ <separator />
+ <toolitem action="LaTeXBuildImageAction" />
+ </toolbar>
+ </ui>"""
# actions
from ..latex.actions import LaTeXMenuAction, LaTeXNewAction, LaTeXChooseMasterAction, \
- LaTeXItemizeAction, LaTeXEnumerateAction, LaTeXFontFamilyAction, LaTeXFontFamilyMenuAction, LaTeXBoldAction, \
- LaTeXItalicAction, LaTeXEmphasizeAction, LaTeXDescriptionAction, LaTeXStructureMenuAction, LaTeXPartAction, LaTeXChapterAction, \
- LaTeXSectionAction, LaTeXSubsectionAction, LaTeXParagraphAction,LaTeXSubparagraphAction, LaTeXStructureAction, \
- LaTeXGraphicsAction, LaTeXUseBibliographyAction, LaTeXTableAction, LaTeXListingAction, LaTeXJustifyLeftAction, \
- LaTeXJustifyCenterAction, LaTeXJustifyRightAction, LaTeXMathMenuAction, LaTeXMathAction, LaTeXDisplayMathAction, \
- LaTeXEquationAction, LaTeXUnEqnArrayAction, LaTeXEqnArrayAction, LaTeXUnderlineAction, LaTeXSmallCapitalsAction, \
- LaTeXRomanAction, LaTeXSansSerifAction, LaTeXTypewriterAction, LaTeXCloseEnvironmentAction, LaTeXBlackboardBoldAction, \
- LaTeXCaligraphyAction, LaTeXFrakturAction, LaTeXBuildImageAction, LaTeXSaveAsTemplateAction
+ LaTeXItemizeAction, LaTeXEnumerateAction, LaTeXFontFamilyAction, LaTeXFontFamilyMenuAction, LaTeXBoldAction, \
+ LaTeXItalicAction, LaTeXEmphasizeAction, LaTeXDescriptionAction, LaTeXStructureMenuAction, LaTeXPartAction, LaTeXChapterAction, \
+ LaTeXSectionAction, LaTeXSubsectionAction, LaTeXParagraphAction,LaTeXSubparagraphAction, LaTeXStructureAction, \
+ LaTeXGraphicsAction, LaTeXUseBibliographyAction, LaTeXTableAction, LaTeXListingAction, LaTeXJustifyLeftAction, \
+ LaTeXJustifyCenterAction, LaTeXJustifyRightAction, LaTeXMathMenuAction, LaTeXMathAction, LaTeXDisplayMathAction, \
+ LaTeXEquationAction, LaTeXUnEqnArrayAction, LaTeXEqnArrayAction, LaTeXUnderlineAction, LaTeXSmallCapitalsAction, \
+ LaTeXRomanAction, LaTeXSansSerifAction, LaTeXTypewriterAction, LaTeXCloseEnvironmentAction, LaTeXBlackboardBoldAction, \
+ LaTeXCaligraphyAction, LaTeXFrakturAction, LaTeXBuildImageAction, LaTeXSaveAsTemplateAction
from ..bibtex.actions import BibTeXMenuAction, BibTeXNewEntryAction
-ACTIONS = [ LaTeXMenuAction, LaTeXNewAction, LaTeXChooseMasterAction,
- LaTeXItemizeAction, LaTeXEnumerateAction, LaTeXFontFamilyAction, LaTeXFontFamilyMenuAction, LaTeXBoldAction,
- LaTeXItalicAction, LaTeXEmphasizeAction, LaTeXDescriptionAction, LaTeXStructureMenuAction, LaTeXPartAction, LaTeXChapterAction,
- LaTeXSectionAction, LaTeXSubsectionAction, LaTeXParagraphAction,LaTeXSubparagraphAction, LaTeXStructureAction,
- LaTeXGraphicsAction, LaTeXUseBibliographyAction, LaTeXTableAction, LaTeXListingAction, LaTeXJustifyLeftAction,
- LaTeXJustifyCenterAction, LaTeXJustifyRightAction, LaTeXMathMenuAction, LaTeXMathAction, LaTeXDisplayMathAction,
- LaTeXEquationAction, LaTeXUnEqnArrayAction, LaTeXEqnArrayAction, LaTeXUnderlineAction, LaTeXSmallCapitalsAction,
- LaTeXRomanAction, LaTeXSansSerifAction, LaTeXTypewriterAction, LaTeXCloseEnvironmentAction, LaTeXBlackboardBoldAction,
- LaTeXCaligraphyAction, LaTeXFrakturAction, LaTeXBuildImageAction, LaTeXSaveAsTemplateAction,
- BibTeXMenuAction, BibTeXNewEntryAction ]
+ACTIONS = [ LaTeXMenuAction, LaTeXNewAction, LaTeXChooseMasterAction,
+ LaTeXItemizeAction, LaTeXEnumerateAction, LaTeXFontFamilyAction, LaTeXFontFamilyMenuAction, LaTeXBoldAction,
+ LaTeXItalicAction, LaTeXEmphasizeAction, LaTeXDescriptionAction, LaTeXStructureMenuAction, LaTeXPartAction, LaTeXChapterAction,
+ LaTeXSectionAction, LaTeXSubsectionAction, LaTeXParagraphAction,LaTeXSubparagraphAction, LaTeXStructureAction,
+ LaTeXGraphicsAction, LaTeXUseBibliographyAction, LaTeXTableAction, LaTeXListingAction, LaTeXJustifyLeftAction,
+ LaTeXJustifyCenterAction, LaTeXJustifyRightAction, LaTeXMathMenuAction, LaTeXMathAction, LaTeXDisplayMathAction,
+ LaTeXEquationAction, LaTeXUnEqnArrayAction, LaTeXEqnArrayAction, LaTeXUnderlineAction, LaTeXSmallCapitalsAction,
+ LaTeXRomanAction, LaTeXSansSerifAction, LaTeXTypewriterAction, LaTeXCloseEnvironmentAction, LaTeXBlackboardBoldAction,
+ LaTeXCaligraphyAction, LaTeXFrakturAction, LaTeXBuildImageAction, LaTeXSaveAsTemplateAction,
+ BibTeXMenuAction, BibTeXNewEntryAction ]
# views
@@ -143,11 +143,11 @@ from ..bibtex.views import BibTeXOutlineView
#WINDOW_SCOPE_VIEWS = { ".tex" : {"LaTeXSymbolMapView" : LaTeXSymbolMapView } }
#
-#EDITOR_SCOPE_VIEWS = { ".tex" : {"IssueView" : IssueView,
-# "LaTeXOutlineView" : LaTeXOutlineView},
-#
-# ".bib" : {"IssueView" : IssueView,
-# "BibTeXOutlineView" : BibTeXOutlineView} }
+#EDITOR_SCOPE_VIEWS = { ".tex" : {"IssueView" : IssueView,
+# "LaTeXOutlineView" : LaTeXOutlineView},
+#
+# ".bib" : {"IssueView" : IssueView,
+# "BibTeXOutlineView" : BibTeXOutlineView} }
from ..preferences import Preferences
LATEX_EXTENSIONS = Preferences().get("latex-extensions").split(",")
@@ -157,11 +157,11 @@ WINDOW_SCOPE_VIEWS = {}
EDITOR_SCOPE_VIEWS = {}
for e in LATEX_EXTENSIONS:
- WINDOW_SCOPE_VIEWS[e] = {"LaTeXSymbolMapView" : LaTeXSymbolMapView }
- EDITOR_SCOPE_VIEWS[e] = {"IssueView" : IssueView, "LaTeXOutlineView" : LaTeXOutlineView}
+ WINDOW_SCOPE_VIEWS[e] = {"LaTeXSymbolMapView" : LaTeXSymbolMapView }
+ EDITOR_SCOPE_VIEWS[e] = {"IssueView" : IssueView, "LaTeXOutlineView" : LaTeXOutlineView}
for e in BIBTEX_EXTENSIONS:
- EDITOR_SCOPE_VIEWS[e] = {"IssueView" : IssueView, "BibTeXOutlineView" : BibTeXOutlineView}
+ EDITOR_SCOPE_VIEWS[e] = {"IssueView" : IssueView, "BibTeXOutlineView" : BibTeXOutlineView}
# editors
diff --git a/latex/base/decorators.py b/latex/base/decorators.py
index 204fc54..844b131 100644
--- a/latex/base/decorators.py
+++ b/latex/base/decorators.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -34,175 +34,175 @@ from . import File
# TODO: maybe create ActionDelegate for GeditWindowDecorator
class GeditTabDecorator(object):
- """
- This monitors the opened file and manages the Editor objects
- according to the current file extension
- """
-
- _log = getLogger("GeditTabDecorator")
-
- def __init__(self, window_decorator, tab, init=False):
- """
- Construct a GeditTabDecorator
-
- @param window_decorator: the parent GeditWindowDecorator
- @param tab: the GeditTab to create this for
- @param init: has to be True if this is created on plugin init to decorate
- already opened files
- """
- self._window_decorator = window_decorator
- self._tab = tab
- self._text_buffer = tab.get_document()
- self._text_view = tab.get_view()
-
- self._editor = None
- self._file = None
-
- # initially check the editor instance
- #
- # this needs to be done, because when we init for already opened files
- # (when plugin is activated in config) we get no "loaded" signal
- if init:
- self._adjust_editor()
-
- # listen to GeditDocument signals
- self._signals_handlers = [
- self._text_buffer.connect("loaded", self._on_load),
- self._text_buffer.connect("saved", self._on_save)
- ]
-
- self._log.debug("Created %s" % self)
-
- @property
- def tab(self):
- return self._tab
-
- def _on_load(self, document, param):
- """
- A file has been loaded
- """
- self._log.debug("loaded")
-
- self._adjust_editor()
-
- def _on_save(self, document, param):
- """
- The file has been saved
- """
- self._log.debug("saved")
-
- if not self._adjust_editor():
- # if the editor has not changed
- if self._editor:
- self._editor.on_save()
-
- def _adjust_editor(self):
- """
- Check if the URI has changed and manage Editor object according to
- file extension
-
- @return: True if the editor has changed
- """
- location = self._text_buffer.get_location()
- if location is None:
- # this happends when the plugin is activated in a running Gedit
- # and this decorator is created for the empty file
-
- self._log.debug("No file loaded")
-
- if self._window_decorator.window.get_active_view() == self._text_view:
- self._window_decorator.adjust(self)
-
- else:
- file = File(location.get_uri())
-
- if file == self._file: # FIXME: != doesn't work for File...
- return False
- else:
- self._log.debug("_adjust_editor: URI has changed")
-
- self._file = file
-
- # URI has changed - manage the editor instance
- if self._editor:
- # editor is present - destroy editor
- self._editor.destroy()
- self._editor = None
-
- # FIXME: comparing file extensions should be case-INsensitive...
- extension = file.extension
-
- # find Editor class for extension
- editor_class = None
- for clazz in EDITORS:
- if extension in clazz.extensions:
- editor_class = clazz
- break
-
- if not editor_class is None:
- # create instance
- self._editor = editor_class.__new__(editor_class)
- editor_class.__init__(self._editor, self, file)
-
- # The following doesn't work because the right expression is evaluated
- # and then assigned to the left. This means that Editor.__init__ is
- # running and reading _editor while _editor is None. That leads to
- #
- # Traceback (most recent call last):
- # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/base/decorators.py", line 662, in _on_load
- # self._adjust_editor()
- # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/base/decorators.py", line 716, in _adjust_editor
- # self._editor = editor_class(self, file)
- # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/base/__init__.py", line 353, in __init__
- # self.init(file, self._window_context)
- # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/latex/editor.py", line 106, in init
- # self.__parse()
- # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/latex/editor.py", line 279, in __parse
- # self._outline_view.set_outline(self._outline)
- # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/latex/views.py", line 228, in set_outline
- # OutlineConverter().convert(self._store, outline, self._offset_map, self._context.active_editor.edited_file)
-
- #self._editor = editor_class(self, file)
- else:
- self._log.warning("No editor class found for extension %s" % extension)
-
- # tell WindowDecorator to adjust actions
- # but only if this tab is the active tab
- if self._window_decorator.window.get_active_view() == self._text_view:
- self._window_decorator.adjust(self)
-
- # notify that URI has changed
- return True
-
- @property
- def file(self):
- return self._file
-
- @property
- def editor(self):
- return self._editor
-
- @property
- def extension(self):
- """
- @return: the extension of the currently opened file
- """
- if self._file is None:
- return None
- else:
- return self._file.extension
-
- def destroy(self):
- # disconnect from signals
- for handler in self._signals_handlers:
- self._text_buffer.disconnect(handler)
-
- # unreference the window decorator
- del self._window_decorator
-
- # destroy Editor instance
- if not self._editor is None:
- self._editor.destroy()
-
- def __del__(self):
- self._log.debug("Properly destroyed %s" % self)
+ """
+ This monitors the opened file and manages the Editor objects
+ according to the current file extension
+ """
+
+ _log = getLogger("GeditTabDecorator")
+
+ def __init__(self, window_decorator, tab, init=False):
+ """
+ Construct a GeditTabDecorator
+
+ @param window_decorator: the parent GeditWindowDecorator
+ @param tab: the GeditTab to create this for
+ @param init: has to be True if this is created on plugin init to decorate
+ already opened files
+ """
+ self._window_decorator = window_decorator
+ self._tab = tab
+ self._text_buffer = tab.get_document()
+ self._text_view = tab.get_view()
+
+ self._editor = None
+ self._file = None
+
+ # initially check the editor instance
+ #
+ # this needs to be done, because when we init for already opened files
+ # (when plugin is activated in config) we get no "loaded" signal
+ if init:
+ self._adjust_editor()
+
+ # listen to GeditDocument signals
+ self._signals_handlers = [
+ self._text_buffer.connect("loaded", self._on_load),
+ self._text_buffer.connect("saved", self._on_save)
+ ]
+
+ self._log.debug("Created %s" % self)
+
+ @property
+ def tab(self):
+ return self._tab
+
+ def _on_load(self, document, param):
+ """
+ A file has been loaded
+ """
+ self._log.debug("loaded")
+
+ self._adjust_editor()
+
+ def _on_save(self, document, param):
+ """
+ The file has been saved
+ """
+ self._log.debug("saved")
+
+ if not self._adjust_editor():
+ # if the editor has not changed
+ if self._editor:
+ self._editor.on_save()
+
+ def _adjust_editor(self):
+ """
+ Check if the URI has changed and manage Editor object according to
+ file extension
+
+ @return: True if the editor has changed
+ """
+ location = self._text_buffer.get_location()
+ if location is None:
+ # this happends when the plugin is activated in a running Gedit
+ # and this decorator is created for the empty file
+
+ self._log.debug("No file loaded")
+
+ if self._window_decorator.window.get_active_view() == self._text_view:
+ self._window_decorator.adjust(self)
+
+ else:
+ file = File(location.get_uri())
+
+ if file == self._file: # FIXME: != doesn't work for File...
+ return False
+ else:
+ self._log.debug("_adjust_editor: URI has changed")
+
+ self._file = file
+
+ # URI has changed - manage the editor instance
+ if self._editor:
+ # editor is present - destroy editor
+ self._editor.destroy()
+ self._editor = None
+
+ # FIXME: comparing file extensions should be case-INsensitive...
+ extension = file.extension
+
+ # find Editor class for extension
+ editor_class = None
+ for clazz in EDITORS:
+ if extension in clazz.extensions:
+ editor_class = clazz
+ break
+
+ if not editor_class is None:
+ # create instance
+ self._editor = editor_class.__new__(editor_class)
+ editor_class.__init__(self._editor, self, file)
+
+ # The following doesn't work because the right expression is evaluated
+ # and then assigned to the left. This means that Editor.__init__ is
+ # running and reading _editor while _editor is None. That leads to
+ #
+ # Traceback (most recent call last):
+ # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/base/decorators.py", line 662, in _on_load
+ # self._adjust_editor()
+ # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/base/decorators.py", line 716, in _adjust_editor
+ # self._editor = editor_class(self, file)
+ # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/base/__init__.py", line 353, in __init__
+ # self.init(file, self._window_context)
+ # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/latex/editor.py", line 106, in init
+ # self.__parse()
+ # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/latex/editor.py", line 279, in __parse
+ # self._outline_view.set_outline(self._outline)
+ # File "/home/michael/.gnome2/Gedit/plugins/GeditLaTeXPlugin/src/latex/views.py", line 228, in set_outline
+ # OutlineConverter().convert(self._store, outline, self._offset_map, self._context.active_editor.edited_file)
+
+ #self._editor = editor_class(self, file)
+ else:
+ self._log.warning("No editor class found for extension %s" % extension)
+
+ # tell WindowDecorator to adjust actions
+ # but only if this tab is the active tab
+ if self._window_decorator.window.get_active_view() == self._text_view:
+ self._window_decorator.adjust(self)
+
+ # notify that URI has changed
+ return True
+
+ @property
+ def file(self):
+ return self._file
+
+ @property
+ def editor(self):
+ return self._editor
+
+ @property
+ def extension(self):
+ """
+ @return: the extension of the currently opened file
+ """
+ if self._file is None:
+ return None
+ else:
+ return self._file.extension
+
+ def destroy(self):
+ # disconnect from signals
+ for handler in self._signals_handlers:
+ self._text_buffer.disconnect(handler)
+
+ # unreference the window decorator
+ del self._window_decorator
+
+ # destroy Editor instance
+ if not self._editor is None:
+ self._editor.destroy()
+
+ def __del__(self):
+ self._log.debug("Properly destroyed %s" % self)
diff --git a/latex/base/job.py b/latex/base/job.py
index 4ac1fc7..59f9242 100644
--- a/latex/base/job.py
+++ b/latex/base/job.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -35,169 +35,169 @@ OBJECT_PATH = '/org/gedit/LaTeXPlugin/JobManager'
class Job(object):
- __log = logging.getLogger("Job")
-
- class NoneReturned(object):
- pass
-
- def __init__(self, argument=None):
- """
- @param arguments: a list of objects to be passed to the Job
- """
- self.__argument = argument
- self.__returned = self.NoneReturned()
- self.__change_listener = None
-
- def set_argument(self, argument):
- self.__argument = argument
-
- def schedule(self):
- """
- Run the Job as a subprocess
- """
- # create queue for communication
- self.__queue = multiprocessing.Queue()
-
- # run process
- self.__process = multiprocessing.Process(target=self.__start, args=(self.__queue,))
-
- # enqueue argument object
- self.__queue.put(self.__argument)
-
- # start process
- self.__process.start()
-
- def abort(self):
- """
- Abort the Job process
- """
- self.__process.terminate()
-
- # TODO: cleanup?
-
- def get_returned(self):
- """
- Get the objects returned by the Job
- """
- if type(self.__returned) is self.NoneReturned:
- # dequeue returned object
- self.__returned = self.__queue.get()
- return self.__returned
-
- def get_exception(self):
- return self.__exception
-
- @property
- def id(self):
- return id(self)
-
- def set_change_listener(self, job_change_listener):
- self.__change_listener = job_change_listener
-
- def __start(self, queue):
- """
- This is started as a subprocess in a separate address space
- """
- # register state change listener
- if not self.__change_listener is None:
- job_manager.add_listener(self.id, self.__change_listener)
-
- # notify state change
- job_manager.change_state(self.id, JobManager.STATE_STARTED)
-
- # dequeue argument object
- argument = queue.get()
-
- # run the job
- self.__exception = None
- try:
- returned = self._run(argument)
- except Exception, e:
- self.__log.error(e)
- self.__exception = e
-
- # enqueue returned object
- queue.put(returned)
-
- # notify state change
- job_manager.change_state(self.id, JobManager.STATE_COMPLETED)
-
- # deregister state change listener
- if not self.__change_listener is None:
- job_manager.remove_listener(self.id)
-
- def _run(self, arguments):
- """
- @return: a list of objects that should be made available after completion
- """
- pass
+ __log = logging.getLogger("Job")
+
+ class NoneReturned(object):
+ pass
+
+ def __init__(self, argument=None):
+ """
+ @param arguments: a list of objects to be passed to the Job
+ """
+ self.__argument = argument
+ self.__returned = self.NoneReturned()
+ self.__change_listener = None
+
+ def set_argument(self, argument):
+ self.__argument = argument
+
+ def schedule(self):
+ """
+ Run the Job as a subprocess
+ """
+ # create queue for communication
+ self.__queue = multiprocessing.Queue()
+
+ # run process
+ self.__process = multiprocessing.Process(target=self.__start, args=(self.__queue,))
+
+ # enqueue argument object
+ self.__queue.put(self.__argument)
+
+ # start process
+ self.__process.start()
+
+ def abort(self):
+ """
+ Abort the Job process
+ """
+ self.__process.terminate()
+
+ # TODO: cleanup?
+
+ def get_returned(self):
+ """
+ Get the objects returned by the Job
+ """
+ if type(self.__returned) is self.NoneReturned:
+ # dequeue returned object
+ self.__returned = self.__queue.get()
+ return self.__returned
+
+ def get_exception(self):
+ return self.__exception
+
+ @property
+ def id(self):
+ return id(self)
+
+ def set_change_listener(self, job_change_listener):
+ self.__change_listener = job_change_listener
+
+ def __start(self, queue):
+ """
+ This is started as a subprocess in a separate address space
+ """
+ # register state change listener
+ if not self.__change_listener is None:
+ job_manager.add_listener(self.id, self.__change_listener)
+
+ # notify state change
+ job_manager.change_state(self.id, JobManager.STATE_STARTED)
+
+ # dequeue argument object
+ argument = queue.get()
+
+ # run the job
+ self.__exception = None
+ try:
+ returned = self._run(argument)
+ except Exception, e:
+ self.__log.error(e)
+ self.__exception = e
+
+ # enqueue returned object
+ queue.put(returned)
+
+ # notify state change
+ job_manager.change_state(self.id, JobManager.STATE_COMPLETED)
+
+ # deregister state change listener
+ if not self.__change_listener is None:
+ job_manager.remove_listener(self.id)
+
+ def _run(self, arguments):
+ """
+ @return: a list of objects that should be made available after completion
+ """
+ pass
class JobChangeListener(object):
- """
- Callback oject for listening to the state changes of a Job
- """
- def _on_state_changed(self, state):
- pass
-
-
+ """
+ Callback oject for listening to the state changes of a Job
+ """
+ def _on_state_changed(self, state):
+ pass
+
+
class GlobalJobChangeListener(object):
- """
- Callback oject for listening to the state changes of ALL Jobs
- """
- def _on_state_changed(self, job_id, state):
- pass
+ """
+ Callback oject for listening to the state changes of ALL Jobs
+ """
+ def _on_state_changed(self, job_id, state):
+ pass
class JobManager(dbus.service.Object):
-
- STATE_STARTED, STATE_COMPLETED = 1, 2
-
- __log = logging.getLogger("JobManager")
-
- def __init__(self):
- bus_name = dbus.service.BusName(BUS_NAME, bus=dbus.SessionBus())
- dbus.service.Object.__init__(self, bus_name, OBJECT_PATH)
-
- self.__global_listener = None
- self.__listeners = {}
-
- self.__log.debug("Created JobManager instance %s" % id(self))
-
- @dbus.service.method(dbus_interface="org.gedit.JobManagerInterface")
- def change_state(self, job_id, state):
- """
- The job with id <job_id> has changed its state to <state>
- """
- self.__log.debug("change_state(%s, %s)" % (job_id, state))
-
- # notify global listener
- if self.__global_listener is None:
- self.__log.warn("No global listener")
- else:
- self.__global_listener._on_state_changed(job_id, state)
-
- # notify listener if present
- try:
- self.__listeners[job_id]._on_state_changed(state)
- except KeyError:
- self.__log.warn("No listener for job %s" % job_id)
-
- def set_global_listener(self, global_job_change_listener):
- self.__global_listener = global_job_change_listener
-
- def add_listener(self, job_id, job_change_listener):
- self.__listeners[job_id] = job_change_listener
-
- def remove_listener(self, job_id):
- del self.__listeners[job_id]
-
- def dispose(self):
- """
- End life-cycle
- """
- self.__log.debug("dispose")
-
+
+ STATE_STARTED, STATE_COMPLETED = 1, 2
+
+ __log = logging.getLogger("JobManager")
+
+ def __init__(self):
+ bus_name = dbus.service.BusName(BUS_NAME, bus=dbus.SessionBus())
+ dbus.service.Object.__init__(self, bus_name, OBJECT_PATH)
+
+ self.__global_listener = None
+ self.__listeners = {}
+
+ self.__log.debug("Created JobManager instance %s" % id(self))
+
+ @dbus.service.method(dbus_interface="org.gedit.JobManagerInterface")
+ def change_state(self, job_id, state):
+ """
+ The job with id <job_id> has changed its state to <state>
+ """
+ self.__log.debug("change_state(%s, %s)" % (job_id, state))
+
+ # notify global listener
+ if self.__global_listener is None:
+ self.__log.warn("No global listener")
+ else:
+ self.__global_listener._on_state_changed(job_id, state)
+
+ # notify listener if present
+ try:
+ self.__listeners[job_id]._on_state_changed(state)
+ except KeyError:
+ self.__log.warn("No listener for job %s" % job_id)
+
+ def set_global_listener(self, global_job_change_listener):
+ self.__global_listener = global_job_change_listener
+
+ def add_listener(self, job_id, job_change_listener):
+ self.__listeners[job_id] = job_change_listener
+
+ def remove_listener(self, job_id):
+ del self.__listeners[job_id]
+
+ def dispose(self):
+ """
+ End life-cycle
+ """
+ self.__log.debug("dispose")
+
job_manager = JobManager()
diff --git a/latex/base/resources.py b/latex/base/resources.py
index 4369ba8..06f9199 100644
--- a/latex/base/resources.py
+++ b/latex/base/resources.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -43,67 +43,67 @@ _PATH_SRCDIR = os.path.abspath(os.path.join(_PATH_ME,"..","..","data"))
# beyond that case, by preferring the local copy to the system one, it
# allows the user to customize things cleanly
_PATH_RO_RESOURCES = [p for p in (
- _PATH_SRCDIR, _PATH_USER, _PATH_SYSTEM) if os.path.exists(p)]
+ _PATH_SRCDIR, _PATH_USER, _PATH_SYSTEM) if os.path.exists(p)]
_log.debug("RO locations: %s" % ",".join(_PATH_RO_RESOURCES ))
_log.debug("RW location: %s" % _PATH_SYSTEM)
_installed_system_wide = os.path.exists(_PATH_SYSTEM)
if _installed_system_wide:
- # ensure that we have a user plugin dir
- if not os.path.exists(_PATH_USER):
- _log.debug("Creating %s" % _PATH_USER)
- os.makedirs(_PATH_USER)
- PLUGIN_PATH = _PATH_SYSTEM # FIXME: only used by build to expand $plugin
+ # ensure that we have a user plugin dir
+ if not os.path.exists(_PATH_USER):
+ _log.debug("Creating %s" % _PATH_USER)
+ os.makedirs(_PATH_USER)
+ PLUGIN_PATH = _PATH_SYSTEM # FIXME: only used by build to expand $plugin
else:
- PLUGIN_PATH = _PATH_USER
+ PLUGIN_PATH = _PATH_USER
MODE_READONLY, MODE_READWRITE = 1, 2
def find_resource(relative_path, access_mode=MODE_READONLY):
- """
- This locates a resource used by the plugin. The access mode determines where to
- search for the relative path.
-
- @param relative_path: a relative path like 'icons/smiley.png'
- @param access_mode: MODE_READONLY|MODE_READWRITE
-
- @return: the full filename of the resource
- """
- _log.debug("Finding: %s (%d)" % (relative_path, access_mode))
- if access_mode == MODE_READONLY:
- # locate a resource for read-only access. Prefer user files
- # to system ones. See comment above
- for p in _PATH_RO_RESOURCES:
- path = "%s/%s" % (p, relative_path)
- if os.path.exists(path):
- return path
-
- _log.critical("File not found: %s" % path)
- return None
-
- elif access_mode == MODE_READWRITE:
- # locate a user-specific resource for read/write access
- path = "%s/%s" % (_PATH_USER, relative_path)
- if os.path.exists(path):
- return path
-
- if _installed_system_wide:
- # resource doesn't exist yet in the user's directory
- # copy the system-wide version
- rw_source = "%s/%s" % (_PATH_SYSTEM, relative_path)
- else:
- # we are in the sourcedir
- rw_source = "%s/%s" % (_PATH_SRCDIR, relative_path)
-
- try:
- _log.info("Copying file to user path %s -> %s" % (rw_source, path))
- assert(rw_source != path)
- shutil.copyfile(rw_source, path)
- except IOError:
- _log.critical("Failed to copy resource to user directory: %s -> %s" % (rw_source, path))
- except AssertionError:
- _log.critical("Source and dest are the same. Bad programmer")
-
- return path
+ """
+ This locates a resource used by the plugin. The access mode determines where to
+ search for the relative path.
+
+ @param relative_path: a relative path like 'icons/smiley.png'
+ @param access_mode: MODE_READONLY|MODE_READWRITE
+
+ @return: the full filename of the resource
+ """
+ _log.debug("Finding: %s (%d)" % (relative_path, access_mode))
+ if access_mode == MODE_READONLY:
+ # locate a resource for read-only access. Prefer user files
+ # to system ones. See comment above
+ for p in _PATH_RO_RESOURCES:
+ path = "%s/%s" % (p, relative_path)
+ if os.path.exists(path):
+ return path
+
+ _log.critical("File not found: %s" % path)
+ return None
+
+ elif access_mode == MODE_READWRITE:
+ # locate a user-specific resource for read/write access
+ path = "%s/%s" % (_PATH_USER, relative_path)
+ if os.path.exists(path):
+ return path
+
+ if _installed_system_wide:
+ # resource doesn't exist yet in the user's directory
+ # copy the system-wide version
+ rw_source = "%s/%s" % (_PATH_SYSTEM, relative_path)
+ else:
+ # we are in the sourcedir
+ rw_source = "%s/%s" % (_PATH_SRCDIR, relative_path)
+
+ try:
+ _log.info("Copying file to user path %s -> %s" % (rw_source, path))
+ assert(rw_source != path)
+ shutil.copyfile(rw_source, path)
+ except IOError:
+ _log.critical("Failed to copy resource to user directory: %s -> %s" % (rw_source, path))
+ except AssertionError:
+ _log.critical("Source and dest are the same. Bad programmer")
+
+ return path
diff --git a/latex/base/templates.py b/latex/base/templates.py
index 66f58e2..02eeade 100644
--- a/latex/base/templates.py
+++ b/latex/base/templates.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -26,588 +26,588 @@ from logging import getLogger
class TemplateToken:
- """
- A token of a template expression
- """
- LITERAL, PLACEHOLDER, CURSOR = 1,2,3
-
- def __init__(self, type, value=None):
- self.type = type
- self.value = value
+ """
+ A token of a template expression
+ """
+ LITERAL, PLACEHOLDER, CURSOR = 1,2,3
+
+ def __init__(self, type, value=None):
+ self.type = type
+ self.value = value
class TemplateTokenizer:
- """
- A simple state machine for tokenizing a template expression
- """
- _INIT, _LITERAL, _LITERAL_DOLLAR, _DOLLAR, _PLACEHOLDER = 1,2,3,4,5
-
- def __init__(self, expression):
- self._character_iter = iter(expression)
- self._buffer = []
- self._end_of_expression = False
-
- def __iter__(self):
- return self
-
- @property
- def _next(self):
- """
- Return the next character from the push back stack or the
- source and return (not raise) StopIteration on end of string
- """
- try:
- return self._buffer.pop()
- except IndexError:
- try:
- return self._character_iter.next()
- except StopIteration:
- return StopIteration
-
- def _push(self, character):
- """
- Push one character of the push back stack
- """
- self._buffer.append(character)
-
- def next(self):
- """
- Return the next token
- """
- state = self._INIT
- while True:
- if self._end_of_expression:
- # end of expression reached in last iteration
- raise StopIteration
-
- char = self._next
-
- if state == self._INIT:
- if char == "$":
- state = self._DOLLAR
- elif char == StopIteration:
- raise StopIteration
- else:
- literal_value = char
- state = self._LITERAL
- elif state == self._LITERAL:
- if char == "$":
- state = self._LITERAL_DOLLAR
- elif char == StopIteration:
- self._end_of_expression = True
- return TemplateToken(TemplateToken.LITERAL, literal_value)
- else:
- literal_value += char
- elif state == self._LITERAL_DOLLAR:
- if char == "{":
- self._push("{")
- self._push("$")
- return TemplateToken(TemplateToken.LITERAL, literal_value)
- elif char == "_":
- self._push("_")
- self._push("$")
- return TemplateToken(TemplateToken.LITERAL, literal_value)
- elif char == StopIteration:
- self._end_of_expression = True
- literal_value += "$"
- return TemplateToken(TemplateToken.LITERAL, literal_value)
- else:
- literal_value += "$"
- state = self._LITERAL
- elif state == self._DOLLAR:
- if char == "_":
- return TemplateToken(TemplateToken.CURSOR)
- elif char == "{":
- placeholder_value = ""
- state = self._PLACEHOLDER
- elif char == StopIteration:
- self._end_of_expression = True
- return TemplateToken(TemplateToken.LITERAL, "$")
- else:
- literal_value = "$"
- state = self._LITERAL
- elif state == self._PLACEHOLDER:
- if char == "}":
- return TemplateToken(TemplateToken.PLACEHOLDER, placeholder_value)
- elif char == StopIteration:
- self._end_of_expression = True
- return TemplateToken(TemplateToken.LITERAL, "${" + placeholder_value)
- else:
- placeholder_value += char
+ """
+ A simple state machine for tokenizing a template expression
+ """
+ _INIT, _LITERAL, _LITERAL_DOLLAR, _DOLLAR, _PLACEHOLDER = 1,2,3,4,5
+
+ def __init__(self, expression):
+ self._character_iter = iter(expression)
+ self._buffer = []
+ self._end_of_expression = False
+
+ def __iter__(self):
+ return self
+
+ @property
+ def _next(self):
+ """
+ Return the next character from the push back stack or the
+ source and return (not raise) StopIteration on end of string
+ """
+ try:
+ return self._buffer.pop()
+ except IndexError:
+ try:
+ return self._character_iter.next()
+ except StopIteration:
+ return StopIteration
+
+ def _push(self, character):
+ """
+ Push one character of the push back stack
+ """
+ self._buffer.append(character)
+
+ def next(self):
+ """
+ Return the next token
+ """
+ state = self._INIT
+ while True:
+ if self._end_of_expression:
+ # end of expression reached in last iteration
+ raise StopIteration
+
+ char = self._next
+
+ if state == self._INIT:
+ if char == "$":
+ state = self._DOLLAR
+ elif char == StopIteration:
+ raise StopIteration
+ else:
+ literal_value = char
+ state = self._LITERAL
+ elif state == self._LITERAL:
+ if char == "$":
+ state = self._LITERAL_DOLLAR
+ elif char == StopIteration:
+ self._end_of_expression = True
+ return TemplateToken(TemplateToken.LITERAL, literal_value)
+ else:
+ literal_value += char
+ elif state == self._LITERAL_DOLLAR:
+ if char == "{":
+ self._push("{")
+ self._push("$")
+ return TemplateToken(TemplateToken.LITERAL, literal_value)
+ elif char == "_":
+ self._push("_")
+ self._push("$")
+ return TemplateToken(TemplateToken.LITERAL, literal_value)
+ elif char == StopIteration:
+ self._end_of_expression = True
+ literal_value += "$"
+ return TemplateToken(TemplateToken.LITERAL, literal_value)
+ else:
+ literal_value += "$"
+ state = self._LITERAL
+ elif state == self._DOLLAR:
+ if char == "_":
+ return TemplateToken(TemplateToken.CURSOR)
+ elif char == "{":
+ placeholder_value = ""
+ state = self._PLACEHOLDER
+ elif char == StopIteration:
+ self._end_of_expression = True
+ return TemplateToken(TemplateToken.LITERAL, "$")
+ else:
+ literal_value = "$"
+ state = self._LITERAL
+ elif state == self._PLACEHOLDER:
+ if char == "}":
+ return TemplateToken(TemplateToken.PLACEHOLDER, placeholder_value)
+ elif char == StopIteration:
+ self._end_of_expression = True
+ return TemplateToken(TemplateToken.LITERAL, "${" + placeholder_value)
+ else:
+ placeholder_value += char
class Placeholder(object):
- """
- @deprecated: use TemplateToken
- """
- def __init__(self, label, offset):
- """
- @param label: label of this placeholder
- @param offset: offset in plain text
- """
- self._label = label
- self._offset = offset
-
- @property
- def label(self):
- return self._label
-
- @property
- def offset(self):
- return self._offset
+ """
+ @deprecated: use TemplateToken
+ """
+ def __init__(self, label, offset):
+ """
+ @param label: label of this placeholder
+ @param offset: offset in plain text
+ """
+ self._label = label
+ self._offset = offset
+
+ @property
+ def label(self):
+ return self._label
+
+ @property
+ def offset(self):
+ return self._offset
class MalformedTemplateException(Exception):
- """
- Raised if a template expression could not be tokenized
- """
+ """
+ Raised if a template expression could not be tokenized
+ """
from ..util import verbose
class TemplateCompiler(object):
- """
- @deprecated: use TemplateTokenizer
- """
-
- _S_DEFAULT, _S_IDENT, _S_PLACEHOLDER = 0, 1, 2
-
- def _reset(self):
- self._placeholders = []
- self._plain = ""
- self._final_cursor_offset = None
-
- @property
- def placeholders(self):
- return self._placeholders
-
- @property
- def plain(self):
- return self._plain
-
- @property
- def final_cursor_offset(self):
- return self._final_cursor_offset
-
- def compile(self, expression):
- """
- @param expression: template expression
- @raise MalformedTemplateException: if the expression could not be tokenized
- @return: a list of tokens
- """
-
- # TODO: redo this from DFA
-
- self._reset()
-
- state = self._S_DEFAULT
- offset = 0
-
- try:
- for c in expression:
- if state == self._S_DEFAULT:
- # we're in plain text
- if c == "$":
- # the magic char appeared, so change state
- state = self._S_IDENT
- else:
- # nothing special, so append to plain text
- self._plain += c
- offset += 1
-
- elif state == self._S_IDENT:
- # the magic char has appeared
- if c == "{":
- # a placeholder is starting, so create a builder for its name
- name = []
- # save its position
- position = offset
- # and change state
- state = self._S_PLACEHOLDER
- elif c == "_":
- # "$_" marks the final cursor position
- self._final_cursor_offset = offset
- # and change state back to default
- state = self._S_DEFAULT
- else:
- # false alarm, the magic sign was just a dollar sign, so append
- # the "$" and the current char to plain text
- self._plain += "$" + c
- offset += 2
- # and change to default state
- state = self._S_DEFAULT
-
- elif state == self._S_PLACEHOLDER:
- # we're in a placeholder definition
- if c == "}":
- # it is ending, so append object
- self._placeholders.append(Placeholder("".join(name), position))
- self._plain += "".join(name)
- # change state
- state = self._S_DEFAULT
- else:
- # it is not ending
- name.append(c)
- offset += 1
- except Exception, e:
- raise MalformedTemplateException(e)
-
- if state == self._S_IDENT:
- # we ended in INDENT state so '$' was the last character - it can't be
- # the magic sign
- self._plain += "$"
- elif state == self._S_PLACEHOLDER:
- # if everything went fine we should end up in DEFAULT state
- # or in INDENT - but never in PLACEHOLDER
- raise MalformedTemplateException("Illegal state: %s" % state)
-
+ """
+ @deprecated: use TemplateTokenizer
+ """
+
+ _S_DEFAULT, _S_IDENT, _S_PLACEHOLDER = 0, 1, 2
+
+ def _reset(self):
+ self._placeholders = []
+ self._plain = ""
+ self._final_cursor_offset = None
+
+ @property
+ def placeholders(self):
+ return self._placeholders
+
+ @property
+ def plain(self):
+ return self._plain
+
+ @property
+ def final_cursor_offset(self):
+ return self._final_cursor_offset
+
+ def compile(self, expression):
+ """
+ @param expression: template expression
+ @raise MalformedTemplateException: if the expression could not be tokenized
+ @return: a list of tokens
+ """
+
+ # TODO: redo this from DFA
+
+ self._reset()
+
+ state = self._S_DEFAULT
+ offset = 0
+
+ try:
+ for c in expression:
+ if state == self._S_DEFAULT:
+ # we're in plain text
+ if c == "$":
+ # the magic char appeared, so change state
+ state = self._S_IDENT
+ else:
+ # nothing special, so append to plain text
+ self._plain += c
+ offset += 1
+
+ elif state == self._S_IDENT:
+ # the magic char has appeared
+ if c == "{":
+ # a placeholder is starting, so create a builder for its name
+ name = []
+ # save its position
+ position = offset
+ # and change state
+ state = self._S_PLACEHOLDER
+ elif c == "_":
+ # "$_" marks the final cursor position
+ self._final_cursor_offset = offset
+ # and change state back to default
+ state = self._S_DEFAULT
+ else:
+ # false alarm, the magic sign was just a dollar sign, so append
+ # the "$" and the current char to plain text
+ self._plain += "$" + c
+ offset += 2
+ # and change to default state
+ state = self._S_DEFAULT
+
+ elif state == self._S_PLACEHOLDER:
+ # we're in a placeholder definition
+ if c == "}":
+ # it is ending, so append object
+ self._placeholders.append(Placeholder("".join(name), position))
+ self._plain += "".join(name)
+ # change state
+ state = self._S_DEFAULT
+ else:
+ # it is not ending
+ name.append(c)
+ offset += 1
+ except Exception, e:
+ raise MalformedTemplateException(e)
+
+ if state == self._S_IDENT:
+ # we ended in INDENT state so '$' was the last character - it can't be
+ # the magic sign
+ self._plain += "$"
+ elif state == self._S_PLACEHOLDER:
+ # if everything went fine we should end up in DEFAULT state
+ # or in INDENT - but never in PLACEHOLDER
+ raise MalformedTemplateException("Illegal state: %s" % state)
+
from gi.repository import Gdk
from . import Template
from ..preferences import Preferences
-
+
class TemplateDelegate(object):
- """
- This handles templates for an Editor instance
- """
-
- _log = getLogger("TemplateDelegate")
-
- _KEY_TAB = "Tab"
- _KEY_LEFT_SHIFT_TAB = "ISO_Left_Tab"
- _KEY_ESCAPE = "Escape"
- _KEY_RETURN = "Return"
-
- def __init__(self, editor):
- self._editor = editor
- self._text_buffer = editor.tab_decorator.tab.get_document()
- self._text_view = editor.tab_decorator.tab.get_view()
-
- self._compiler = TemplateCompiler()
-
- # create tags
- self._tag_template = self._text_buffer.create_tag("template",
- background=Preferences().get("template-background-color"))
- self._tag_placeholder = self._text_buffer.create_tag("placeholder",
- background=Preferences().get("placeholder-background-color"),
- foreground=Preferences().get("placeholder-foreground-color"))
-
- self._active = False
-
- @verbose
- def insert(self, template):
- """
- @param template: a Template instance
- @raise MalformedTemplateException: from TemplateCompiler.compile
- """
- assert type(template) is Template
-
- # apply indentation
- expression = template.expression.replace("\n", "\n%s" % self._editor.indentation)
-
- self._compiler.compile(expression)
- self._do_insert()
-
- def _do_insert(self):
- if len(self._compiler.placeholders) == 0:
- # template contains no placeholders > just insert plain text
- #
- # if it contains a cursor position, we check if there's a selection
- # if there is one, we surround that selection by the two pieces of the template
-
- if self._compiler.final_cursor_offset:
- bounds = self._text_buffer.get_selection_bounds()
- if len(bounds):
- # cursor position and selection > surround selection
-
- text = self._compiler.plain
- position = self._compiler.final_cursor_offset
-
- leftText = text[:position]
- rightText = text[position:]
-
- # store the selection marks
- lMark = self._text_buffer.create_mark(None, bounds[0], False)
-
- rMark = self._text_buffer.create_mark(None, bounds[1], True)
-
- # insert first piece at the beginning...
- self._text_buffer.insert(bounds[0], leftText)
-
- # ...and the other one at the end
- rIter = self._text_buffer.get_iter_at_mark(rMark)
- self._text_buffer.insert(rIter, rightText)
-
- # restore selection
- lIter = self._text_buffer.get_iter_at_mark(lMark)
- rIter = self._text_buffer.get_iter_at_mark(rMark)
- self._text_buffer.select_range(lIter, rIter)
-
- # delete marks
- self._text_buffer.delete_mark(lMark)
- self._text_buffer.delete_mark(rMark)
-
- else:
- # cursor position, no selection > insert text and place cursor
-
- startOffset = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert()).get_offset()
- self._text_buffer.insert_at_cursor(self._compiler.plain)
- self._markEnd = self._text_buffer.create_mark(None,
- self._text_buffer.get_iter_at_offset(startOffset + self._compiler.final_cursor_offset), True)
- self._text_buffer.place_cursor(self._text_buffer.get_iter_at_mark(self._markEnd))
-
- else:
- # no final cursor position > just insert plain text
-
- self._text_buffer.insert_at_cursor(self._compiler.plain)
-
- else:
- # avoid inserting templates into templates
- if self._active:
- self._leave_template(place_cursor=False)
-
- # save template start offset
- startIter = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
- start = startIter.get_offset()
- self._markStart = self._text_buffer.create_mark(None, startIter, True)
-
- # insert template text
- self._text_buffer.insert_at_cursor(self._compiler.plain)
-
- # highlight placeholders and save their positions
-
- self._placeholder_marks = [] # holds the left and right marks of all placeholders in the active template
-
- for placeholder in self._compiler.placeholders:
-
- itLeft = self._text_buffer.get_iter_at_offset(start + placeholder.offset)
- itRight = self._text_buffer.get_iter_at_offset(start + placeholder.offset + len(placeholder.label))
-
- self._text_buffer.apply_tag_by_name("placeholder", itLeft, itRight)
-
- markLeft = self._text_buffer.create_mark(None, itLeft, True)
- markRight = self._text_buffer.create_mark(None, itRight, False)
-
- self._placeholder_marks.append([markLeft, markRight])
-
-
- # highlight complete template area
- itStart = self._text_buffer.get_iter_at_offset(start)
- itEnd = self._text_buffer.get_iter_at_offset(start + len(self._compiler.plain))
-
- self._text_buffer.apply_tag_by_name("template", itStart, itEnd)
-
- self._markEnd = self._text_buffer.create_mark(None, itEnd, True)
-
- # mark end cursor position or template end
- if self._compiler.final_cursor_offset:
- self._mark_final = self._text_buffer.create_mark(None,
- self._text_buffer.get_iter_at_offset(start + self._compiler.final_cursor_offset),
- True)
- else:
- self._mark_final = self._text_buffer.create_mark(None, itEnd, True)
-
-
- self._selected_placeholder = 0
-
- self._activate()
-
- self._select_next_placeholder()
-
- def _activate(self):
- """
- Listen to TextView signals
- """
- assert not self._active
-
- self._handlers = [
- self._text_view.connect("key-press-event", self._on_key_pressed),
- self._text_view.connect_after("key-release-event", self._on_key_released),
- self._text_view.connect("button-press-event", self._on_button_pressed) ]
- self._active = True
-
- def _deactivate(self):
- """
- Disconnect from TextView signals
- """
- assert self._active
-
- for handler in self._handlers:
- self._text_view.disconnect(handler)
-
- # TODO: delete TextMarks
-
- self._active = False
-
- def _on_key_pressed(self, text_view, event):
- """
- Jump to the next or to the previous placeholder mark
- """
- assert self._active
-
- key = Gdk.keyval_name(event.keyval)
-
- if key == self._KEY_TAB:
- # select next placeholder
- self._selected_placeholder += 1
-
- try:
- self._select_next_placeholder()
- except IndexError:
- # last reached
- self._leave_template()
-
- # swallow event
- return True
-
- elif key == self._KEY_LEFT_SHIFT_TAB:
- # select previous placeholder
- self._selected_placeholder -= 1
-
- try:
- self._select_next_placeholder()
- except IndexError:
- # first reached
- self._selected_placeholder = 0
-
- # swallow event
- return True
-
- elif key == self._KEY_ESCAPE:
- # abort
- self._leave_template()
-
- def _update_duplicates(self):
- """
- Copy the text from the current placeholder to its duplicates
- if present
- """
-
- placeholders = self._compiler.placeholders
- selected_i = self._selected_placeholder
- try:
- selected_placeholder = placeholders[selected_i]
- except IndexError:
- # FIXME: template has been left?
- return
-
- # find duplicates
- duplicates = []
- for i in range(len(self._compiler.placeholders)):
- if i != selected_i and placeholders[i].label == placeholders[selected_i].label:
- duplicates.append(i)
-
- # get current text in placeholder
- l, r = self._placeholder_marks[selected_i]
- li = self._text_buffer.get_iter_at_mark(l)
- ri = self._text_buffer.get_iter_at_mark(r)
-
- text = self._text_buffer.get_text(li, ri, False)
-
- # copy text to duplicates
- for i in duplicates:
- l, r = self._placeholder_marks[i]
-
- li = self._text_buffer.get_iter_at_mark(l)
- ri = self._text_buffer.get_iter_at_mark(r)
-
- self._text_buffer.delete(li, ri)
- self._text_buffer.insert(ri, text)
-
- def _on_key_released(self, text_view, event):
- """
- Swallow key events if neccessary
- """
- assert self._active
-
- key = Gdk.keyval_name(event.keyval)
-
- if key == self._KEY_TAB or key == self._KEY_LEFT_SHIFT_TAB:
- # swallow event
- return True
- else:
- # check if the cursor has left the template
- if self._cursor_in_template:
- if not key == self._KEY_RETURN: # don't update dupl after Return to keep the placeholder highlightings
- self._update_duplicates()
- else:
- self._leave_template(place_cursor=False)
-
-
- def _on_button_pressed(self, text_view, event):
- """
- Leave template when mouse button is pressed
- """
- assert self._active
-
- self._leave_template()
-
- def _select_next_placeholder(self):
- """
- Select the next placeholder
- """
- # get stored marks
- markLeft, markRight = self._placeholder_marks[self._selected_placeholder]
-
- # select
- itLeft = self._text_buffer.get_iter_at_mark(markLeft)
- itRight = self._text_buffer.get_iter_at_mark(markRight)
-
- self._text_buffer.select_range(itLeft, itRight)
-
- def _leave_template(self, place_cursor=True):
- """
- Quit template insertion.
-
- Disconnect from signals, remove highlight, delete marks and place the cursor
- at the final position.
- """
- #self._log.debug("_leaveTemplate")
-
- # remove highlighting
- self._text_buffer.remove_tag_by_name("placeholder", self._text_buffer.get_start_iter(),
- self._text_buffer.get_end_iter())
- self._text_buffer.remove_tag_by_name("template", self._text_buffer.get_start_iter(),
- self._text_buffer.get_end_iter())
-
- # move to end cursor position or template end
- if place_cursor:
- self._text_buffer.place_cursor(self._text_buffer.get_iter_at_mark(self._mark_final))
-
- self._deactivate()
-
- @property
- def _cursor_in_template(self):
- """
- @return: True if the cursor is in the template
- """
- itLeft = self._text_buffer.get_iter_at_mark(self._markStart)
- itRight = self._text_buffer.get_iter_at_mark(self._markEnd)
-
- left = itLeft.get_offset()
- right = itRight.get_offset()
- offset = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert()).get_offset()
-
- if offset < left or offset > right:
- return False
- return True
-
- def destroy(self):
- self._log.debug("destroy")
-
- # remove tags
- table = self._text_buffer.get_tag_table()
- table.remove(self._tag_template)
- table.remove(self._tag_placeholder)
-
- # unreference editor
- del self._editor
-
- # deactivate
- if self._active:
- self._deactivate()
-
-
-
+ """
+ This handles templates for an Editor instance
+ """
+
+ _log = getLogger("TemplateDelegate")
+
+ _KEY_TAB = "Tab"
+ _KEY_LEFT_SHIFT_TAB = "ISO_Left_Tab"
+ _KEY_ESCAPE = "Escape"
+ _KEY_RETURN = "Return"
+
+ def __init__(self, editor):
+ self._editor = editor
+ self._text_buffer = editor.tab_decorator.tab.get_document()
+ self._text_view = editor.tab_decorator.tab.get_view()
+
+ self._compiler = TemplateCompiler()
+
+ # create tags
+ self._tag_template = self._text_buffer.create_tag("template",
+ background=Preferences().get("template-background-color"))
+ self._tag_placeholder = self._text_buffer.create_tag("placeholder",
+ background=Preferences().get("placeholder-background-color"),
+ foreground=Preferences().get("placeholder-foreground-color"))
+
+ self._active = False
+
+ @verbose
+ def insert(self, template):
+ """
+ @param template: a Template instance
+ @raise MalformedTemplateException: from TemplateCompiler.compile
+ """
+ assert type(template) is Template
+
+ # apply indentation
+ expression = template.expression.replace("\n", "\n%s" % self._editor.indentation)
+
+ self._compiler.compile(expression)
+ self._do_insert()
+
+ def _do_insert(self):
+ if len(self._compiler.placeholders) == 0:
+ # template contains no placeholders > just insert plain text
+ #
+ # if it contains a cursor position, we check if there's a selection
+ # if there is one, we surround that selection by the two pieces of the template
+
+ if self._compiler.final_cursor_offset:
+ bounds = self._text_buffer.get_selection_bounds()
+ if len(bounds):
+ # cursor position and selection > surround selection
+
+ text = self._compiler.plain
+ position = self._compiler.final_cursor_offset
+
+ leftText = text[:position]
+ rightText = text[position:]
+
+ # store the selection marks
+ lMark = self._text_buffer.create_mark(None, bounds[0], False)
+
+ rMark = self._text_buffer.create_mark(None, bounds[1], True)
+
+ # insert first piece at the beginning...
+ self._text_buffer.insert(bounds[0], leftText)
+
+ # ...and the other one at the end
+ rIter = self._text_buffer.get_iter_at_mark(rMark)
+ self._text_buffer.insert(rIter, rightText)
+
+ # restore selection
+ lIter = self._text_buffer.get_iter_at_mark(lMark)
+ rIter = self._text_buffer.get_iter_at_mark(rMark)
+ self._text_buffer.select_range(lIter, rIter)
+
+ # delete marks
+ self._text_buffer.delete_mark(lMark)
+ self._text_buffer.delete_mark(rMark)
+
+ else:
+ # cursor position, no selection > insert text and place cursor
+
+ startOffset = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert()).get_offset()
+ self._text_buffer.insert_at_cursor(self._compiler.plain)
+ self._markEnd = self._text_buffer.create_mark(None,
+ self._text_buffer.get_iter_at_offset(startOffset + self._compiler.final_cursor_offset), True)
+ self._text_buffer.place_cursor(self._text_buffer.get_iter_at_mark(self._markEnd))
+
+ else:
+ # no final cursor position > just insert plain text
+
+ self._text_buffer.insert_at_cursor(self._compiler.plain)
+
+ else:
+ # avoid inserting templates into templates
+ if self._active:
+ self._leave_template(place_cursor=False)
+
+ # save template start offset
+ startIter = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert())
+ start = startIter.get_offset()
+ self._markStart = self._text_buffer.create_mark(None, startIter, True)
+
+ # insert template text
+ self._text_buffer.insert_at_cursor(self._compiler.plain)
+
+ # highlight placeholders and save their positions
+
+ self._placeholder_marks = [] # holds the left and right marks of all placeholders in the active template
+
+ for placeholder in self._compiler.placeholders:
+
+ itLeft = self._text_buffer.get_iter_at_offset(start + placeholder.offset)
+ itRight = self._text_buffer.get_iter_at_offset(start + placeholder.offset + len(placeholder.label))
+
+ self._text_buffer.apply_tag_by_name("placeholder", itLeft, itRight)
+
+ markLeft = self._text_buffer.create_mark(None, itLeft, True)
+ markRight = self._text_buffer.create_mark(None, itRight, False)
+
+ self._placeholder_marks.append([markLeft, markRight])
+
+
+ # highlight complete template area
+ itStart = self._text_buffer.get_iter_at_offset(start)
+ itEnd = self._text_buffer.get_iter_at_offset(start + len(self._compiler.plain))
+
+ self._text_buffer.apply_tag_by_name("template", itStart, itEnd)
+
+ self._markEnd = self._text_buffer.create_mark(None, itEnd, True)
+
+ # mark end cursor position or template end
+ if self._compiler.final_cursor_offset:
+ self._mark_final = self._text_buffer.create_mark(None,
+ self._text_buffer.get_iter_at_offset(start + self._compiler.final_cursor_offset),
+ True)
+ else:
+ self._mark_final = self._text_buffer.create_mark(None, itEnd, True)
+
+
+ self._selected_placeholder = 0
+
+ self._activate()
+
+ self._select_next_placeholder()
+
+ def _activate(self):
+ """
+ Listen to TextView signals
+ """
+ assert not self._active
+
+ self._handlers = [
+ self._text_view.connect("key-press-event", self._on_key_pressed),
+ self._text_view.connect_after("key-release-event", self._on_key_released),
+ self._text_view.connect("button-press-event", self._on_button_pressed) ]
+ self._active = True
+
+ def _deactivate(self):
+ """
+ Disconnect from TextView signals
+ """
+ assert self._active
+
+ for handler in self._handlers:
+ self._text_view.disconnect(handler)
+
+ # TODO: delete TextMarks
+
+ self._active = False
+
+ def _on_key_pressed(self, text_view, event):
+ """
+ Jump to the next or to the previous placeholder mark
+ """
+ assert self._active
+
+ key = Gdk.keyval_name(event.keyval)
+
+ if key == self._KEY_TAB:
+ # select next placeholder
+ self._selected_placeholder += 1
+
+ try:
+ self._select_next_placeholder()
+ except IndexError:
+ # last reached
+ self._leave_template()
+
+ # swallow event
+ return True
+
+ elif key == self._KEY_LEFT_SHIFT_TAB:
+ # select previous placeholder
+ self._selected_placeholder -= 1
+
+ try:
+ self._select_next_placeholder()
+ except IndexError:
+ # first reached
+ self._selected_placeholder = 0
+
+ # swallow event
+ return True
+
+ elif key == self._KEY_ESCAPE:
+ # abort
+ self._leave_template()
+
+ def _update_duplicates(self):
+ """
+ Copy the text from the current placeholder to its duplicates
+ if present
+ """
+
+ placeholders = self._compiler.placeholders
+ selected_i = self._selected_placeholder
+ try:
+ selected_placeholder = placeholders[selected_i]
+ except IndexError:
+ # FIXME: template has been left?
+ return
+
+ # find duplicates
+ duplicates = []
+ for i in range(len(self._compiler.placeholders)):
+ if i != selected_i and placeholders[i].label == placeholders[selected_i].label:
+ duplicates.append(i)
+
+ # get current text in placeholder
+ l, r = self._placeholder_marks[selected_i]
+ li = self._text_buffer.get_iter_at_mark(l)
+ ri = self._text_buffer.get_iter_at_mark(r)
+
+ text = self._text_buffer.get_text(li, ri, False)
+
+ # copy text to duplicates
+ for i in duplicates:
+ l, r = self._placeholder_marks[i]
+
+ li = self._text_buffer.get_iter_at_mark(l)
+ ri = self._text_buffer.get_iter_at_mark(r)
+
+ self._text_buffer.delete(li, ri)
+ self._text_buffer.insert(ri, text)
+
+ def _on_key_released(self, text_view, event):
+ """
+ Swallow key events if neccessary
+ """
+ assert self._active
+
+ key = Gdk.keyval_name(event.keyval)
+
+ if key == self._KEY_TAB or key == self._KEY_LEFT_SHIFT_TAB:
+ # swallow event
+ return True
+ else:
+ # check if the cursor has left the template
+ if self._cursor_in_template:
+ if not key == self._KEY_RETURN: # don't update dupl after Return to keep the placeholder highlightings
+ self._update_duplicates()
+ else:
+ self._leave_template(place_cursor=False)
+
+
+ def _on_button_pressed(self, text_view, event):
+ """
+ Leave template when mouse button is pressed
+ """
+ assert self._active
+
+ self._leave_template()
+
+ def _select_next_placeholder(self):
+ """
+ Select the next placeholder
+ """
+ # get stored marks
+ markLeft, markRight = self._placeholder_marks[self._selected_placeholder]
+
+ # select
+ itLeft = self._text_buffer.get_iter_at_mark(markLeft)
+ itRight = self._text_buffer.get_iter_at_mark(markRight)
+
+ self._text_buffer.select_range(itLeft, itRight)
+
+ def _leave_template(self, place_cursor=True):
+ """
+ Quit template insertion.
+
+ Disconnect from signals, remove highlight, delete marks and place the cursor
+ at the final position.
+ """
+ #self._log.debug("_leaveTemplate")
+
+ # remove highlighting
+ self._text_buffer.remove_tag_by_name("placeholder", self._text_buffer.get_start_iter(),
+ self._text_buffer.get_end_iter())
+ self._text_buffer.remove_tag_by_name("template", self._text_buffer.get_start_iter(),
+ self._text_buffer.get_end_iter())
+
+ # move to end cursor position or template end
+ if place_cursor:
+ self._text_buffer.place_cursor(self._text_buffer.get_iter_at_mark(self._mark_final))
+
+ self._deactivate()
+
+ @property
+ def _cursor_in_template(self):
+ """
+ @return: True if the cursor is in the template
+ """
+ itLeft = self._text_buffer.get_iter_at_mark(self._markStart)
+ itRight = self._text_buffer.get_iter_at_mark(self._markEnd)
+
+ left = itLeft.get_offset()
+ right = itRight.get_offset()
+ offset = self._text_buffer.get_iter_at_mark(self._text_buffer.get_insert()).get_offset()
+
+ if offset < left or offset > right:
+ return False
+ return True
+
+ def destroy(self):
+ self._log.debug("destroy")
+
+ # remove tags
+ table = self._text_buffer.get_tag_table()
+ table.remove(self._tag_template)
+ table.remove(self._tag_placeholder)
+
+ # unreference editor
+ del self._editor
+
+ # deactivate
+ if self._active:
+ self._deactivate()
+
+
+
diff --git a/latex/base/windowactivatable.py b/latex/base/windowactivatable.py
index 94cfedd..d1a49b8 100644
--- a/latex/base/windowactivatable.py
+++ b/latex/base/windowactivatable.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -26,7 +26,7 @@ from gi.repository import Gedit, GObject, Gio, Gtk, PeasGtk
import logging
import string
-logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(name)s - %(message)s")
+logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s %(name)s - %(message)s")
from ..preferences import Preferences
from ..preferences.dialog import PreferencesDialog
@@ -38,599 +38,599 @@ from . import File, SideView, BottomView, WindowContext
from decorators import GeditTabDecorator
class LaTeXWindowActivatable(GObject.Object, Gedit.WindowActivatable, PeasGtk.Configurable):
- __gtype_name__ = "LaTeXWindowActivatable"
-
- """
- This class
- - manages the GeditTabDecorators
- - hooks the actions as menu items and tool items
- - installs side and bottom panel views
- """
-
- window = GObject.property(type=Gedit.Window)
-
- _log = logging.getLogger("LaTeXWindowActivatable")
-
- # ui definition template for hooking tools in Gedit's ui
- _tool_ui_template = string.Template("""<ui>
- <menubar name="MenuBar">
- <menu name="ToolsMenu" action="Tools">
- <placeholder name="ToolsOps_1">$items</placeholder>
- </menu>
- </menubar>
- </ui>""")
-
- def __init__(self):
- GObject.Object.__init__(self)
-
- def do_activate(self):
- """
- Called when the window extension is activated
- """
- self._preferences = Preferences()
- self._tool_preferences = ToolPreferences()
- self._tool_preferences.connect("tools-changed", self._on_tools_changed)
-
- #
- # initialize context object
- #
- self._window_context = WindowContext(self, EDITOR_SCOPE_VIEWS)
-
- # the order is important!
- self._init_actions()
- self._init_tool_actions()
- self._init_views()
- self._init_tab_decorators()
-
- # FIXME: find another way to save a document
- self._save_action = self._ui_manager.get_action("/MenuBar/FileMenu/FileSaveMenu")
-
- #
- # listen to tab signals
- #
- self._signal_handlers = [
- self.window.connect("tab_added", self._on_tab_added),
- self.window.connect("tab_removed", self._on_tab_removed),
- self.window.connect("active_tab_changed", self._on_active_tab_changed) ]
-
- def do_deactivate(self):
- """
- Called when the window extension is deactivated
- """
- # save preferences and stop listening
- self._preferences.save()
- self._tool_preferences.save()
-
- # destroy tab decorators
- self._active_tab_decorator = None
- for tab in self._tab_decorators:
- self._tab_decorators[tab].destroy()
- self._tab_decorators = {}
-
- # disconnect from tab signals
- for handler in self._signal_handlers:
- self.window.disconnect(handler)
- del self._signal_handlers
-
- # remove all views
- self.disable()
-
- # destroy all window scope views
- # (the editor scope views are destroyed by the editor)
- for i in self._window_context.window_scope_views:
- self._window_context.window_scope_views[i].destroy()
- self._window_context.window_scope_views = {}
-
- # remove toolbar
- self._toolbar.destroy()
-
- # remove tool actions
- self._ui_manager.remove_ui(self._tool_ui_id)
- for gtk_action in self._action_handlers:
- gtk_action.disconnect(self._action_handlers[gtk_action])
- self._tool_action_group.remove_action(gtk_action)
- self._ui_manager.remove_action_group(self._tool_action_group)
-
- # remove actions
- self._ui_manager.remove_ui(self._ui_id)
- for clazz in self._action_objects:
- self._action_objects[clazz].unhook(self._action_group)
- self._ui_manager.remove_action_group(self._action_group)
-
- # destroy the window context
- self._window_context.destroy()
- del self._window_context
-
- def do_create_configure_widget(self):
- return PreferencesDialog().dialog
-
- def _init_views(self):
- """
- """
-
- # selection states for each TabDecorator
- self._selected_bottom_views = {}
- self._selected_side_views = {}
-
- # currently hooked editor-scope views
- self._side_views = []
- self._bottom_views = []
-
- # currently hooked window-scope views
- self._window_side_views = []
- self._window_bottom_views = []
-
- # caches window-scope View instances
- self._views = {}
-
- #
- # init the ToolView, it's always present
- #
- # TODO: position is ignored
- #
- tool_view = ToolView(self._window_context)
- self._views["ToolView"] = tool_view
- #fixme put the id!
- bottom_panel = self.window.get_bottom_panel()
- bottom_panel.add_item(tool_view, "ToolViewid", tool_view.label, tool_view.icon)
- #self._window_bottom_views.append(tool_view)
-
- # update window context
- self._window_context.window_scope_views = self._views
-
- def _init_actions(self):
- """
- Merge the plugin's UI definition with the one of Gedit and hook the actions
- """
- self._ui_manager = self.window.get_ui_manager()
- self._action_group = Gtk.ActionGroup("LaTeXWindowActivatableActions")
- self._icon_factory = Gtk.IconFactory()
- self._icon_factory.add_default()
-
- # create action instances, hook them and build up some
- # hash tables
-
- self._action_objects = {} # name -> Action object
- self._action_extensions = {} # extension -> action names
-
- for clazz in ACTIONS:
- action = clazz(icon_factory=self._icon_factory)
- action.hook(self._action_group, self._window_context)
-
- self._action_objects[clazz.__name__] = action
-
- for extension in action.extensions:
- if extension in self._action_extensions.keys():
- self._action_extensions[extension].append(clazz.__name__)
- else:
- self._action_extensions[extension] = [clazz.__name__]
-
- # merge ui
- self._ui_manager.insert_action_group(self._action_group, -1)
- self._ui_id = self._ui_manager.add_ui_from_string(UI)
-
- # hook the toolbar
- self._toolbar = self._ui_manager.get_widget("/LaTeXToolbar")
- self._toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ)
-
- self._main_box = self.window.get_children()[0]
- self._main_box.pack_start(self._toolbar, False, True, 0)
- self._main_box.reorder_child(self._toolbar, 2)
-
- def _init_tab_decorators(self):
- """
- Look for already open tabs and create decorators for them
- """
- self._tab_decorators = {}
- self._active_tab_decorator = None
- active_view = self.window.get_active_view()
- views = self.window.get_views()
-
- for view in views:
- tab = Gedit.Tab.get_from_document(view.get_buffer())
- decorator = self._create_tab_decorator(tab, init=True)
- if view is active_view:
- self._active_tab_decorator = decorator
-
- self._log.debug("_init_tab_decorators: initialized %s decorators" % len(views))
-
- if len(views) > 0 and not self._active_tab_decorator:
- self._log.warning("_init_tab_decorators: no active decorator found")
-
- def _init_tool_actions(self):
- """
- - Load defined Tools
- - create and init ToolActions from them
- - hook them in the window UI
- - create a map from extensions to lists of ToolActions
- """
-
- # add a MenuToolButton with the tools menu to the toolbar afterwards
- # FIXME: this is quite hacky
- menu = Gtk.Menu()
-
- # this is used for enable/disable actions by name
- # None stands for every extension
- self._tool_action_extensions = { None : [] }
-
- self._tool_action_group = Gtk.ActionGroup("LaTeXPluginToolActions")
-
- items_ui = ""
-
- self._action_handlers = {}
-
- i = 1 # counting tool actions
- accel_counter = 1 # counting tool actions without custom accel
- for tool in self._tool_preferences.tools:
- # hopefully unique action name
- name = "Tool%sAction" % i
-
- # update extension-tool mapping
- for extension in tool.extensions:
- try:
- self._tool_action_extensions[extension].append(name)
- except KeyError:
- # extension not yet mapped
- self._tool_action_extensions[extension] = [name]
-
- # create action
- action = ToolAction(tool)
- gtk_action = Gtk.Action(name, action.label, action.tooltip, action.stock_id)
- self._action_handlers[gtk_action] = gtk_action.connect("activate", lambda gtk_action, action: action.activate(self._window_context), action)
-
- if not tool.accelerator is None and len(tool.accelerator) > 0:
- # TODO: validate accelerator!
- self._tool_action_group.add_action_with_accel(gtk_action, tool.accelerator)
- else:
- self._tool_action_group.add_action_with_accel(gtk_action, "<Ctrl><Alt>%s" % accel_counter)
- accel_counter += 1
-
- # add to MenuToolBar menu
- # FIXME: GtkWarning: gtk_accel_label_set_accel_closure: assertion `gtk_accel_group_from_accel_closure (accel_closure) != NULL' failed
- menu.add(gtk_action.create_menu_item())
-
- # add UI definition
- items_ui += """<menuitem action="%s" />""" % name
-
- i += 1
-
- tool_ui = self._tool_ui_template.substitute({"items" : items_ui})
-
- self._ui_manager.insert_action_group(self._tool_action_group, -1)
- self._tool_ui_id = self._ui_manager.add_ui_from_string(tool_ui)
-
- # add a MenuToolButton with the tools menu to the toolbar
- self._menu_tool_button = Gtk.MenuToolButton.new_from_stock(Gtk.STOCK_CONVERT)
- self._menu_tool_button.set_menu(menu)
- self._menu_tool_button.show_all()
- self._toolbar.insert(self._menu_tool_button, -1)
-
- def save_file(self):
- """
- Trigger the 'Save' action
-
- (used by ToolAction before tool run)
- """
- self._save_action.activate()
-
- def _on_tools_changed(self):
- self._log.debug("_on_tools_changed")
-
- # remove tool actions and ui
- self._ui_manager.remove_ui(self._tool_ui_id)
- for gtk_action in self._action_handlers:
- gtk_action.disconnect(self._action_handlers[gtk_action])
- self._tool_action_group.remove_action(gtk_action)
- self._ui_manager.remove_action_group(self._tool_action_group)
-
- # remove MenuToolButton
- self._toolbar.remove(self._menu_tool_button)
-
- # re-init tool actions
- self._init_tool_actions()
-
- # re-adjust action states
- self.adjust(self._active_tab_decorator)
-
- def activate_tab(self, file):
- """
- Activate the GeditTab containing the given File or open a new
- tab for it (this is called by the WindowContext)
-
- @param file: a File object
- """
- for tab, tab_decorator in self._tab_decorators.iteritems():
- if tab_decorator.file and tab_decorator.file == file:
- self.window.set_active_tab(tab)
- return
-
- # not found, open file in a new tab...
-
- uri = file.uri
- gfile = Gio.file_new_for_uri(uri)
-
- if Gedit.utils_is_valid_location(gfile):
- self._log.debug("GeditWindow.create_tab_from_uri(%s)" % uri)
- self.window.create_tab_from_location(
- gfile, Gedit.encoding_get_current(),
- 1, 1, False, True)
- else:
- self._log.error("Gedit.utils.uri_is_valid(%s) = False" % uri)
-
- def disable(self):
- """
- Called if there are no more tabs after tab_removed
- """
- self._toolbar.hide()
-
- # disable all actions
- for name in self._action_objects.iterkeys():
- self._action_group.get_action(name).set_visible(False)
-
- # disable all tool actions
- for l in self._tool_action_extensions.values():
- for name in l:
- self._tool_action_group.get_action(name).set_sensitive(False)
-
- # remove all side views
- side_views = self._window_side_views + self._side_views
- for view in side_views:
- self.window.get_side_panel().remove_item(view)
- if view in self._side_views: self._side_views.remove(view)
- if view in self._window_side_views: self._window_side_views.remove(view)
-
- # remove all bottom views
- bottom_views = self._window_bottom_views + self._bottom_views
- for view in bottom_views:
- self.window.get_bottom_panel().remove_item(view)
- if view in self._bottom_views: self._bottom_views.remove(view)
- if view in self._window_bottom_views: self._window_bottom_views.remove(view)
-
- def adjust(self, tab_decorator):
- """
- Adjust actions and views according to the currently active TabDecorator
- (the file type it contains)
-
- Called by
- * _on_active_tab_changed()
- * GeditTabDecorator when the Editor instance changes
- """
-
- # TODO: improve and simplify this!
-
- extension = tab_decorator.extension
-
- self._log.debug("---------- ADJUST: %s" % (extension))
-
- # FIXME: a hack again...
- # the toolbar should hide when it doesn't contain any visible items
- latex_extensions = self._preferences.get("latex-extensions").split(",")
- show_toolbar = self._preferences.get_bool("show-latex-toolbar")
- if show_toolbar and extension in latex_extensions:
- self._toolbar.show()
- else:
- self._toolbar.hide()
-
- #
- # adjust actions
- #
- # FIXME: we always get the state of the new decorator after tab change
- # but we need to save the one of the old decorator
- #
- # FIXME: we are dealing with sets so saving the index as selection state
- # is nonsense
- #
-
- # disable all actions
- for name in self._action_objects:
- self._action_group.get_action(name).set_visible(False)
-
- # disable all tool actions
- for l in self._tool_action_extensions.values():
- for name in l:
- self._tool_action_group.get_action(name).set_sensitive(False)
-
-
- # enable the actions for all extensions
- for name in self._action_extensions[None]:
- self._action_group.get_action(name).set_visible(True)
-
- # enable the actions registered for the extension
- if extension:
- try:
- for name in self._action_extensions[extension]:
- self._action_group.get_action(name).set_visible(True)
- except KeyError:
- pass
-
-
- # enable the tool actions that apply for all extensions
- for name in self._tool_action_extensions[None]:
- self._tool_action_group.get_action(name).set_sensitive(True)
-
- # enable the tool actions that apply for this extension
- if extension:
- try:
- for name in self._tool_action_extensions[extension]:
- self._tool_action_group.get_action(name).set_sensitive(True)
- except KeyError:
- pass
-
- #
- # adjust editor-scope views
- #
-
- # determine set of side/bottom views BEFORE
-
- before_side_views = set(self._side_views)
- before_bottom_views = set(self._bottom_views)
-
- # determine set of side/bottom views AFTER
-
- after_side_views = set()
- after_bottom_views = set()
-
- if tab_decorator.editor:
- editor_views = self._window_context.editor_scope_views[tab_decorator.editor]
- for id, view in editor_views.iteritems():
- if isinstance(view, BottomView):
- after_bottom_views.add(view)
- elif isinstance(view, SideView):
- after_side_views.add(view)
- else:
- raise RuntimeError("Invalid view type: %s" % view)
-
- # remove BEFORE.difference(AFTER)
- for view in before_side_views.difference(after_side_views):
- self.window.get_side_panel().remove_item(view)
- self._side_views.remove(view)
-
- for view in before_bottom_views.difference(after_bottom_views):
- self.window.get_bottom_panel().remove_item(view)
- self._bottom_views.remove(view)
-
- # add AFTER.difference(BEFORE)
- i = 1
- for view in after_side_views.difference(before_side_views):
- i+=1
- self.window.get_side_panel().add_item(view, "after_side_view_id" + str(i), view.label, view.icon)
- self._side_views.append(view)
- i = 1
- for view in after_bottom_views.difference(before_bottom_views):
- i+=1
- print view.label, view.icon
- self.window.get_bottom_panel().add_item(view, "bottom_view_id" + str(i),view.label, view.icon)
- self._bottom_views.append(view)
-
-
- #
- # adjust window-scope views
- #
-
- # determine set of side/bottom views BEFORE
-
- before_window_side_views = set(self._window_side_views)
- before_window_bottom_views = set(self._window_bottom_views)
-
- # determine set of side/bottom views AFTER
-
- after_window_side_views = set()
- after_window_bottom_views = set()
-
- try:
- for id, clazz in WINDOW_SCOPE_VIEWS[extension].iteritems():
-
- # find or create View instance
- view = None
- try:
- view = self._views[id]
- except KeyError:
- view = clazz.__new__(clazz)
- clazz.__init__(view, self._window_context)
- self._views[id] = view
-
- if isinstance(view, BottomView):
- after_window_bottom_views.add(view)
- elif isinstance(view, SideView):
- after_window_side_views.add(view)
- else:
- raise RuntimeError("Invalid view type: %s" % view)
- except KeyError:
- self._log.debug("No window-scope views for this extension")
-
- # remove BEFORE.difference(AFTER)
- for view in before_window_side_views.difference(after_window_side_views):
- self.window.get_side_panel().remove_item(view)
- self._window_side_views.remove(view)
-
- for view in before_window_bottom_views.difference(after_window_bottom_views):
- self.window.get_bottom_panel().remove_item(view)
- self._window_bottom_views.remove(view)
-
- # add AFTER.difference(BEFORE)
- i = 1
- for view in after_window_side_views.difference(before_window_side_views):
- i += 1
- self.window.get_side_panel().add_item(view,"WHATView"+ str(i), view.label, view.icon)
- self._window_side_views.append(view)
-
- for view in after_window_bottom_views.difference(before_window_bottom_views):
- self.window.get_bottom_panel().add_item(view, view.label, view.icon)
- self._window_bottom_views.append(view)
-
- #
- # update window context
- #
- self._window_context.window_scope_views = self._views
-
- def _on_tab_added(self, window, tab):
- """
- A new tab has been added
-
- @param window: Gedit.Window object
- @param tab: Gedit.Tab object
- """
- self._log.debug("tab_added")
-
- if tab in self._tab_decorators:
- self._log.warning("There is already a decorator for tab %s" % tab)
- return
-
- self._create_tab_decorator(tab)
-
- def _on_tab_removed(self, window, tab):
- """
- A tab has been closed
-
- @param window: GeditWindow
- @param tab: the closed GeditTab
- """
- self._log.debug("tab_removed")
-
- # As we don't call GeditWindowDecorator.adjust() if the new
- # tab is not the active one (for example, when opening several
- # files at once, see GeditTabDecorator._adjust_editor()),
- # it may happen that self._selected_side_views[tab] is not set.
- if self._tab_decorators[tab] in self._selected_side_views:
- del self._selected_side_views[self._tab_decorators[tab]]
- if self._tab_decorators[tab] in self._selected_bottom_views:
- del self._selected_bottom_views[self._tab_decorators[tab]]
-
- self._tab_decorators[tab].destroy()
- if self._active_tab_decorator == self._tab_decorators[tab]:
- self._active_tab_decorator = None
-
- del self._tab_decorators[tab]
-
- if len(self._tab_decorators) == 0:
- # no more tabs
- self.disable()
-
- def _on_active_tab_changed(self, window, tab):
- """
- The active tab has changed
-
- @param window: the GeditWindow
- @param tab: the activated GeditTab
- """
- self._log.debug("active_tab_changed")
-
- if tab in self._tab_decorators.keys():
- decorator = self._tab_decorators[tab]
- else:
- # (on Gedit startup 'tab-changed' comes before 'tab-added')
- # remember: init=True crashes the plugin here!
- decorator = self._create_tab_decorator(tab)
-
- self._active_tab_decorator = decorator
-
- # adjust actions and views
- self.adjust(decorator)
-
- def _create_tab_decorator(self, tab, init=False):
- """
- Create a new GeditTabDecorator for a GeditTab
- """
- decorator = GeditTabDecorator(self, tab, init)
- self._tab_decorators[tab] = decorator
- return decorator
+ __gtype_name__ = "LaTeXWindowActivatable"
+
+ """
+ This class
+ - manages the GeditTabDecorators
+ - hooks the actions as menu items and tool items
+ - installs side and bottom panel views
+ """
+
+ window = GObject.property(type=Gedit.Window)
+
+ _log = logging.getLogger("LaTeXWindowActivatable")
+
+ # ui definition template for hooking tools in Gedit's ui
+ _tool_ui_template = string.Template("""<ui>
+ <menubar name="MenuBar">
+ <menu name="ToolsMenu" action="Tools">
+ <placeholder name="ToolsOps_1">$items</placeholder>
+ </menu>
+ </menubar>
+ </ui>""")
+
+ def __init__(self):
+ GObject.Object.__init__(self)
+
+ def do_activate(self):
+ """
+ Called when the window extension is activated
+ """
+ self._preferences = Preferences()
+ self._tool_preferences = ToolPreferences()
+ self._tool_preferences.connect("tools-changed", self._on_tools_changed)
+
+ #
+ # initialize context object
+ #
+ self._window_context = WindowContext(self, EDITOR_SCOPE_VIEWS)
+
+ # the order is important!
+ self._init_actions()
+ self._init_tool_actions()
+ self._init_views()
+ self._init_tab_decorators()
+
+ # FIXME: find another way to save a document
+ self._save_action = self._ui_manager.get_action("/MenuBar/FileMenu/FileSaveMenu")
+
+ #
+ # listen to tab signals
+ #
+ self._signal_handlers = [
+ self.window.connect("tab_added", self._on_tab_added),
+ self.window.connect("tab_removed", self._on_tab_removed),
+ self.window.connect("active_tab_changed", self._on_active_tab_changed) ]
+
+ def do_deactivate(self):
+ """
+ Called when the window extension is deactivated
+ """
+ # save preferences and stop listening
+ self._preferences.save()
+ self._tool_preferences.save()
+
+ # destroy tab decorators
+ self._active_tab_decorator = None
+ for tab in self._tab_decorators:
+ self._tab_decorators[tab].destroy()
+ self._tab_decorators = {}
+
+ # disconnect from tab signals
+ for handler in self._signal_handlers:
+ self.window.disconnect(handler)
+ del self._signal_handlers
+
+ # remove all views
+ self.disable()
+
+ # destroy all window scope views
+ # (the editor scope views are destroyed by the editor)
+ for i in self._window_context.window_scope_views:
+ self._window_context.window_scope_views[i].destroy()
+ self._window_context.window_scope_views = {}
+
+ # remove toolbar
+ self._toolbar.destroy()
+
+ # remove tool actions
+ self._ui_manager.remove_ui(self._tool_ui_id)
+ for gtk_action in self._action_handlers:
+ gtk_action.disconnect(self._action_handlers[gtk_action])
+ self._tool_action_group.remove_action(gtk_action)
+ self._ui_manager.remove_action_group(self._tool_action_group)
+
+ # remove actions
+ self._ui_manager.remove_ui(self._ui_id)
+ for clazz in self._action_objects:
+ self._action_objects[clazz].unhook(self._action_group)
+ self._ui_manager.remove_action_group(self._action_group)
+
+ # destroy the window context
+ self._window_context.destroy()
+ del self._window_context
+
+ def do_create_configure_widget(self):
+ return PreferencesDialog().dialog
+
+ def _init_views(self):
+ """
+ """
+
+ # selection states for each TabDecorator
+ self._selected_bottom_views = {}
+ self._selected_side_views = {}
+
+ # currently hooked editor-scope views
+ self._side_views = []
+ self._bottom_views = []
+
+ # currently hooked window-scope views
+ self._window_side_views = []
+ self._window_bottom_views = []
+
+ # caches window-scope View instances
+ self._views = {}
+
+ #
+ # init the ToolView, it's always present
+ #
+ # TODO: position is ignored
+ #
+ tool_view = ToolView(self._window_context)
+ self._views["ToolView"] = tool_view
+ #fixme put the id!
+ bottom_panel = self.window.get_bottom_panel()
+ bottom_panel.add_item(tool_view, "ToolViewid", tool_view.label, tool_view.icon)
+ #self._window_bottom_views.append(tool_view)
+
+ # update window context
+ self._window_context.window_scope_views = self._views
+
+ def _init_actions(self):
+ """
+ Merge the plugin's UI definition with the one of Gedit and hook the actions
+ """
+ self._ui_manager = self.window.get_ui_manager()
+ self._action_group = Gtk.ActionGroup("LaTeXWindowActivatableActions")
+ self._icon_factory = Gtk.IconFactory()
+ self._icon_factory.add_default()
+
+ # create action instances, hook them and build up some
+ # hash tables
+
+ self._action_objects = {} # name -> Action object
+ self._action_extensions = {} # extension -> action names
+
+ for clazz in ACTIONS:
+ action = clazz(icon_factory=self._icon_factory)
+ action.hook(self._action_group, self._window_context)
+
+ self._action_objects[clazz.__name__] = action
+
+ for extension in action.extensions:
+ if extension in self._action_extensions.keys():
+ self._action_extensions[extension].append(clazz.__name__)
+ else:
+ self._action_extensions[extension] = [clazz.__name__]
+
+ # merge ui
+ self._ui_manager.insert_action_group(self._action_group, -1)
+ self._ui_id = self._ui_manager.add_ui_from_string(UI)
+
+ # hook the toolbar
+ self._toolbar = self._ui_manager.get_widget("/LaTeXToolbar")
+ self._toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ)
+
+ self._main_box = self.window.get_children()[0]
+ self._main_box.pack_start(self._toolbar, False, True, 0)
+ self._main_box.reorder_child(self._toolbar, 2)
+
+ def _init_tab_decorators(self):
+ """
+ Look for already open tabs and create decorators for them
+ """
+ self._tab_decorators = {}
+ self._active_tab_decorator = None
+ active_view = self.window.get_active_view()
+ views = self.window.get_views()
+
+ for view in views:
+ tab = Gedit.Tab.get_from_document(view.get_buffer())
+ decorator = self._create_tab_decorator(tab, init=True)
+ if view is active_view:
+ self._active_tab_decorator = decorator
+
+ self._log.debug("_init_tab_decorators: initialized %s decorators" % len(views))
+
+ if len(views) > 0 and not self._active_tab_decorator:
+ self._log.warning("_init_tab_decorators: no active decorator found")
+
+ def _init_tool_actions(self):
+ """
+ - Load defined Tools
+ - create and init ToolActions from them
+ - hook them in the window UI
+ - create a map from extensions to lists of ToolActions
+ """
+
+ # add a MenuToolButton with the tools menu to the toolbar afterwards
+ # FIXME: this is quite hacky
+ menu = Gtk.Menu()
+
+ # this is used for enable/disable actions by name
+ # None stands for every extension
+ self._tool_action_extensions = { None : [] }
+
+ self._tool_action_group = Gtk.ActionGroup("LaTeXPluginToolActions")
+
+ items_ui = ""
+
+ self._action_handlers = {}
+
+ i = 1 # counting tool actions
+ accel_counter = 1 # counting tool actions without custom accel
+ for tool in self._tool_preferences.tools:
+ # hopefully unique action name
+ name = "Tool%sAction" % i
+
+ # update extension-tool mapping
+ for extension in tool.extensions:
+ try:
+ self._tool_action_extensions[extension].append(name)
+ except KeyError:
+ # extension not yet mapped
+ self._tool_action_extensions[extension] = [name]
+
+ # create action
+ action = ToolAction(tool)
+ gtk_action = Gtk.Action(name, action.label, action.tooltip, action.stock_id)
+ self._action_handlers[gtk_action] = gtk_action.connect("activate", lambda gtk_action, action: action.activate(self._window_context), action)
+
+ if not tool.accelerator is None and len(tool.accelerator) > 0:
+ # TODO: validate accelerator!
+ self._tool_action_group.add_action_with_accel(gtk_action, tool.accelerator)
+ else:
+ self._tool_action_group.add_action_with_accel(gtk_action, "<Ctrl><Alt>%s" % accel_counter)
+ accel_counter += 1
+
+ # add to MenuToolBar menu
+ # FIXME: GtkWarning: gtk_accel_label_set_accel_closure: assertion `gtk_accel_group_from_accel_closure (accel_closure) != NULL' failed
+ menu.add(gtk_action.create_menu_item())
+
+ # add UI definition
+ items_ui += """<menuitem action="%s" />""" % name
+
+ i += 1
+
+ tool_ui = self._tool_ui_template.substitute({"items" : items_ui})
+
+ self._ui_manager.insert_action_group(self._tool_action_group, -1)
+ self._tool_ui_id = self._ui_manager.add_ui_from_string(tool_ui)
+
+ # add a MenuToolButton with the tools menu to the toolbar
+ self._menu_tool_button = Gtk.MenuToolButton.new_from_stock(Gtk.STOCK_CONVERT)
+ self._menu_tool_button.set_menu(menu)
+ self._menu_tool_button.show_all()
+ self._toolbar.insert(self._menu_tool_button, -1)
+
+ def save_file(self):
+ """
+ Trigger the 'Save' action
+
+ (used by ToolAction before tool run)
+ """
+ self._save_action.activate()
+
+ def _on_tools_changed(self):
+ self._log.debug("_on_tools_changed")
+
+ # remove tool actions and ui
+ self._ui_manager.remove_ui(self._tool_ui_id)
+ for gtk_action in self._action_handlers:
+ gtk_action.disconnect(self._action_handlers[gtk_action])
+ self._tool_action_group.remove_action(gtk_action)
+ self._ui_manager.remove_action_group(self._tool_action_group)
+
+ # remove MenuToolButton
+ self._toolbar.remove(self._menu_tool_button)
+
+ # re-init tool actions
+ self._init_tool_actions()
+
+ # re-adjust action states
+ self.adjust(self._active_tab_decorator)
+
+ def activate_tab(self, file):
+ """
+ Activate the GeditTab containing the given File or open a new
+ tab for it (this is called by the WindowContext)
+
+ @param file: a File object
+ """
+ for tab, tab_decorator in self._tab_decorators.iteritems():
+ if tab_decorator.file and tab_decorator.file == file:
+ self.window.set_active_tab(tab)
+ return
+
+ # not found, open file in a new tab...
+
+ uri = file.uri
+ gfile = Gio.file_new_for_uri(uri)
+
+ if Gedit.utils_is_valid_location(gfile):
+ self._log.debug("GeditWindow.create_tab_from_uri(%s)" % uri)
+ self.window.create_tab_from_location(
+ gfile, Gedit.encoding_get_current(),
+ 1, 1, False, True)
+ else:
+ self._log.error("Gedit.utils.uri_is_valid(%s) = False" % uri)
+
+ def disable(self):
+ """
+ Called if there are no more tabs after tab_removed
+ """
+ self._toolbar.hide()
+
+ # disable all actions
+ for name in self._action_objects.iterkeys():
+ self._action_group.get_action(name).set_visible(False)
+
+ # disable all tool actions
+ for l in self._tool_action_extensions.values():
+ for name in l:
+ self._tool_action_group.get_action(name).set_sensitive(False)
+
+ # remove all side views
+ side_views = self._window_side_views + self._side_views
+ for view in side_views:
+ self.window.get_side_panel().remove_item(view)
+ if view in self._side_views: self._side_views.remove(view)
+ if view in self._window_side_views: self._window_side_views.remove(view)
+
+ # remove all bottom views
+ bottom_views = self._window_bottom_views + self._bottom_views
+ for view in bottom_views:
+ self.window.get_bottom_panel().remove_item(view)
+ if view in self._bottom_views: self._bottom_views.remove(view)
+ if view in self._window_bottom_views: self._window_bottom_views.remove(view)
+
+ def adjust(self, tab_decorator):
+ """
+ Adjust actions and views according to the currently active TabDecorator
+ (the file type it contains)
+
+ Called by
+ * _on_active_tab_changed()
+ * GeditTabDecorator when the Editor instance changes
+ """
+
+ # TODO: improve and simplify this!
+
+ extension = tab_decorator.extension
+
+ self._log.debug("---------- ADJUST: %s" % (extension))
+
+ # FIXME: a hack again...
+ # the toolbar should hide when it doesn't contain any visible items
+ latex_extensions = self._preferences.get("latex-extensions").split(",")
+ show_toolbar = self._preferences.get_bool("show-latex-toolbar")
+ if show_toolbar and extension in latex_extensions:
+ self._toolbar.show()
+ else:
+ self._toolbar.hide()
+
+ #
+ # adjust actions
+ #
+ # FIXME: we always get the state of the new decorator after tab change
+ # but we need to save the one of the old decorator
+ #
+ # FIXME: we are dealing with sets so saving the index as selection state
+ # is nonsense
+ #
+
+ # disable all actions
+ for name in self._action_objects:
+ self._action_group.get_action(name).set_visible(False)
+
+ # disable all tool actions
+ for l in self._tool_action_extensions.values():
+ for name in l:
+ self._tool_action_group.get_action(name).set_sensitive(False)
+
+
+ # enable the actions for all extensions
+ for name in self._action_extensions[None]:
+ self._action_group.get_action(name).set_visible(True)
+
+ # enable the actions registered for the extension
+ if extension:
+ try:
+ for name in self._action_extensions[extension]:
+ self._action_group.get_action(name).set_visible(True)
+ except KeyError:
+ pass
+
+
+ # enable the tool actions that apply for all extensions
+ for name in self._tool_action_extensions[None]:
+ self._tool_action_group.get_action(name).set_sensitive(True)
+
+ # enable the tool actions that apply for this extension
+ if extension:
+ try:
+ for name in self._tool_action_extensions[extension]:
+ self._tool_action_group.get_action(name).set_sensitive(True)
+ except KeyError:
+ pass
+
+ #
+ # adjust editor-scope views
+ #
+
+ # determine set of side/bottom views BEFORE
+
+ before_side_views = set(self._side_views)
+ before_bottom_views = set(self._bottom_views)
+
+ # determine set of side/bottom views AFTER
+
+ after_side_views = set()
+ after_bottom_views = set()
+
+ if tab_decorator.editor:
+ editor_views = self._window_context.editor_scope_views[tab_decorator.editor]
+ for id, view in editor_views.iteritems():
+ if isinstance(view, BottomView):
+ after_bottom_views.add(view)
+ elif isinstance(view, SideView):
+ after_side_views.add(view)
+ else:
+ raise RuntimeError("Invalid view type: %s" % view)
+
+ # remove BEFORE.difference(AFTER)
+ for view in before_side_views.difference(after_side_views):
+ self.window.get_side_panel().remove_item(view)
+ self._side_views.remove(view)
+
+ for view in before_bottom_views.difference(after_bottom_views):
+ self.window.get_bottom_panel().remove_item(view)
+ self._bottom_views.remove(view)
+
+ # add AFTER.difference(BEFORE)
+ i = 1
+ for view in after_side_views.difference(before_side_views):
+ i+=1
+ self.window.get_side_panel().add_item(view, "after_side_view_id" + str(i), view.label, view.icon)
+ self._side_views.append(view)
+ i = 1
+ for view in after_bottom_views.difference(before_bottom_views):
+ i+=1
+ print view.label, view.icon
+ self.window.get_bottom_panel().add_item(view, "bottom_view_id" + str(i),view.label, view.icon)
+ self._bottom_views.append(view)
+
+
+ #
+ # adjust window-scope views
+ #
+
+ # determine set of side/bottom views BEFORE
+
+ before_window_side_views = set(self._window_side_views)
+ before_window_bottom_views = set(self._window_bottom_views)
+
+ # determine set of side/bottom views AFTER
+
+ after_window_side_views = set()
+ after_window_bottom_views = set()
+
+ try:
+ for id, clazz in WINDOW_SCOPE_VIEWS[extension].iteritems():
+
+ # find or create View instance
+ view = None
+ try:
+ view = self._views[id]
+ except KeyError:
+ view = clazz.__new__(clazz)
+ clazz.__init__(view, self._window_context)
+ self._views[id] = view
+
+ if isinstance(view, BottomView):
+ after_window_bottom_views.add(view)
+ elif isinstance(view, SideView):
+ after_window_side_views.add(view)
+ else:
+ raise RuntimeError("Invalid view type: %s" % view)
+ except KeyError:
+ self._log.debug("No window-scope views for this extension")
+
+ # remove BEFORE.difference(AFTER)
+ for view in before_window_side_views.difference(after_window_side_views):
+ self.window.get_side_panel().remove_item(view)
+ self._window_side_views.remove(view)
+
+ for view in before_window_bottom_views.difference(after_window_bottom_views):
+ self.window.get_bottom_panel().remove_item(view)
+ self._window_bottom_views.remove(view)
+
+ # add AFTER.difference(BEFORE)
+ i = 1
+ for view in after_window_side_views.difference(before_window_side_views):
+ i += 1
+ self.window.get_side_panel().add_item(view,"WHATView"+ str(i), view.label, view.icon)
+ self._window_side_views.append(view)
+
+ for view in after_window_bottom_views.difference(before_window_bottom_views):
+ self.window.get_bottom_panel().add_item(view, view.label, view.icon)
+ self._window_bottom_views.append(view)
+
+ #
+ # update window context
+ #
+ self._window_context.window_scope_views = self._views
+
+ def _on_tab_added(self, window, tab):
+ """
+ A new tab has been added
+
+ @param window: Gedit.Window object
+ @param tab: Gedit.Tab object
+ """
+ self._log.debug("tab_added")
+
+ if tab in self._tab_decorators:
+ self._log.warning("There is already a decorator for tab %s" % tab)
+ return
+
+ self._create_tab_decorator(tab)
+
+ def _on_tab_removed(self, window, tab):
+ """
+ A tab has been closed
+
+ @param window: GeditWindow
+ @param tab: the closed GeditTab
+ """
+ self._log.debug("tab_removed")
+
+ # As we don't call GeditWindowDecorator.adjust() if the new
+ # tab is not the active one (for example, when opening several
+ # files at once, see GeditTabDecorator._adjust_editor()),
+ # it may happen that self._selected_side_views[tab] is not set.
+ if self._tab_decorators[tab] in self._selected_side_views:
+ del self._selected_side_views[self._tab_decorators[tab]]
+ if self._tab_decorators[tab] in self._selected_bottom_views:
+ del self._selected_bottom_views[self._tab_decorators[tab]]
+
+ self._tab_decorators[tab].destroy()
+ if self._active_tab_decorator == self._tab_decorators[tab]:
+ self._active_tab_decorator = None
+
+ del self._tab_decorators[tab]
+
+ if len(self._tab_decorators) == 0:
+ # no more tabs
+ self.disable()
+
+ def _on_active_tab_changed(self, window, tab):
+ """
+ The active tab has changed
+
+ @param window: the GeditWindow
+ @param tab: the activated GeditTab
+ """
+ self._log.debug("active_tab_changed")
+
+ if tab in self._tab_decorators.keys():
+ decorator = self._tab_decorators[tab]
+ else:
+ # (on Gedit startup 'tab-changed' comes before 'tab-added')
+ # remember: init=True crashes the plugin here!
+ decorator = self._create_tab_decorator(tab)
+
+ self._active_tab_decorator = decorator
+
+ # adjust actions and views
+ self.adjust(decorator)
+
+ def _create_tab_decorator(self, tab, init=False):
+ """
+ Create a new GeditTabDecorator for a GeditTab
+ """
+ decorator = GeditTabDecorator(self, tab, init)
+ self._tab_decorators[tab] = decorator
+ return decorator
diff --git a/latex/bibtex/__init__.py b/latex/bibtex/__init__.py
index ed57ce0..9de43e6 100644
--- a/latex/bibtex/__init__.py
+++ b/latex/bibtex/__init__.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
diff --git a/latex/bibtex/actions.py b/latex/bibtex/actions.py
index b456cfe..ba32e6e 100644
--- a/latex/bibtex/actions.py
+++ b/latex/bibtex/actions.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -30,29 +30,29 @@ from dialogs import InsertBibTeXEntryDialog
class BibTeXMenuAction(Action):
- extensions = [".bib"]
- label = "BibTeX"
- stock_id = None
- accelerator = None
- tooltip = None
-
- def activate(self, context):
- pass
+ extensions = [".bib"]
+ label = "BibTeX"
+ stock_id = None
+ accelerator = None
+ tooltip = None
+
+ def activate(self, context):
+ pass
class BibTeXNewEntryAction(Action):
- extensions = [".bib"]
- label = "New BibTeX Entry..."
- stock_id = None
- accelerator = None
- tooltip = "Create a new BibTeX entry"
-
- _dialog = None
-
- def activate(self, context):
- if not self._dialog:
- self._dialog = InsertBibTeXEntryDialog()
-
- source = self._dialog.run()
- if not source is None:
- context.active_editor.append(source)
\ No newline at end of file
+ extensions = [".bib"]
+ label = "New BibTeX Entry..."
+ stock_id = None
+ accelerator = None
+ tooltip = "Create a new BibTeX entry"
+
+ _dialog = None
+
+ def activate(self, context):
+ if not self._dialog:
+ self._dialog = InsertBibTeXEntryDialog()
+
+ source = self._dialog.run()
+ if not source is None:
+ context.active_editor.append(source)
\ No newline at end of file
diff --git a/latex/bibtex/cache.py b/latex/bibtex/cache.py
index 0b0e97d..635fc53 100644
--- a/latex/bibtex/cache.py
+++ b/latex/bibtex/cache.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -29,83 +29,82 @@ from ..issues import MockIssueHandler
class BibTeXDocumentCache(object):
- """
- This is used to cache BibTeX document models.
-
- This is only used in the context of code completion and validation for LaTeX documents
- but not when editing a BibTeX file.
-
- Of course, this must be implemented as a singleton class.
- """
-
- # TODO: serialize the cache on shutdown
-
- _log = getLogger("BibTeXDocumentCache")
-
- class Entry(object):
- """
- An entry in the cache
- """
- _log = getLogger("bibtex.cache.BibTeXDocumentCache.Entry")
-
- def __init__(self, file):
- self.__file = file
- self.__parser = BibTeXParser(quiet=True)
- self.__issue_handler = MockIssueHandler()
- self.__mtime = 0
- self.__document = None
-
- self.synchronize()
-
- @property
- def modified(self):
- return (self.__file.mtime > self.__mtime)
-
- @property
- def document(self):
- return self.__document
-
- def synchronize(self):
- """
- Synchronize document model with file contents.
-
- This may throw OSError
- """
- # update timestamp
- self.__mtime = self.__file.mtime
-
- # parse
- self.__document = self.__parser.parse(open(self.__file.path, "r").read(), self.__file, self.__issue_handler)
-
- def __new__(cls):
- if not '_instance' in cls.__dict__:
- cls._instance = object.__new__(cls)
- return cls._instance
-
- def __init__(self):
- if not '_ready' in dir(self):
- self._entries = {}
- self._ready = True
-
- def get_document(self, file):
- """
- Return the (hopefully) cached document model for a given file
-
- @param file: a File object
- """
- try:
- # update entry if necessary
- entry = self._entries[file.uri]
- if entry.modified:
- self._log.debug("File '%s' modified, synchronizing..." % file)
- entry.synchronize()
- except KeyError:
- self._log.debug("Cache fault for '%s'" % file)
- # create new entry
- entry = self.Entry(file)
- self._entries[file.uri] = entry
-
- return entry.document
-
-
-
\ No newline at end of file
+ """
+ This is used to cache BibTeX document models.
+
+ This is only used in the context of code completion and validation for LaTeX documents
+ but not when editing a BibTeX file.
+
+ Of course, this must be implemented as a singleton class.
+ """
+
+ # TODO: serialize the cache on shutdown
+
+ _log = getLogger("BibTeXDocumentCache")
+
+ class Entry(object):
+ """
+ An entry in the cache
+ """
+ _log = getLogger("bibtex.cache.BibTeXDocumentCache.Entry")
+
+ def __init__(self, file):
+ self.__file = file
+ self.__parser = BibTeXParser(quiet=True)
+ self.__issue_handler = MockIssueHandler()
+ self.__mtime = 0
+ self.__document = None
+
+ self.synchronize()
+
+ @property
+ def modified(self):
+ return (self.__file.mtime > self.__mtime)
+
+ @property
+ def document(self):
+ return self.__document
+
+ def synchronize(self):
+ """
+ Synchronize document model with file contents.
+
+ This may throw OSError
+ """
+ # update timestamp
+ self.__mtime = self.__file.mtime
+
+ # parse
+ self.__document = self.__parser.parse(open(self.__file.path, "r").read(), self.__file, self.__issue_handler)
+
+ def __new__(cls):
+ if not '_instance' in cls.__dict__:
+ cls._instance = object.__new__(cls)
+ return cls._instance
+
+ def __init__(self):
+ if not '_ready' in dir(self):
+ self._entries = {}
+ self._ready = True
+
+ def get_document(self, file):
+ """
+ Return the (hopefully) cached document model for a given file
+
+ @param file: a File object
+ """
+ try:
+ # update entry if necessary
+ entry = self._entries[file.uri]
+ if entry.modified:
+ self._log.debug("File '%s' modified, synchronizing..." % file)
+ entry.synchronize()
+ except KeyError:
+ self._log.debug("Cache fault for '%s'" % file)
+ # create new entry
+ entry = self.Entry(file)
+ self._entries[file.uri] = entry
+
+ return entry.document
+
+
diff --git a/latex/bibtex/completion.py b/latex/bibtex/completion.py
index 2e40470..e22ef01 100644
--- a/latex/bibtex/completion.py
+++ b/latex/bibtex/completion.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -34,93 +34,93 @@ from parser import BibTeXParser
class BibTeXEntryTypeProposal(Proposal):
- """
- """
- icon = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/document.png"))
-
- _color = Preferences().get("light-foreground-color")
-
- def __init__(self, overlap, type):
- """
- @param overlap: the number of overlapping characters
- @param type: an EntryType
- """
- self._overlap = overlap
- self._type = type
- self._details = None
- self._source = None
-
- def _generate(self):
- """
- Generate Template and details string
- """
- template = "@%s{${Identifier}" % self._type.name
- self._details = "@%s{<span color='%s'>Identifier</span>" % (self._type.name, self._color)
- for field in self._type.required_fields:
- template += ",\n\t%s = {${%s}}" % (field.name, field.label)
- self._details += ",\n\t%s = {<span color='%s'>%s</span>}" % (field.name, self._color, field.label)
- template += "\n}"
- self._details += "\n}"
- self._source = Template(template)
-
- @property
- def source(self):
- if not self._source:
- self._generate()
- return self._source
-
- @property
- def label(self):
- return self._type.name
-
- @property
- def details(self):
- if not self._details:
- self._generate()
- return self._details
-
- @property
- def overlap(self):
- return self._overlap
+ """
+ """
+ icon = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/document.png"))
+
+ _color = Preferences().get("light-foreground-color")
+
+ def __init__(self, overlap, type):
+ """
+ @param overlap: the number of overlapping characters
+ @param type: an EntryType
+ """
+ self._overlap = overlap
+ self._type = type
+ self._details = None
+ self._source = None
+
+ def _generate(self):
+ """
+ Generate Template and details string
+ """
+ template = "@%s{${Identifier}" % self._type.name
+ self._details = "@%s{<span color='%s'>Identifier</span>" % (self._type.name, self._color)
+ for field in self._type.required_fields:
+ template += ",\n\t%s = {${%s}}" % (field.name, field.label)
+ self._details += ",\n\t%s = {<span color='%s'>%s</span>}" % (field.name, self._color, field.label)
+ template += "\n}"
+ self._details += "\n}"
+ self._source = Template(template)
+
+ @property
+ def source(self):
+ if not self._source:
+ self._generate()
+ return self._source
+
+ @property
+ def label(self):
+ return self._type.name
+
+ @property
+ def details(self):
+ if not self._details:
+ self._generate()
+ return self._details
+
+ @property
+ def overlap(self):
+ return self._overlap
class BibTeXCompletionHandler(ICompletionHandler):
- """
- This implements the BibTeX-specific code completion
- """
- _log = getLogger("BibTeXCompletionHandler")
-
- trigger_keys = ["@"]
- prefix_delimiters = ["@"]
-
- def __init__(self):
- self._model = BibTeXModel()
- self._parser = BibTeXParser()
- self._issue_handler = MockIssueHandler()
-
- def complete(self, prefix):
- self._log.debug("complete: '%s'" % prefix)
-
- proposals = []
-
- if len(prefix) == 1:
- # propose all entry types
- types = self._model.types
- proposals = [BibTeXEntryTypeProposal(1, type) for type in types]
- else:
- if prefix[1:].isalpha():
- type_name_prefix = prefix[1:].lower()
- overlap = len(type_name_prefix) + 1
- # prefix is @[a-zA-Z]+
- types = [type for type in self._model.types if type.name.lower().startswith(type_name_prefix)]
- proposals = [BibTeXEntryTypeProposal(overlap, type) for type in types]
- else:
- # parse prefix
-# document = self._parser.parse(prefix, None, self._issue_handler)
-#
-# print document
- pass
-
- return proposals
-
-
+ """
+ This implements the BibTeX-specific code completion
+ """
+ _log = getLogger("BibTeXCompletionHandler")
+
+ trigger_keys = ["@"]
+ prefix_delimiters = ["@"]
+
+ def __init__(self):
+ self._model = BibTeXModel()
+ self._parser = BibTeXParser()
+ self._issue_handler = MockIssueHandler()
+
+ def complete(self, prefix):
+ self._log.debug("complete: '%s'" % prefix)
+
+ proposals = []
+
+ if len(prefix) == 1:
+ # propose all entry types
+ types = self._model.types
+ proposals = [BibTeXEntryTypeProposal(1, type) for type in types]
+ else:
+ if prefix[1:].isalpha():
+ type_name_prefix = prefix[1:].lower()
+ overlap = len(type_name_prefix) + 1
+ # prefix is @[a-zA-Z]+
+ types = [type for type in self._model.types if type.name.lower().startswith(type_name_prefix)]
+ proposals = [BibTeXEntryTypeProposal(overlap, type) for type in types]
+ else:
+ # parse prefix
+# document = self._parser.parse(prefix, None, self._issue_handler)
+#
+# print document
+ pass
+
+ return proposals
+
+
diff --git a/latex/bibtex/dialogs.py b/latex/bibtex/dialogs.py
index 7a4167e..68d09e4 100644
--- a/latex/bibtex/dialogs.py
+++ b/latex/bibtex/dialogs.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -31,162 +31,162 @@ from model import BibTeXModel
class InsertBibTeXEntryDialog(GladeInterface):
-
- filename = find_resource("ui/insert_bibtex_entry.ui")
- _dialog = None
-
- def run(self):
- dialog = self._getDialog()
-
- result = None
-
- if dialog.run() == 1:
-
- s = "@%s{%s,\n" % (self._activeType.name, self._entryIdent.get_text())
-
- # fields
- f = []
- for entry, field in self._mapping.iteritems():
- value = entry.get_text()
- if len(value):
- if field == "title":
- f.append("\t%s = {{%s}}" % (field, value))
- else:
- f.append("\t%s = {%s}" % (field, value))
- s += ",\n".join(f)
-
- # abstract
- #buf = self._view_abstract.get_buffer()
- #abs = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
- #if len(abs):
- # s += ",\n\tabstract = {%s}" % abs
-
- s += "\n}"
-
- result = s
-
- dialog.hide()
-
- return result
-
- def _getDialog(self):
- if not self._dialog:
- self._dialog = self.find_widget("dialogInsertBibtexEntry")
-
- # get BibTeX model
- self._model = BibTeXModel()
-
- # setup types combobox
-
- self._activeType = None
- self._mapping = {} # this maps Gtk.Entry objects to field names
- self._fieldCache = {} # this is used to restore field values after the type has changed
-
- self._storeTypes = Gtk.ListStore(str)
- for t in self._model.types:
- self._storeTypes.append([t.name])
-
- comboTypes = self.find_widget("comboTypes")
- comboTypes.set_model(self._storeTypes)
- cell = Gtk.CellRendererText()
- comboTypes.pack_start(cell, True)
- comboTypes.add_attribute(cell, "text", 0)
-
- self._boxRequired = self.find_widget("boxRequired")
- self._boxOptional = self.find_widget("boxOptional")
-
- self._entryIdent = self.find_widget("entryIdent")
-
- self._buttonOk = self.find_widget("buttonOk")
-
- self.connect_signals({ "on_comboTypes_changed" : self._comboTypesChanged,
- "on_entryIdent_changed" : self._identChanged })
-
- comboTypes.set_active(0)
-
- return self._dialog
-
- def _identChanged(self, entry):
- enable = bool(len(entry.get_text()))
- self._buttonOk.set_sensitive(enable)
-
- def _comboTypesChanged(self, combo):
- i = combo.get_active()
- self._activeType = self._model.types[i]
-
- # cache values
-
- for entry, fieldName in self._mapping.iteritems():
- text = entry.get_text()
- if len(text):
- self._fieldCache[fieldName] = entry.get_text()
-
- # reset mapping
-
- self._mapping = {}
-
- # required fields
-
- tbl_required = Gtk.Table()
- tbl_required.set_border_width(5)
- tbl_required.set_row_spacings(5)
- tbl_required.set_col_spacings(5)
- i = 0
-
- for field in self._activeType.required_fields:
- label = Gtk.Label(label=field.label + ":")
- label.set_alignment(0, .5)
-
- entry = Gtk.Entry()
-
- tbl_required.attach(label, 0, 1, i, i + 1, xoptions=Gtk.AttachOptions.FILL)
- tbl_required.attach(entry, 1, 2, i, i + 1)
-
- self._mapping[entry] = field.name
-
- # try to restore field value
- try:
- entry.set_text(self._fieldCache[field.name])
- except KeyError:
- pass
-
- i += 1
-
- for child in self._boxRequired.get_children():
- child.destroy()
-
- tbl_required.show_all()
- self._boxRequired.pack_start(tbl_required, False)
-
- # optional fields
-
- tbl_optional = Gtk.Table()
- tbl_optional.set_border_width(5)
- tbl_optional.set_row_spacings(5)
- tbl_optional.set_col_spacings(5)
- i = 0
-
- for field in self._activeType.optional_fields:
- label = Gtk.Label(label=field.label + ":")
- label.set_alignment(0, .5)
-
- entry = Gtk.Entry()
-
- tbl_optional.attach(label, 0, 1, i, i + 1, xoptions=Gtk.AttachOptions.FILL)
- tbl_optional.attach(entry, 1, 2, i, i + 1)
-
- self._mapping[entry] = field.name
-
- # try to restore field value
- try:
- entry.set_text(self._fieldCache[field.name])
- except KeyError:
- pass
-
- i += 1
-
- for child in self._boxOptional.get_children():
- child.destroy()
-
- tbl_optional.show_all()
- self._boxOptional.pack_start(tbl_optional, False)
+
+ filename = find_resource("ui/insert_bibtex_entry.ui")
+ _dialog = None
+
+ def run(self):
+ dialog = self._getDialog()
+
+ result = None
+
+ if dialog.run() == 1:
+
+ s = "@%s{%s,\n" % (self._activeType.name, self._entryIdent.get_text())
+
+ # fields
+ f = []
+ for entry, field in self._mapping.iteritems():
+ value = entry.get_text()
+ if len(value):
+ if field == "title":
+ f.append("\t%s = {{%s}}" % (field, value))
+ else:
+ f.append("\t%s = {%s}" % (field, value))
+ s += ",\n".join(f)
+
+ # abstract
+ #buf = self._view_abstract.get_buffer()
+ #abs = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
+ #if len(abs):
+ # s += ",\n\tabstract = {%s}" % abs
+
+ s += "\n}"
+
+ result = s
+
+ dialog.hide()
+
+ return result
+
+ def _getDialog(self):
+ if not self._dialog:
+ self._dialog = self.find_widget("dialogInsertBibtexEntry")
+
+ # get BibTeX model
+ self._model = BibTeXModel()
+
+ # setup types combobox
+
+ self._activeType = None
+ self._mapping = {} # this maps Gtk.Entry objects to field names
+ self._fieldCache = {} # this is used to restore field values after the type has changed
+
+ self._storeTypes = Gtk.ListStore(str)
+ for t in self._model.types:
+ self._storeTypes.append([t.name])
+
+ comboTypes = self.find_widget("comboTypes")
+ comboTypes.set_model(self._storeTypes)
+ cell = Gtk.CellRendererText()
+ comboTypes.pack_start(cell, True)
+ comboTypes.add_attribute(cell, "text", 0)
+
+ self._boxRequired = self.find_widget("boxRequired")
+ self._boxOptional = self.find_widget("boxOptional")
+
+ self._entryIdent = self.find_widget("entryIdent")
+
+ self._buttonOk = self.find_widget("buttonOk")
+
+ self.connect_signals({ "on_comboTypes_changed" : self._comboTypesChanged,
+ "on_entryIdent_changed" : self._identChanged })
+
+ comboTypes.set_active(0)
+
+ return self._dialog
+
+ def _identChanged(self, entry):
+ enable = bool(len(entry.get_text()))
+ self._buttonOk.set_sensitive(enable)
+
+ def _comboTypesChanged(self, combo):
+ i = combo.get_active()
+ self._activeType = self._model.types[i]
+
+ # cache values
+
+ for entry, fieldName in self._mapping.iteritems():
+ text = entry.get_text()
+ if len(text):
+ self._fieldCache[fieldName] = entry.get_text()
+
+ # reset mapping
+
+ self._mapping = {}
+
+ # required fields
+
+ tbl_required = Gtk.Table()
+ tbl_required.set_border_width(5)
+ tbl_required.set_row_spacings(5)
+ tbl_required.set_col_spacings(5)
+ i = 0
+
+ for field in self._activeType.required_fields:
+ label = Gtk.Label(label=field.label + ":")
+ label.set_alignment(0, .5)
+
+ entry = Gtk.Entry()
+
+ tbl_required.attach(label, 0, 1, i, i + 1, xoptions=Gtk.AttachOptions.FILL)
+ tbl_required.attach(entry, 1, 2, i, i + 1)
+
+ self._mapping[entry] = field.name
+
+ # try to restore field value
+ try:
+ entry.set_text(self._fieldCache[field.name])
+ except KeyError:
+ pass
+
+ i += 1
+
+ for child in self._boxRequired.get_children():
+ child.destroy()
+
+ tbl_required.show_all()
+ self._boxRequired.pack_start(tbl_required, False)
+
+ # optional fields
+
+ tbl_optional = Gtk.Table()
+ tbl_optional.set_border_width(5)
+ tbl_optional.set_row_spacings(5)
+ tbl_optional.set_col_spacings(5)
+ i = 0
+
+ for field in self._activeType.optional_fields:
+ label = Gtk.Label(label=field.label + ":")
+ label.set_alignment(0, .5)
+
+ entry = Gtk.Entry()
+
+ tbl_optional.attach(label, 0, 1, i, i + 1, xoptions=Gtk.AttachOptions.FILL)
+ tbl_optional.attach(entry, 1, 2, i, i + 1)
+
+ self._mapping[entry] = field.name
+
+ # try to restore field value
+ try:
+ entry.set_text(self._fieldCache[field.name])
+ except KeyError:
+ pass
+
+ i += 1
+
+ for child in self._boxOptional.get_children():
+ child.destroy()
+
+ tbl_optional.show_all()
+ self._boxOptional.pack_start(tbl_optional, False)
diff --git a/latex/bibtex/editor.py b/latex/bibtex/editor.py
index ed42430..07d3f2e 100644
--- a/latex/bibtex/editor.py
+++ b/latex/bibtex/editor.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -39,173 +39,173 @@ from validator import BibTeXValidator
BENCHMARK = True
if BENCHMARK:
- import time
+ import time
class ParseJob(Job):
- def _run(self, arguments):
- file = arguments[0]
- content = arguments[1]
- self._parser = BibTeXParser()
- return self._parser.parse(content, file, MockIssueHandler())
+ def _run(self, arguments):
+ file = arguments[0]
+ content = arguments[1]
+ self._parser = BibTeXParser()
+ return self._parser.parse(content, file, MockIssueHandler())
class BibTeXEditor(Editor, IIssueHandler, JobChangeListener):
-
- _log = getLogger("BibTeXEditor")
- extensions = [".bib"]
-
- @property
- def completion_handlers(self):
- self.__bibtex_completion_handler = BibTeXCompletionHandler()
- return [ self.__bibtex_completion_handler ]
-
- def __init__(self, tab_decorator, file):
- Editor.__init__(self, tab_decorator, file)
- self._parse_job = None
-
- def init(self, file, context):
- self._log.debug("init(%s)" % file)
-
- self._preferences = Preferences()
-
- self._file = file
- self._context = context
-
- self.register_marker_type("bibtex-error", self._preferences.get("error-background-color"))
- self.register_marker_type("bibtex-warning", self._preferences.get("warning-background-color"))
-
- self._issue_view = context.find_view(self, "IssueView")
- self._parser = BibTeXParser()
- self._validator = BibTeXValidator()
- self._outline_view = context.find_view(self, "BibTeXOutlineView")
-
- self._parse_job = ParseJob()
- self._parse_job.set_change_listener(self)
-
-
- # initially parse
- self.__parse()
-
- def on_save(self):
- """
- The file has been saved
-
- Update models
- """
- self.__parse()
-
-
-# def _on_state_changed(self, state):
-# #
-# # job.JobChangeListener._on_state_changed
-# #
-# if (state == JobManager.STATE_COMPLETED):
-# self._log.debug("Parser finished")
-#
-# self._document = self._parse_job.get_returned()
-#
-# #self._log.debug(str(self._document))
-#
-# for entry in self._document.entries:
-# print entry.key
-#
-# # FIXME: gedit crashes here with:
-# # gedit: Fatal IO error 11 (Resource temporarily unavailable) on X server :0.0.
-#
-# self._log.debug("Validating...")
-# self._validator.validate(self._document, self._file, self)
-# self._log.debug("Validating finished")
-#
-# self._outline_view.set_outline(self._document)
-#
-#
-# def __parse_async(self):
-#
-# # reset highlight
-# self.remove_markers("bibtex-error")
-# self.remove_markers("bibtex-warning")
-#
-# # reset issues
-# self._issue_view.clear()
-#
-# self._log.debug("Starting parser job")
-#
-# self._parse_job.set_argument([self._file, self.content])
-# self._parse_job.schedule()
-
-
- @verbose
- def __parse(self):
- """
- """
- self._log.debug("__parse")
-
- content = self.content
-
- # reset highlight
- self.remove_markers("bibtex-error")
- self.remove_markers("bibtex-warning")
-
- # reset issues
- self._issue_view.clear()
-
-# self.parse(self._file)
-
- if BENCHMARK: t = time.clock()
-
- # parse document
- self._document = self._parser.parse(content, self._file, self)
-
- if BENCHMARK: self._log.info("BibTeXParser.parse: %f" % (time.clock() - t))
-
- self._log.debug("Parsed %s bytes of content" % len(content))
-
- # validate
- if BENCHMARK: t = time.clock()
-
- self._validator.validate(self._document, self._file, self)
-
- # 0.11
- if BENCHMARK: self._log.info("BibTeXValidator.validate: %f" % (time.clock() - t))
-
- self._outline_view.set_outline(self._document)
-
-# def _on_parser_finished(self, model):
-# """
-# """
-# self._document = model
-# self._outline_view.set_outline(self._document)
-
- def issue(self, issue):
- # overriding IIssueHandler.issue
-
- self._issue_view.append_issue(issue)
-
- if issue.file == self._file:
- if issue.severity == Issue.SEVERITY_ERROR:
- self.create_marker("bibtex-error", issue.start, issue.end)
- elif issue.severity == Issue.SEVERITY_WARNING:
- self.create_marker("bibtex-warning", issue.start, issue.end)
-
- def on_cursor_moved(self, offset):
- """
- The cursor has moved
- """
- if self._preferences.get_bool("outline-connect-to-editor"):
- self._outline_view.select_path_by_offset(offset)
-
- def destroy(self):
- # unreference the window context
- del self._context
-
- # remove parse listener
- if self._parse_job != None:
- self._parse_job.set_change_listener(None)
-
- Editor.destroy(self)
-
- def __del__(self):
- self._log.debug("Properly destroyed %s" % self)
-
+
+ _log = getLogger("BibTeXEditor")
+ extensions = [".bib"]
+
+ @property
+ def completion_handlers(self):
+ self.__bibtex_completion_handler = BibTeXCompletionHandler()
+ return [ self.__bibtex_completion_handler ]
+
+ def __init__(self, tab_decorator, file):
+ Editor.__init__(self, tab_decorator, file)
+ self._parse_job = None
+
+ def init(self, file, context):
+ self._log.debug("init(%s)" % file)
+
+ self._preferences = Preferences()
+
+ self._file = file
+ self._context = context
+
+ self.register_marker_type("bibtex-error", self._preferences.get("error-background-color"))
+ self.register_marker_type("bibtex-warning", self._preferences.get("warning-background-color"))
+
+ self._issue_view = context.find_view(self, "IssueView")
+ self._parser = BibTeXParser()
+ self._validator = BibTeXValidator()
+ self._outline_view = context.find_view(self, "BibTeXOutlineView")
+
+ self._parse_job = ParseJob()
+ self._parse_job.set_change_listener(self)
+
+
+ # initially parse
+ self.__parse()
+
+ def on_save(self):
+ """
+ The file has been saved
+
+ Update models
+ """
+ self.__parse()
+
+
+# def _on_state_changed(self, state):
+# #
+# # job.JobChangeListener._on_state_changed
+# #
+# if (state == JobManager.STATE_COMPLETED):
+# self._log.debug("Parser finished")
+#
+# self._document = self._parse_job.get_returned()
+#
+# #self._log.debug(str(self._document))
+#
+# for entry in self._document.entries:
+# print entry.key
+#
+# # FIXME: gedit crashes here with:
+# # gedit: Fatal IO error 11 (Resource temporarily unavailable) on X server :0.0.
+#
+# self._log.debug("Validating...")
+# self._validator.validate(self._document, self._file, self)
+# self._log.debug("Validating finished")
+#
+# self._outline_view.set_outline(self._document)
+#
+#
+# def __parse_async(self):
+#
+# # reset highlight
+# self.remove_markers("bibtex-error")
+# self.remove_markers("bibtex-warning")
+#
+# # reset issues
+# self._issue_view.clear()
+#
+# self._log.debug("Starting parser job")
+#
+# self._parse_job.set_argument([self._file, self.content])
+# self._parse_job.schedule()
+
+
+ @verbose
+ def __parse(self):
+ """
+ """
+ self._log.debug("__parse")
+
+ content = self.content
+
+ # reset highlight
+ self.remove_markers("bibtex-error")
+ self.remove_markers("bibtex-warning")
+
+ # reset issues
+ self._issue_view.clear()
+
+# self.parse(self._file)
+
+ if BENCHMARK: t = time.clock()
+
+ # parse document
+ self._document = self._parser.parse(content, self._file, self)
+
+ if BENCHMARK: self._log.info("BibTeXParser.parse: %f" % (time.clock() - t))
+
+ self._log.debug("Parsed %s bytes of content" % len(content))
+
+ # validate
+ if BENCHMARK: t = time.clock()
+
+ self._validator.validate(self._document, self._file, self)
+
+ # 0.11
+ if BENCHMARK: self._log.info("BibTeXValidator.validate: %f" % (time.clock() - t))
+
+ self._outline_view.set_outline(self._document)
+
+# def _on_parser_finished(self, model):
+# """
+# """
+# self._document = model
+# self._outline_view.set_outline(self._document)
+
+ def issue(self, issue):
+ # overriding IIssueHandler.issue
+
+ self._issue_view.append_issue(issue)
+
+ if issue.file == self._file:
+ if issue.severity == Issue.SEVERITY_ERROR:
+ self.create_marker("bibtex-error", issue.start, issue.end)
+ elif issue.severity == Issue.SEVERITY_WARNING:
+ self.create_marker("bibtex-warning", issue.start, issue.end)
+
+ def on_cursor_moved(self, offset):
+ """
+ The cursor has moved
+ """
+ if self._preferences.get_bool("outline-connect-to-editor"):
+ self._outline_view.select_path_by_offset(offset)
+
+ def destroy(self):
+ # unreference the window context
+ del self._context
+
+ # remove parse listener
+ if self._parse_job != None:
+ self._parse_job.set_change_listener(None)
+
+ Editor.destroy(self)
+
+ def __del__(self):
+ self._log.debug("Properly destroyed %s" % self)
+
diff --git a/latex/bibtex/model.py b/latex/bibtex/model.py
index f849e3a..ea016b8 100644
--- a/latex/bibtex/model.py
+++ b/latex/bibtex/model.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -29,79 +29,78 @@ from ..base.resources import find_resource
class Type(object):
- def __init__(self, name):
- self.name = name
- self.required_fields = []
- self.optional_fields = []
-
- def __cmp__(self, other):
- # FIXME: there must be something simpler...
- if self.name < other.name:
- return -1
- elif self.name > other.name:
- return 1
- else:
- return 0
-
- #return self.name.__cmp__(other.name) str has no __cmp__
+ def __init__(self, name):
+ self.name = name
+ self.required_fields = []
+ self.optional_fields = []
+
+ def __cmp__(self, other):
+ # FIXME: there must be something simpler...
+ if self.name < other.name:
+ return -1
+ elif self.name > other.name:
+ return 1
+ else:
+ return 0
+
+ #return self.name.__cmp__(other.name) str has no __cmp__
class Field(object):
- def __init__(self, name, label):
- self.name = name
- self.label = label
+ def __init__(self, name, label):
+ self.name = name
+ self.label = label
class BibTeXModel(object):
- def __new__(cls):
- if not '_instance' in cls.__dict__:
- cls._instance = object.__new__(cls)
- return cls._instance
-
- def __init__(self):
- if not '_ready' in dir(self):
- # init object
- self._fields = {}
- self._types = {}
-
- # parse bibtex.xml
- self._bibtex = ElementTree.parse(find_resource("bibtex.xml")).getroot()
-
- for field_e in self._bibtex.findall("fields/field"):
- id = field_e.get("id")
- self._fields[id] = Field(id, field_e.get("_label"))
-
- for type_e in self._bibtex.findall("types/type"):
- id = type_e.get("id")
- type = Type(id)
-
- for required_field_e in type_e.findall("required/field"):
- field_id = required_field_e.get("id")
- type.required_fields.append(self._fields[field_id])
-
- for optional_field_e in type_e.findall("optional/field"):
- field_id = optional_field_e.get("id")
- type.optional_fields.append(self._fields[field_id])
-
- self._types[id.lower()] = type
-
- self._types_list = None
- self._ready = True
-
- def find_type(self, name):
- """
- Find an entry type by its name
- """
- return self._types[name.lower()]
-
- @property
- def types(self):
- """
- List all entry types in sorted order
- """
- if self._types_list is None:
- self._types_list = self._types.values()
- self._types_list.sort()
- return self._types_list
-
-
\ No newline at end of file
+ def __new__(cls):
+ if not '_instance' in cls.__dict__:
+ cls._instance = object.__new__(cls)
+ return cls._instance
+
+ def __init__(self):
+ if not '_ready' in dir(self):
+ # init object
+ self._fields = {}
+ self._types = {}
+
+ # parse bibtex.xml
+ self._bibtex = ElementTree.parse(find_resource("bibtex.xml")).getroot()
+
+ for field_e in self._bibtex.findall("fields/field"):
+ id = field_e.get("id")
+ self._fields[id] = Field(id, field_e.get("_label"))
+
+ for type_e in self._bibtex.findall("types/type"):
+ id = type_e.get("id")
+ type = Type(id)
+
+ for required_field_e in type_e.findall("required/field"):
+ field_id = required_field_e.get("id")
+ type.required_fields.append(self._fields[field_id])
+
+ for optional_field_e in type_e.findall("optional/field"):
+ field_id = optional_field_e.get("id")
+ type.optional_fields.append(self._fields[field_id])
+
+ self._types[id.lower()] = type
+
+ self._types_list = None
+ self._ready = True
+
+ def find_type(self, name):
+ """
+ Find an entry type by its name
+ """
+ return self._types[name.lower()]
+
+ @property
+ def types(self):
+ """
+ List all entry types in sorted order
+ """
+ if self._types_list is None:
+ self._types_list = self._types.values()
+ self._types_list.sort()
+ return self._types_list
+
diff --git a/latex/bibtex/parser.py b/latex/bibtex/parser.py
index 673d0e0..2bdbe27 100644
--- a/latex/bibtex/parser.py
+++ b/latex/bibtex/parser.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -29,31 +29,31 @@ BibTeX parser and object model
## async parser feature
##
#if __name__ == "__main__":
-# #
-# # The script has been started in a shell process
-# #
-# # Parse the file passed as first argument and write the model
-# # as a pickled object to STDOUT
-# #
-# import sys
-#
-# # TODO: fetch issues and pass them together with the model in a special
-# # transfer object
-#
-# plugin_path = sys.argv[1]
-# filename = sys.argv[2]
-#
-# sys.path.append(plugin_path)
-# sys.path.append("/home/michael/.gnome2/gedit/plugins")
-#
-# from issues import MockIssueHandler
-# from base import File
-#
-# model = BibTeXParser().parse_async(open(filename).read(), filename)
+# #
+# # The script has been started in a shell process
+# #
+# # Parse the file passed as first argument and write the model
+# # as a pickled object to STDOUT
+# #
+# import sys
+#
+# # TODO: fetch issues and pass them together with the model in a special
+# # transfer object
+#
+# plugin_path = sys.argv[1]
+# filename = sys.argv[2]
+#
+# sys.path.append(plugin_path)
+# sys.path.append("/home/michael/.gnome2/gedit/plugins")
+#
+# from issues import MockIssueHandler
+# from base import File
+#
+# model = BibTeXParser().parse_async(open(filename).read(), filename)
#else:
-# #
-# # normal package code...
-# #
+# #
+# # normal package code...
+# #
from logging import getLogger
@@ -64,26 +64,26 @@ from ..issues import Issue, MockIssueHandler
from ..preferences import Preferences
-# import sys
-# import os
-#
-# print "======== sys.path=%s, cwd=%s" % (sys.path, os.getcwd())
+# import sys
+# import os
+#
+# print "======== sys.path=%s, cwd=%s" % (sys.path, os.getcwd())
class Token(object):
- """
- A BibTeX token
- """
-
- AT, TEXT, COMMA, EQUALS, QUOTE, HASH, CURLY_OPEN, CURLY_CLOSE, ROUND_OPEN, ROUND_CLOSE = range(10)
-
- def __init__(self, type, offset, value):
- self.type = type
- self.offset = offset
- self.value = value
-
- def __str__(self):
- return "<Token type='%s' value='%s' @%s>" % (self.type, self.value, self.offset)
+ """
+ A BibTeX token
+ """
+
+ AT, TEXT, COMMA, EQUALS, QUOTE, HASH, CURLY_OPEN, CURLY_CLOSE, ROUND_OPEN, ROUND_CLOSE = range(10)
+
+ def __init__(self, type, offset, value):
+ self.type = type
+ self.offset = offset
+ self.value = value
+
+ def __str__(self):
+ return "<Token type='%s' value='%s' @%s>" % (self.type, self.value, self.offset)
from ..util import StringReader
@@ -91,512 +91,512 @@ from ..util import open_info
class BibTeXLexer(object):
- """
- BibTeX lexer. We only separate text from special tokens here and
- apply escaping.
- """
-
- _TERMINALS_TOKENS = {"@" : Token.AT, "," : Token.COMMA, "=" : Token.EQUALS,
- "{" : Token.CURLY_OPEN, "}" : Token.CURLY_CLOSE, "\"" : Token.QUOTE,
- "#" : Token.HASH, "(" : Token.ROUND_OPEN, ")" : Token.ROUND_CLOSE}
-
- _TERMINALS = set(_TERMINALS_TOKENS.keys())
-
- def __init__(self, string):
- self._reader = StringReader(string)
-
- def __iter__(self):
- return self
-
- def next(self):
- """
- Return the next token
- """
-
- escaping = False
- textBuilder = None
- textStart = None
-
- while True:
- c = self._reader.read()
-
- if not escaping and c in self._TERMINALS:
- if textBuilder is not None:
- self._reader.unread(c)
- text = "".join(textBuilder)
- textBuilder = None
- return Token(Token.TEXT, textStart, text)
-
- return Token(self._TERMINALS_TOKENS[c], self._reader.offset - 1, c)
-
- else:
- if textBuilder is None:
- textStart = self._reader.offset
- textBuilder = [c]
- else:
- textBuilder.append(c)
-
- if c == "\\":
- escaping = True
- else:
- escaping = False
+ """
+ BibTeX lexer. We only separate text from special tokens here and
+ apply escaping.
+ """
+
+ _TERMINALS_TOKENS = {"@" : Token.AT, "," : Token.COMMA, "=" : Token.EQUALS,
+ "{" : Token.CURLY_OPEN, "}" : Token.CURLY_CLOSE, "\"" : Token.QUOTE,
+ "#" : Token.HASH, "(" : Token.ROUND_OPEN, ")" : Token.ROUND_CLOSE}
+
+ _TERMINALS = set(_TERMINALS_TOKENS.keys())
+
+ def __init__(self, string):
+ self._reader = StringReader(string)
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ """
+ Return the next token
+ """
+
+ escaping = False
+ textBuilder = None
+ textStart = None
+
+ while True:
+ c = self._reader.read()
+
+ if not escaping and c in self._TERMINALS:
+ if textBuilder is not None:
+ self._reader.unread(c)
+ text = "".join(textBuilder)
+ textBuilder = None
+ return Token(Token.TEXT, textStart, text)
+
+ return Token(self._TERMINALS_TOKENS[c], self._reader.offset - 1, c)
+
+ else:
+ if textBuilder is None:
+ textStart = self._reader.offset
+ textBuilder = [c]
+ else:
+ textBuilder.append(c)
+
+ if c == "\\":
+ escaping = True
+ else:
+ escaping = False
class BibTeXParser(object):
- """
- A fast and safe BibTeX parser that generates a handy model on the fly.
-
- Instead of raising exceptions this parser uses an IIssueHandler.
- """
-
- _OUTSIDE, _TYPE, _AFTER_TYPE, _AFTER_STRING_TYPE, _KEY, _STRING_KEY, _AFTER_KEY, _AFTER_STRING_KEY, \
- _STRING_VALUE, _QUOTED_STRING_VALUE, _FIELD_NAME, _AFTER_FIELD_NAME, _FIELD_VALUE, _EMBRACED_FIELD_VALUE, \
- _QUOTED_FIELD_VALUE = range(15)
-
- def __init__(self, quiet=False):
- self._quiet = quiet
- self._max_size_info_shown = False
-
- self._state = None
- self._type = None
- self._constant = None
- self._entry = None
- self._file = None
- self._closingDelimiter = None
- self._document = None
- self._field = None
- self._value = None
- self._stack = None
-
- #
- # callables for each state of the parser
- #
-
- def _on_outside(self, token, file, issue_handler):
- if token.type == Token.AT:
- self._state = self._TYPE
-
- def _on_type(self, token, file, issue_handler):
- if token.type == Token.TEXT:
- self._type = token.value.strip()
-
- if self._type.lower() == "string" :
- self._constant = Constant()
- self._state = self._AFTER_STRING_TYPE
- elif self._type.lower() in ["preamble", "comment"]: # simply skip PREAMBLE and COMMENT entries
- self._state = self._OUTSIDE
- else:
- self._entry = Entry()
- self._entry.type = self._type
- self._entry.start = token.offset - 2
- self._state = self._AFTER_TYPE
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> in entry type" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._entry = None
- self._state = self._OUTSIDE
-
- def _on_after_type(self, token, file, issue_handler):
- if token.type == Token.CURLY_OPEN:
- self._closingDelimiter = Token.CURLY_CLOSE
- self._state = self._KEY
- elif token.type == Token.ROUND_OPEN:
- self._closingDelimiter = Token.ROUND_CLOSE
- self._state = self._KEY
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> after entry type" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._entry = None
- self._state = self._OUTSIDE
-
- def _on_after_string_type(self, token, file, issue_handler):
- if token.type == Token.CURLY_OPEN:
- self._closingDelimiter = Token.CURLY_CLOSE
- self._state = self._STRING_KEY
- elif token.type == Token.ROUND_OPEN:
- self._closingDelimiter = Token.ROUND_CLOSE
- self._state = self._STRING_KEY
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> after string type" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._constant = None
- self._state = self._OUTSIDE
-
- def _on_key(self, token, file, issue_handler):
- if token.type == Token.TEXT:
- self._entry.key = token.value.strip()
- self._state = self._AFTER_KEY
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> in entry key" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._entry = None
- self._state = self._OUTSIDE
-
- def _on_string_key(self, token, file, issue_handler):
- if token.type == Token.TEXT:
- self._constant.name = token.value.strip()
- self._state = self._AFTER_STRING_KEY
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> in string key" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._constant = None
- self._state = self._OUTSIDE
-
- def _on_after_key(self, token, file, issue_handler):
- if token.type == Token.COMMA:
- self._state = self._FIELD_NAME
- elif token.type == self._closingDelimiter:
- self._entry.end = token.offset + 1
- self._document.entries.append(self._entry)
- self._state = self._OUTSIDE
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> after entry key" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._entry = None
- self._state = self._OUTSIDE
-
- def _on_after_string_key(self, token, file, issue_handler):
- if token.type == Token.EQUALS:
- self._state = self._STRING_VALUE
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> after string key" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._constant = None
- self._state = self._OUTSIDE
-
- def _on_string_value(self, token, file, issue_handler):
- if token.type == Token.QUOTE:
- self._state = self._QUOTED_STRING_VALUE
-
- def _on_quoted_string_value(self, token):
- if token.type == Token.TEXT:
- self._constant.value = token.value
- self._document.constants.append(self._constant)
- self._state = self._OUTSIDE
-
- def _on_field_name(self, token, file, issue_handler):
- if token.type == Token.TEXT:
-
- if token.value.isspace():
- return
-
- self._field = Field()
- self._field.name = token.value.strip()
- self._state = self._AFTER_FIELD_NAME
- elif token.type == self._closingDelimiter:
- self._entry.end = token.offset + 1
- self._document.entries.append(self._entry)
- self._state = self._OUTSIDE
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> in field name" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._entry = None
- self._state = self._OUTSIDE
-
- def _on_after_field_name(self, token, file, issue_handler):
- if token.type == Token.EQUALS:
- self._state = self._FIELD_VALUE
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> after field name" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._entry = None
- self._state = self._OUTSIDE
-
- def _on_field_value(self, token, file, issue_handler):
- # TODO: we may not recognize something like "author = ," as an error
-
- if token.value.isspace():
- return
-
- if token.type == Token.TEXT:
- self._value = token.value.strip()
- if self._value.isdigit():
- self._field.value.append(NumberValue(self._value))
- else:
- self._field.value.append(ConstantReferenceValue(self._value))
- elif token.type == Token.CURLY_OPEN:
- self._value = ""
- self._stack = [Token.CURLY_OPEN]
- self._state = self._EMBRACED_FIELD_VALUE
- elif token.type == Token.QUOTE:
- self._value = ""
- #stack = [Token.QUOTE]
- self._state = self._QUOTED_FIELD_VALUE
- elif token.type == Token.COMMA:
- self._entry.fields.append(self._field)
- self._state = self._FIELD_NAME
- elif token.type == self._closingDelimiter:
- self._entry.fields.append(self._field)
- self._entry.end = token.offset + 1
- self._document.entries.append(self._entry)
- self._state = self._OUTSIDE
- elif token.type == Token.HASH:
- pass
- else:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> in field value" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._entry = None
- self._state = self._OUTSIDE
-
- def _on_embraced_field_value(self, token, file, issue_handler):
- if token.type == Token.CURLY_OPEN:
- self._stack.append(Token.CURLY_OPEN)
- self._value += token.value
- elif token.type == Token.CURLY_CLOSE:
- try:
- while self._stack[-1] != Token.CURLY_OPEN:
- self._stack.pop()
- self._stack.pop()
-
- if len(self._stack) == 0:
- self._field.value.append(StringValue(self._value))
- self._state = self._FIELD_VALUE
- else:
- self._value += token.value
-
- except IndexError:
- issue_handler.issue(Issue("Unexpected token <b>%s</b> in field value" % escape(token.value),
- token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
- self._entry = None
- self._state = self._OUTSIDE
- else:
- self._value += token.value
-
- def _on_quoted_field_value(self, token, file, issue_handler):
- if token.type == Token.QUOTE:
- self._field.value.append(StringValue(self._value))
- self._state = self._FIELD_VALUE
- else:
- self._value += token.value
-
- def parse(self, string, file, issue_handler):
- """
- Parse a BibTeX content
- @param string: the content to be parsed
- @param file: the File object containing the BibTeX
- @param issue_handler: an object implementing IIssueHandler
- """
- self._document = Document()
-
- # respect maximum BibTeX file size
- max_size_kb = int(Preferences().get("maximum-bibtex-size"))
- length = len(string)
-
- if length > max_size_kb * 1024:
- if not self._quiet and not self._max_size_info_shown:
- open_info("BibTeX file will not be parsed", "The maximum size of BibTeX files to parse is set to %s KB." % max_size_kb)
- self._max_size_info_shown = True
- return self._document
-
- # parse
- self._state = self._OUTSIDE
-
- #
- # use this hash table instead of endless if...elif statements
- #
- callables = {
- self._OUTSIDE : self._on_outside,
- self._TYPE : self._on_type,
- self._AFTER_TYPE : self._on_after_type,
- self._AFTER_STRING_TYPE : self._on_after_string_type,
- self._KEY : self._on_key,
- self._STRING_KEY : self._on_string_key,
- self._AFTER_KEY : self._on_after_key,
- self._AFTER_STRING_KEY : self._on_after_string_key,
- self._STRING_VALUE : self._on_string_value,
- self._QUOTED_STRING_VALUE : self._on_quoted_string_value,
- self._FIELD_NAME : self._on_field_name,
- self._AFTER_FIELD_NAME : self._on_after_field_name,
- self._FIELD_VALUE : self._on_field_value,
- self._EMBRACED_FIELD_VALUE : self._on_embraced_field_value,
- self._QUOTED_FIELD_VALUE : self._on_quoted_field_value
- }
-
- for token in BibTeXLexer(string):
- callables[self._state].__call__(token, file, issue_handler)
-
- return self._document
-
- #~ def __del__(self):
- #~ print "properly destroyed %s" % self
+ """
+ A fast and safe BibTeX parser that generates a handy model on the fly.
+
+ Instead of raising exceptions this parser uses an IIssueHandler.
+ """
+
+ _OUTSIDE, _TYPE, _AFTER_TYPE, _AFTER_STRING_TYPE, _KEY, _STRING_KEY, _AFTER_KEY, _AFTER_STRING_KEY, \
+ _STRING_VALUE, _QUOTED_STRING_VALUE, _FIELD_NAME, _AFTER_FIELD_NAME, _FIELD_VALUE, _EMBRACED_FIELD_VALUE, \
+ _QUOTED_FIELD_VALUE = range(15)
+
+ def __init__(self, quiet=False):
+ self._quiet = quiet
+ self._max_size_info_shown = False
+
+ self._state = None
+ self._type = None
+ self._constant = None
+ self._entry = None
+ self._file = None
+ self._closingDelimiter = None
+ self._document = None
+ self._field = None
+ self._value = None
+ self._stack = None
+
+ #
+ # callables for each state of the parser
+ #
+
+ def _on_outside(self, token, file, issue_handler):
+ if token.type == Token.AT:
+ self._state = self._TYPE
+
+ def _on_type(self, token, file, issue_handler):
+ if token.type == Token.TEXT:
+ self._type = token.value.strip()
+
+ if self._type.lower() == "string" :
+ self._constant = Constant()
+ self._state = self._AFTER_STRING_TYPE
+ elif self._type.lower() in ["preamble", "comment"]: # simply skip PREAMBLE and COMMENT entries
+ self._state = self._OUTSIDE
+ else:
+ self._entry = Entry()
+ self._entry.type = self._type
+ self._entry.start = token.offset - 2
+ self._state = self._AFTER_TYPE
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> in entry type" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._entry = None
+ self._state = self._OUTSIDE
+
+ def _on_after_type(self, token, file, issue_handler):
+ if token.type == Token.CURLY_OPEN:
+ self._closingDelimiter = Token.CURLY_CLOSE
+ self._state = self._KEY
+ elif token.type == Token.ROUND_OPEN:
+ self._closingDelimiter = Token.ROUND_CLOSE
+ self._state = self._KEY
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> after entry type" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._entry = None
+ self._state = self._OUTSIDE
+
+ def _on_after_string_type(self, token, file, issue_handler):
+ if token.type == Token.CURLY_OPEN:
+ self._closingDelimiter = Token.CURLY_CLOSE
+ self._state = self._STRING_KEY
+ elif token.type == Token.ROUND_OPEN:
+ self._closingDelimiter = Token.ROUND_CLOSE
+ self._state = self._STRING_KEY
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> after string type" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._constant = None
+ self._state = self._OUTSIDE
+
+ def _on_key(self, token, file, issue_handler):
+ if token.type == Token.TEXT:
+ self._entry.key = token.value.strip()
+ self._state = self._AFTER_KEY
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> in entry key" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._entry = None
+ self._state = self._OUTSIDE
+
+ def _on_string_key(self, token, file, issue_handler):
+ if token.type == Token.TEXT:
+ self._constant.name = token.value.strip()
+ self._state = self._AFTER_STRING_KEY
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> in string key" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._constant = None
+ self._state = self._OUTSIDE
+
+ def _on_after_key(self, token, file, issue_handler):
+ if token.type == Token.COMMA:
+ self._state = self._FIELD_NAME
+ elif token.type == self._closingDelimiter:
+ self._entry.end = token.offset + 1
+ self._document.entries.append(self._entry)
+ self._state = self._OUTSIDE
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> after entry key" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._entry = None
+ self._state = self._OUTSIDE
+
+ def _on_after_string_key(self, token, file, issue_handler):
+ if token.type == Token.EQUALS:
+ self._state = self._STRING_VALUE
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> after string key" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._constant = None
+ self._state = self._OUTSIDE
+
+ def _on_string_value(self, token, file, issue_handler):
+ if token.type == Token.QUOTE:
+ self._state = self._QUOTED_STRING_VALUE
+
+ def _on_quoted_string_value(self, token):
+ if token.type == Token.TEXT:
+ self._constant.value = token.value
+ self._document.constants.append(self._constant)
+ self._state = self._OUTSIDE
+
+ def _on_field_name(self, token, file, issue_handler):
+ if token.type == Token.TEXT:
+
+ if token.value.isspace():
+ return
+
+ self._field = Field()
+ self._field.name = token.value.strip()
+ self._state = self._AFTER_FIELD_NAME
+ elif token.type == self._closingDelimiter:
+ self._entry.end = token.offset + 1
+ self._document.entries.append(self._entry)
+ self._state = self._OUTSIDE
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> in field name" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._entry = None
+ self._state = self._OUTSIDE
+
+ def _on_after_field_name(self, token, file, issue_handler):
+ if token.type == Token.EQUALS:
+ self._state = self._FIELD_VALUE
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> after field name" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._entry = None
+ self._state = self._OUTSIDE
+
+ def _on_field_value(self, token, file, issue_handler):
+ # TODO: we may not recognize something like "author = ," as an error
+
+ if token.value.isspace():
+ return
+
+ if token.type == Token.TEXT:
+ self._value = token.value.strip()
+ if self._value.isdigit():
+ self._field.value.append(NumberValue(self._value))
+ else:
+ self._field.value.append(ConstantReferenceValue(self._value))
+ elif token.type == Token.CURLY_OPEN:
+ self._value = ""
+ self._stack = [Token.CURLY_OPEN]
+ self._state = self._EMBRACED_FIELD_VALUE
+ elif token.type == Token.QUOTE:
+ self._value = ""
+ #stack = [Token.QUOTE]
+ self._state = self._QUOTED_FIELD_VALUE
+ elif token.type == Token.COMMA:
+ self._entry.fields.append(self._field)
+ self._state = self._FIELD_NAME
+ elif token.type == self._closingDelimiter:
+ self._entry.fields.append(self._field)
+ self._entry.end = token.offset + 1
+ self._document.entries.append(self._entry)
+ self._state = self._OUTSIDE
+ elif token.type == Token.HASH:
+ pass
+ else:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> in field value" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._entry = None
+ self._state = self._OUTSIDE
+
+ def _on_embraced_field_value(self, token, file, issue_handler):
+ if token.type == Token.CURLY_OPEN:
+ self._stack.append(Token.CURLY_OPEN)
+ self._value += token.value
+ elif token.type == Token.CURLY_CLOSE:
+ try:
+ while self._stack[-1] != Token.CURLY_OPEN:
+ self._stack.pop()
+ self._stack.pop()
+
+ if len(self._stack) == 0:
+ self._field.value.append(StringValue(self._value))
+ self._state = self._FIELD_VALUE
+ else:
+ self._value += token.value
+
+ except IndexError:
+ issue_handler.issue(Issue("Unexpected token <b>%s</b> in field value" % escape(token.value),
+ token.offset, token.offset + 1, file, Issue.SEVERITY_ERROR))
+ self._entry = None
+ self._state = self._OUTSIDE
+ else:
+ self._value += token.value
+
+ def _on_quoted_field_value(self, token, file, issue_handler):
+ if token.type == Token.QUOTE:
+ self._field.value.append(StringValue(self._value))
+ self._state = self._FIELD_VALUE
+ else:
+ self._value += token.value
+
+ def parse(self, string, file, issue_handler):
+ """
+ Parse a BibTeX content
+ @param string: the content to be parsed
+ @param file: the File object containing the BibTeX
+ @param issue_handler: an object implementing IIssueHandler
+ """
+ self._document = Document()
+
+ # respect maximum BibTeX file size
+ max_size_kb = int(Preferences().get("maximum-bibtex-size"))
+ length = len(string)
+
+ if length > max_size_kb * 1024:
+ if not self._quiet and not self._max_size_info_shown:
+ open_info("BibTeX file will not be parsed", "The maximum size of BibTeX files to parse is set to %s KB." % max_size_kb)
+ self._max_size_info_shown = True
+ return self._document
+
+ # parse
+ self._state = self._OUTSIDE
+
+ #
+ # use this hash table instead of endless if...elif statements
+ #
+ callables = {
+ self._OUTSIDE : self._on_outside,
+ self._TYPE : self._on_type,
+ self._AFTER_TYPE : self._on_after_type,
+ self._AFTER_STRING_TYPE : self._on_after_string_type,
+ self._KEY : self._on_key,
+ self._STRING_KEY : self._on_string_key,
+ self._AFTER_KEY : self._on_after_key,
+ self._AFTER_STRING_KEY : self._on_after_string_key,
+ self._STRING_VALUE : self._on_string_value,
+ self._QUOTED_STRING_VALUE : self._on_quoted_string_value,
+ self._FIELD_NAME : self._on_field_name,
+ self._AFTER_FIELD_NAME : self._on_after_field_name,
+ self._FIELD_VALUE : self._on_field_value,
+ self._EMBRACED_FIELD_VALUE : self._on_embraced_field_value,
+ self._QUOTED_FIELD_VALUE : self._on_quoted_field_value
+ }
+
+ for token in BibTeXLexer(string):
+ callables[self._state].__call__(token, file, issue_handler)
+
+ return self._document
+
+ #~ def __del__(self):
+ #~ print "properly destroyed %s" % self
#
# BibTeX object model
#
class Value(object):
- def __init__(self, text):
- self.text = text
-
- MAX_MARKUP_LENGTH = 50
-
- @property
- def markup(self):
- text = self.text
-
- # remove braces
- if text.startswith("{{") and text.endswith("}}"):
- text = text[2:-2]
- elif text.startswith("{") and text.endswith("}"):
- text = text[1:-1]
- elif text.startswith("\\url{") and text.endswith("}"):
- text = text[5:-1]
-
- # truncate
- if len(text) > self.MAX_MARKUP_LENGTH:
- text = text[:self.MAX_MARKUP_LENGTH] + "..."
-
- # remove newlines
- text = text.replace("\n", "")
-
- # escape problematic characters
- text = escape(text)
-
- return text
-
- def __str__(self):
- return "<Value text='%s'>" % self.text
+ def __init__(self, text):
+ self.text = text
+
+ MAX_MARKUP_LENGTH = 50
+
+ @property
+ def markup(self):
+ text = self.text
+
+ # remove braces
+ if text.startswith("{{") and text.endswith("}}"):
+ text = text[2:-2]
+ elif text.startswith("{") and text.endswith("}"):
+ text = text[1:-1]
+ elif text.startswith("\\url{") and text.endswith("}"):
+ text = text[5:-1]
+
+ # truncate
+ if len(text) > self.MAX_MARKUP_LENGTH:
+ text = text[:self.MAX_MARKUP_LENGTH] + "..."
+
+ # remove newlines
+ text = text.replace("\n", "")
+
+ # escape problematic characters
+ text = escape(text)
+
+ return text
+
+ def __str__(self):
+ return "<Value text='%s'>" % self.text
class NumberValue(Value):
- def __str__(self):
- return "<NumberValue text='%s'>" % self.text
+ def __str__(self):
+ return "<NumberValue text='%s'>" % self.text
class StringValue(Value):
- def __str__(self):
- return "<StringValue text='%s'>" % self.text
+ def __str__(self):
+ return "<StringValue text='%s'>" % self.text
class ConstantReferenceValue(Value):
- @property
- def markup(self):
- return "<i>%s</i>" % escape(self.text)
-
- def __str__(self):
- return "<ReferenceValue text='%s'>" % self.text
+ @property
+ def markup(self):
+ return "<i>%s</i>" % escape(self.text)
+
+ def __str__(self):
+ return "<ReferenceValue text='%s'>" % self.text
class Field(object):
- def __init__(self):
- self.name = None
- self.value = []
-
- @property
- def valueMarkup(self):
- return " ".join([v.markup for v in self.value])
-
- @property
- def valueString(self):
- return " ".join([v.text for v in self.value])
-
- def __str__(self):
- return "<Field name='%s' value='%s' />" % (self.name, self.valueString)
+ def __init__(self):
+ self.name = None
+ self.value = []
+
+ @property
+ def valueMarkup(self):
+ return " ".join([v.markup for v in self.value])
+
+ @property
+ def valueString(self):
+ return " ".join([v.text for v in self.value])
+
+ def __str__(self):
+ return "<Field name='%s' value='%s' />" % (self.name, self.valueString)
class Entry(object):
- def __init__(self):
- self.type = None
- self.key = None
- self.start = None
- self.end = None
- self.fields = []
-
- def findField(self, name):
- for field in self.fields:
- if field.name == name:
- return field
- raise KeyError
-
- def __str__(self):
- s = "<Entry type='%s' key='%s'>\n" % (self.type, self.key)
- for field in self.fields:
- s += "\t" + str(field) + "\n"
- s += "</Entry>"
- return s
+ def __init__(self):
+ self.type = None
+ self.key = None
+ self.start = None
+ self.end = None
+ self.fields = []
+
+ def findField(self, name):
+ for field in self.fields:
+ if field.name == name:
+ return field
+ raise KeyError
+
+ def __str__(self):
+ s = "<Entry type='%s' key='%s'>\n" % (self.type, self.key)
+ for field in self.fields:
+ s += "\t" + str(field) + "\n"
+ s += "</Entry>"
+ return s
class Constant(object):
- """
- A BibTeX string constant
- """
- def __init__(self):
- self.name = None
- self.value = None
+ """
+ A BibTeX string constant
+ """
+ def __init__(self):
+ self.name = None
+ self.value = None
class Document(object):
- def __init__(self):
- self.entries = []
- self.constants = []
-
- def __str__(self):
- s = "<Document>\n"
- for entry in self.entries:
- s += str(entry) + "\n"
- s += "</Document>"
- return s
-
-
-# #
-# # async parser feature
-# #
-#
-# # TODO: put the __main__ part in another file
-#
-# import pickle
-# import os
-#
-# from ..tools.util import Process
-# from ..base.resources import PLUGIN_PATH
-#
-# # TODO: time pickle.loads() and pickle.dump()
-# # TODO: support Process.abort()
-#
-# class AsyncParserRunner(Process):
-#
-# __log = getLogger("AsyncParserRunner")
-#
-# def parse(self, file):
-# self.__pickled_object = None
-#
-# source_path = PLUGIN_PATH + "/src"
-# self.__log.debug("chdir: %s" % source_path)
-# os.chdir(source_path)
-#
-# self.execute("python %s/bibtex/parser.py %s %s" % (source_path, source_path, file.path))
-#
-# def _on_stdout(self, text):
-# # Process._on_stdout
-# self.__pickled_object = text
-#
-# def _on_stderr(self, text):
-# # Process._on_stderr
-# self.__log.debug("_on_stderr: %s" % text)
-#
-# def _on_abort(self):
-# # Process._on_abort
-# pass
-#
-# def _on_exit(self, condition):
-# # Process._on_exit
-# self.__log.debug("_on_exit")
-#
-# model = None
-#
-# if condition:
-# self.__log.error("failed")
-# else:
-# model = pickle.loads(self.__pickled_object)
-#
-# self._on_parser_finished(model)
-#
-# def _on_parser_finished(self, model):
-# """
-# To be overridden by the subclass
-# """
-
-
-
-
-
+ def __init__(self):
+ self.entries = []
+ self.constants = []
+
+ def __str__(self):
+ s = "<Document>\n"
+ for entry in self.entries:
+ s += str(entry) + "\n"
+ s += "</Document>"
+ return s
+
+
+# #
+# # async parser feature
+# #
+#
+# # TODO: put the __main__ part in another file
+#
+# import pickle
+# import os
+#
+# from ..tools.util import Process
+# from ..base.resources import PLUGIN_PATH
+#
+# # TODO: time pickle.loads() and pickle.dump()
+# # TODO: support Process.abort()
+#
+# class AsyncParserRunner(Process):
+#
+# __log = getLogger("AsyncParserRunner")
+#
+# def parse(self, file):
+# self.__pickled_object = None
+#
+# source_path = PLUGIN_PATH + "/src"
+# self.__log.debug("chdir: %s" % source_path)
+# os.chdir(source_path)
+#
+# self.execute("python %s/bibtex/parser.py %s %s" % (source_path, source_path, file.path))
+#
+# def _on_stdout(self, text):
+# # Process._on_stdout
+# self.__pickled_object = text
+#
+# def _on_stderr(self, text):
+# # Process._on_stderr
+# self.__log.debug("_on_stderr: %s" % text)
+#
+# def _on_abort(self):
+# # Process._on_abort
+# pass
+#
+# def _on_exit(self, condition):
+# # Process._on_exit
+# self.__log.debug("_on_exit")
+#
+# model = None
+#
+# if condition:
+# self.__log.error("failed")
+# else:
+# model = pickle.loads(self.__pickled_object)
+#
+# self._on_parser_finished(model)
+#
+# def _on_parser_finished(self, model):
+# """
+# To be overridden by the subclass
+# """
+
+
+
+
+
diff --git a/latex/bibtex/validator.py b/latex/bibtex/validator.py
index bcbe87e..cb69552 100644
--- a/latex/bibtex/validator.py
+++ b/latex/bibtex/validator.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -29,61 +29,60 @@ from model import BibTeXModel
class BibTeXValidator:
- """
- This checks for
- - duplicate entry names
- - duplicate fields
- - missing required fields *
- - unused fields *
-
- *) relies on the BibTeX model definition
- """
-
- _log = getLogger("BibTeXValidator")
-
- def __init__(self):
- self._model = BibTeXModel()
-
- def validate(self, document, file, issue_handler):
- """
- @param document: a bibtex.parser.Document object
- @param issue_handler: an object implementing IIssueHandler
- """
- entry_keys = []
- for entry in document.entries:
- # check for duplicate keys
- if entry.key in entry_keys:
- issue_handler.issue(Issue("Duplicate key <b>%s</b>" % entry.key, entry.start, entry.end, file, Issue.SEVERITY_ERROR))
- else:
- entry_keys.append(entry.key)
-
- field_names = []
- for field in entry.fields:
- # check for duplicate fields
- if field.name in field_names:
- issue_handler.issue(Issue("Duplicate field <b>%s</b>" % field.name, entry.start, entry.end, file, Issue.SEVERITY_ERROR))
- else:
- field_names.append(field.name)
-
- try:
- # check for missing required fields
- required_field_names = set(map(lambda f: f.name, self._model.find_type(entry.type).required_fields))
- missing_field_names = required_field_names.difference(set(field_names))
- if len(missing_field_names) > 0:
- issue_handler.issue(Issue("Possibly missing field(s): <b>%s</b>" % ",".join(missing_field_names), entry.start, entry.end, file, Issue.SEVERITY_WARNING))
-
- # check for unused fields
- optional_field_names = set(map(lambda f: f.name, self._model.find_type(entry.type).optional_fields))
- unused_field_names = set(field_names).difference(optional_field_names.union(required_field_names))
- if len(unused_field_names) > 0:
- issue_handler.issue(Issue("Possibly unused field(s): <b>%s</b>" % ",".join(unused_field_names), entry.start, entry.end, file, Issue.SEVERITY_WARNING))
- except KeyError:
- #self._log.debug("Type not found: %s" % entry.type)
- pass
-
-
-
-
-
-
-
\ No newline at end of file
+ """
+ This checks for
+ - duplicate entry names
+ - duplicate fields
+ - missing required fields *
+ - unused fields *
+
+ *) relies on the BibTeX model definition
+ """
+
+ _log = getLogger("BibTeXValidator")
+
+ def __init__(self):
+ self._model = BibTeXModel()
+
+ def validate(self, document, file, issue_handler):
+ """
+ @param document: a bibtex.parser.Document object
+ @param issue_handler: an object implementing IIssueHandler
+ """
+ entry_keys = []
+ for entry in document.entries:
+ # check for duplicate keys
+ if entry.key in entry_keys:
+ issue_handler.issue(Issue("Duplicate key <b>%s</b>" % entry.key, entry.start, entry.end, file, Issue.SEVERITY_ERROR))
+ else:
+ entry_keys.append(entry.key)
+
+ field_names = []
+ for field in entry.fields:
+ # check for duplicate fields
+ if field.name in field_names:
+ issue_handler.issue(Issue("Duplicate field <b>%s</b>" % field.name, entry.start, entry.end, file, Issue.SEVERITY_ERROR))
+ else:
+ field_names.append(field.name)
+
+ try:
+ # check for missing required fields
+ required_field_names = set(map(lambda f: f.name, self._model.find_type(entry.type).required_fields))
+ missing_field_names = required_field_names.difference(set(field_names))
+ if len(missing_field_names) > 0:
+ issue_handler.issue(Issue("Possibly missing field(s): <b>%s</b>" % ",".join(missing_field_names), entry.start, entry.end, file, Issue.SEVERITY_WARNING))
+
+ # check for unused fields
+ optional_field_names = set(map(lambda f: f.name, self._model.find_type(entry.type).optional_fields))
+ unused_field_names = set(field_names).difference(optional_field_names.union(required_field_names))
+ if len(unused_field_names) > 0:
+ issue_handler.issue(Issue("Possibly unused field(s): <b>%s</b>" % ",".join(unused_field_names), entry.start, entry.end, file, Issue.SEVERITY_WARNING))
+ except KeyError:
+ #self._log.debug("Type not found: %s" % entry.type)
+ pass
+
+
+
+
+
+
diff --git a/latex/bibtex/views.py b/latex/bibtex/views.py
index e0d256b..ee5e538 100644
--- a/latex/bibtex/views.py
+++ b/latex/bibtex/views.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -41,256 +41,256 @@ GROUP_NONE, GROUP_TYPE, GROUP_AUTHOR, GROUP_YEAR = 1, 2, 3, 4
class BibTeXOutlineView(BaseOutlineView):
- """
- Special outline view for BibTeX
- """
-
- _log = getLogger("BibTeXOutlineView")
-
- def __init__(self, context, editor):
- BaseOutlineView.__init__(self, context, editor)
- self._handlers = {}
-
- def init(self, context):
- BaseOutlineView.init(self, context)
-
- self._grouping = GROUP_NONE
-
- # add grouping controls to toolbar
-
- self._item_none = Gtk.RadioMenuItem(None, "No Grouping")
- self._item_type = Gtk.RadioMenuItem(self._item_none, "Group by Type")
- self._item_author = Gtk.RadioMenuItem(self._item_none, "Group by Author")
- self._item_year = Gtk.RadioMenuItem(self._item_none, "Group by Year")
-
- self._preferences = Preferences()
-
- grouping = self._preferences.get("BibtexOutlineGrouping", GROUP_NONE)
- if grouping == GROUP_NONE:
- self._item_none.set_active(True)
- elif grouping == GROUP_TYPE:
- self._item_type.set_active(True)
- elif grouping == GROUP_AUTHOR:
- self._item_author.set_active(True)
- elif grouping == GROUP_YEAR:
- self._item_year.set_active(True)
-
- self._handlers[self._item_none] = self._item_none.connect("toggled", self._on_grouping_toggled)
- self._handlers[self._item_type] = self._item_type.connect("toggled", self._on_grouping_toggled)
- self._handlers[self._item_author] = self._item_author.connect("toggled", self._on_grouping_toggled)
- self._handlers[self._item_year] = self._item_year.connect("toggled", self._on_grouping_toggled)
-
- menu = Gtk.Menu()
- menu.add(self._item_none)
- menu.add(self._item_type)
- menu.add(self._item_author)
- menu.add(self._item_year)
- menu.show_all()
-
- tool_button = Gtk.MenuToolButton(Gtk.STOCK_SORT_DESCENDING)
- tool_button.set_menu(menu)
- tool_button.show()
-
- self._toolbar.insert(tool_button, -1)
-
- def _on_grouping_toggled(self, toggle_button):
- if self._item_none.get_active():
- self._grouping = GROUP_NONE
- elif self._item_type.get_active():
- self._grouping = GROUP_TYPE
- elif self._item_author.get_active():
- self._grouping = GROUP_AUTHOR
- elif self._item_year.get_active():
- self._grouping = GROUP_YEAR
-
- if self._outline:
- self._update()
-
- def _update(self):
- #t = time.clock()
- self._offset_map = OutlineOffsetMap()
- OutlineConverter().convert(self._store, self._outline, self._offset_map, self._grouping)
- #dt = time.clock() - t
- #self._log.debug("OutlineConverter.convert: %fs" % dt)
-
- def set_outline(self, outline):
- """
- Display the given model
- """
- self._outline = outline
-
- self.assure_init()
- self._save_state()
- self._update()
- self._restore_state()
-
- def _on_node_selected(self, node):
- """
- An outline node has been selected
- """
- if isinstance(node, Entry):
- self._editor.select(node.start, node.end)
-
- def destroy(self):
- for obj in self._handlers:
- obj.disconnect(self._handlers[obj])
- BaseOutlineView.destroy(self)
+ """
+ Special outline view for BibTeX
+ """
+
+ _log = getLogger("BibTeXOutlineView")
+
+ def __init__(self, context, editor):
+ BaseOutlineView.__init__(self, context, editor)
+ self._handlers = {}
+
+ def init(self, context):
+ BaseOutlineView.init(self, context)
+
+ self._grouping = GROUP_NONE
+
+ # add grouping controls to toolbar
+
+ self._item_none = Gtk.RadioMenuItem(None, "No Grouping")
+ self._item_type = Gtk.RadioMenuItem(self._item_none, "Group by Type")
+ self._item_author = Gtk.RadioMenuItem(self._item_none, "Group by Author")
+ self._item_year = Gtk.RadioMenuItem(self._item_none, "Group by Year")
+
+ self._preferences = Preferences()
+
+ grouping = self._preferences.get("BibtexOutlineGrouping", GROUP_NONE)
+ if grouping == GROUP_NONE:
+ self._item_none.set_active(True)
+ elif grouping == GROUP_TYPE:
+ self._item_type.set_active(True)
+ elif grouping == GROUP_AUTHOR:
+ self._item_author.set_active(True)
+ elif grouping == GROUP_YEAR:
+ self._item_year.set_active(True)
+
+ self._handlers[self._item_none] = self._item_none.connect("toggled", self._on_grouping_toggled)
+ self._handlers[self._item_type] = self._item_type.connect("toggled", self._on_grouping_toggled)
+ self._handlers[self._item_author] = self._item_author.connect("toggled", self._on_grouping_toggled)
+ self._handlers[self._item_year] = self._item_year.connect("toggled", self._on_grouping_toggled)
+
+ menu = Gtk.Menu()
+ menu.add(self._item_none)
+ menu.add(self._item_type)
+ menu.add(self._item_author)
+ menu.add(self._item_year)
+ menu.show_all()
+
+ tool_button = Gtk.MenuToolButton(Gtk.STOCK_SORT_DESCENDING)
+ tool_button.set_menu(menu)
+ tool_button.show()
+
+ self._toolbar.insert(tool_button, -1)
+
+ def _on_grouping_toggled(self, toggle_button):
+ if self._item_none.get_active():
+ self._grouping = GROUP_NONE
+ elif self._item_type.get_active():
+ self._grouping = GROUP_TYPE
+ elif self._item_author.get_active():
+ self._grouping = GROUP_AUTHOR
+ elif self._item_year.get_active():
+ self._grouping = GROUP_YEAR
+
+ if self._outline:
+ self._update()
+
+ def _update(self):
+ #t = time.clock()
+ self._offset_map = OutlineOffsetMap()
+ OutlineConverter().convert(self._store, self._outline, self._offset_map, self._grouping)
+ #dt = time.clock() - t
+ #self._log.debug("OutlineConverter.convert: %fs" % dt)
+
+ def set_outline(self, outline):
+ """
+ Display the given model
+ """
+ self._outline = outline
+
+ self.assure_init()
+ self._save_state()
+ self._update()
+ self._restore_state()
+
+ def _on_node_selected(self, node):
+ """
+ An outline node has been selected
+ """
+ if isinstance(node, Entry):
+ self._editor.select(node.start, node.end)
+
+ def destroy(self):
+ for obj in self._handlers:
+ obj.disconnect(self._handlers[obj])
+ BaseOutlineView.destroy(self)
class OutlineConverter(object):
- """
- This converts a BibTeX document to a Gtk.TreeStore and realizes the
- grouping feature
- """
-
- _ICON_ENTRY = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/document.png"))
- _ICON_FIELD = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/field.png"))
- _ICON_AUTHOR = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/users.png"))
- _ICON_YEAR = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/calendar.png"))
- _ICON_TYPE = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/documents.png"))
-
- def convert(self, tree_store, document, offset_map, grouping=GROUP_NONE):
- """
- Convert a BibTeX document model into a Gtk.TreeStore
-
- @param tree_store: the Gtk.TreeStore to fill
- @param document: the BibTeX document model (bibtex.parser.Document object)
- @param offset_map: the OutlineOffsetMap object to be filled
- @param grouping: the grouping to use: GROUP_NONE|GROUP_TYPE|GROUP_AUTHOR|GROUP_YEAR
- """
-
- color = Preferences().get("light-foreground-color")
-
- tree_store.clear()
-
- if grouping == GROUP_TYPE:
- # group by entry type
-
- groups = {} # maps lower case entry type names to lists of entries
-
- # collect
- for entry in document.entries:
- try:
- entryList = groups[entry.type]
- entryList.append(entry)
- except KeyError:
- groups[entry.type] = [entry]
-
- # sort by type
- entryTypes = groups.keys()
- entryTypes.sort()
-
- # build tree
- for entryType in entryTypes:
- entries = groups[entryType]
-
- parentType = tree_store.append(None, ["%s <span color='%s'>%s</span>" % (escape(entryType), color, len(entries)), self._ICON_TYPE, None])
-
- for entry in entries:
- parentEntry = tree_store.append(parentType, [escape(entry.key), self._ICON_ENTRY, entry])
-
- offset_map.put(entry.start, tree_store.get_path(parentEntry))
-
- for field in entry.fields:
- tree_store.append(parentEntry, ["<span color='%s'>%s</span> %s" % (color, escape(field.name), field.valueMarkup),
- self._ICON_FIELD, field])
-
- elif grouping == GROUP_YEAR:
- # group by year
-
- NO_YEAR_IDENT = "<i>n/a</i>"
-
- groups = {}
-
- # collect
- for entry in document.entries:
- try:
- year = str(entry.findField("year").valueString)
- except KeyError:
- # no year, so put this in an extra group
- year = NO_YEAR_IDENT
-
- try:
- entries = groups[year]
- entries.append(entry)
- except KeyError:
- groups[year] = [entry]
-
- # sort by year
- years = groups.keys()
- years.sort()
-
- # build tree
- for year in years:
- entries = groups[year]
-
- parentYear = tree_store.append(None, ["%s <span color='%s'>%s</span>" % (year, color, len(entries)), self._ICON_YEAR, None])
-
- for entry in entries:
- parentEntry = tree_store.append(parentYear, ["%s <span color='%s'>%s</span>" % (escape(entry.key), color, escape(entry.type)),
- self._ICON_ENTRY, entry])
-
- offset_map.put(entry.start, tree_store.get_path(parentEntry))
-
- for field in entry.fields:
- tree_store.append(parentEntry, ["<span color='%s'>%s</span> %s" % (color, escape(field.name), field.valueMarkup),
- self._ICON_FIELD, field])
-
- elif grouping == GROUP_AUTHOR:
-
- NA_IDENT = "Unknown Author"
-
- groups = {}
-
- # group
- for entry in document.entries:
- # split list of authors
- try:
- authorValue = str(entry.findField("author").valueString)
- authors = [a.strip() for a in authorValue.split(" and ")]
- except KeyError:
- # no year, so put this in an extra group
- authors = [NA_IDENT]
-
- # add to group(s)
- for author in authors:
- try:
- entries = groups[author]
- entries.append(entry)
- except KeyError:
- groups[author] = [entry]
-
- # sort
- authors = groups.keys()
- authors.sort()
-
- # build tree
- for author in authors:
- entries = groups[author]
-
- parent = tree_store.append(None, ["%s <span color='%s'>%s</span>" % (escape(author), color, len(entries)), self._ICON_AUTHOR, None])
-
- for entry in entries:
- parentEntry = tree_store.append(parent, ["%s <span color='%s'>%s</span>" % (escape(entry.key), color, escape(entry.type)),
- self._ICON_ENTRY, entry])
-
- offset_map.put(entry.start, tree_store.get_path(parentEntry))
-
- for field in entry.fields:
- tree_store.append(parentEntry, ["<span color='%s'>%s</span> %s" % (color, escape(field.name), field.valueMarkup),
- self._ICON_FIELD, field])
-
- else:
- # no grouping, display entries and fields in a tree
-
- for entry in document.entries:
- parent = tree_store.append(None, ["%s <span color='%s'>%s</span>" % (escape(entry.key), color, escape(entry.type)), self._ICON_ENTRY, entry])
-
- offset_map.put(entry.start, tree_store.get_path(parent))
-
- for field in entry.fields:
- tree_store.append(parent, ["<span color='%s'>%s</span> %s" % (color, escape(field.name), field.valueMarkup), self._ICON_FIELD, field])
-
-
-
-
+ """
+ This converts a BibTeX document to a Gtk.TreeStore and realizes the
+ grouping feature
+ """
+
+ _ICON_ENTRY = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/document.png"))
+ _ICON_FIELD = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/field.png"))
+ _ICON_AUTHOR = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/users.png"))
+ _ICON_YEAR = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/calendar.png"))
+ _ICON_TYPE = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/documents.png"))
+
+ def convert(self, tree_store, document, offset_map, grouping=GROUP_NONE):
+ """
+ Convert a BibTeX document model into a Gtk.TreeStore
+
+ @param tree_store: the Gtk.TreeStore to fill
+ @param document: the BibTeX document model (bibtex.parser.Document object)
+ @param offset_map: the OutlineOffsetMap object to be filled
+ @param grouping: the grouping to use: GROUP_NONE|GROUP_TYPE|GROUP_AUTHOR|GROUP_YEAR
+ """
+
+ color = Preferences().get("light-foreground-color")
+
+ tree_store.clear()
+
+ if grouping == GROUP_TYPE:
+ # group by entry type
+
+ groups = {} # maps lower case entry type names to lists of entries
+
+ # collect
+ for entry in document.entries:
+ try:
+ entryList = groups[entry.type]
+ entryList.append(entry)
+ except KeyError:
+ groups[entry.type] = [entry]
+
+ # sort by type
+ entryTypes = groups.keys()
+ entryTypes.sort()
+
+ # build tree
+ for entryType in entryTypes:
+ entries = groups[entryType]
+
+ parentType = tree_store.append(None, ["%s <span color='%s'>%s</span>" % (escape(entryType), color, len(entries)), self._ICON_TYPE, None])
+
+ for entry in entries:
+ parentEntry = tree_store.append(parentType, [escape(entry.key), self._ICON_ENTRY, entry])
+
+ offset_map.put(entry.start, tree_store.get_path(parentEntry))
+
+ for field in entry.fields:
+ tree_store.append(parentEntry, ["<span color='%s'>%s</span> %s" % (color, escape(field.name), field.valueMarkup),
+ self._ICON_FIELD, field])
+
+ elif grouping == GROUP_YEAR:
+ # group by year
+
+ NO_YEAR_IDENT = "<i>n/a</i>"
+
+ groups = {}
+
+ # collect
+ for entry in document.entries:
+ try:
+ year = str(entry.findField("year").valueString)
+ except KeyError:
+ # no year, so put this in an extra group
+ year = NO_YEAR_IDENT
+
+ try:
+ entries = groups[year]
+ entries.append(entry)
+ except KeyError:
+ groups[year] = [entry]
+
+ # sort by year
+ years = groups.keys()
+ years.sort()
+
+ # build tree
+ for year in years:
+ entries = groups[year]
+
+ parentYear = tree_store.append(None, ["%s <span color='%s'>%s</span>" % (year, color, len(entries)), self._ICON_YEAR, None])
+
+ for entry in entries:
+ parentEntry = tree_store.append(parentYear, ["%s <span color='%s'>%s</span>" % (escape(entry.key), color, escape(entry.type)),
+ self._ICON_ENTRY, entry])
+
+ offset_map.put(entry.start, tree_store.get_path(parentEntry))
+
+ for field in entry.fields:
+ tree_store.append(parentEntry, ["<span color='%s'>%s</span> %s" % (color, escape(field.name), field.valueMarkup),
+ self._ICON_FIELD, field])
+
+ elif grouping == GROUP_AUTHOR:
+
+ NA_IDENT = "Unknown Author"
+
+ groups = {}
+
+ # group
+ for entry in document.entries:
+ # split list of authors
+ try:
+ authorValue = str(entry.findField("author").valueString)
+ authors = [a.strip() for a in authorValue.split(" and ")]
+ except KeyError:
+ # no year, so put this in an extra group
+ authors = [NA_IDENT]
+
+ # add to group(s)
+ for author in authors:
+ try:
+ entries = groups[author]
+ entries.append(entry)
+ except KeyError:
+ groups[author] = [entry]
+
+ # sort
+ authors = groups.keys()
+ authors.sort()
+
+ # build tree
+ for author in authors:
+ entries = groups[author]
+
+ parent = tree_store.append(None, ["%s <span color='%s'>%s</span>" % (escape(author), color, len(entries)), self._ICON_AUTHOR, None])
+
+ for entry in entries:
+ parentEntry = tree_store.append(parent, ["%s <span color='%s'>%s</span>" % (escape(entry.key), color, escape(entry.type)),
+ self._ICON_ENTRY, entry])
+
+ offset_map.put(entry.start, tree_store.get_path(parentEntry))
+
+ for field in entry.fields:
+ tree_store.append(parentEntry, ["<span color='%s'>%s</span> %s" % (color, escape(field.name), field.valueMarkup),
+ self._ICON_FIELD, field])
+
+ else:
+ # no grouping, display entries and fields in a tree
+
+ for entry in document.entries:
+ parent = tree_store.append(None, ["%s <span color='%s'>%s</span>" % (escape(entry.key), color, escape(entry.type)), self._ICON_ENTRY, entry])
+
+ offset_map.put(entry.start, tree_store.get_path(parent))
+
+ for field in entry.fields:
+ tree_store.append(parent, ["<span color='%s'>%s</span> %s" % (color, escape(field.name), field.valueMarkup), self._ICON_FIELD, field])
+
+
+
+
diff --git a/latex/issues.py b/latex/issues.py
index 378e2c7..fe7f54f 100644
--- a/latex/issues.py
+++ b/latex/issues.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -26,128 +26,127 @@ from logging import getLogger
class IIssueHandler(object):
- """
- A class implementing this interface handles issues
- """
-
- def clear(self):
- """
- Remove all partitions and issues
- """
- raise NotImplementedError
-
- def issue(self, issue):
- """
- An issue has occured
- """
- raise NotImplementedError
-
-
+ """
+ A class implementing this interface handles issues
+ """
+
+ def clear(self):
+ """
+ Remove all partitions and issues
+ """
+ raise NotImplementedError
+
+ def issue(self, issue):
+ """
+ An issue has occured
+ """
+ raise NotImplementedError
+
+
class MockIssueHandler(IIssueHandler):
- """
- This is used by the BibTeXDocumentCache
- """
- __log = getLogger("MockIssueHandler")
-
- def clear(self):
- pass
-
- def issue(self, issue):
- self.__log.debug(str(issue))
+ """
+ This is used by the BibTeXDocumentCache
+ """
+ __log = getLogger("MockIssueHandler")
+
+ def clear(self):
+ pass
+
+ def issue(self, issue):
+ self.__log.debug(str(issue))
class IStructuredIssueHandler(object):
- def clear(self):
- """
- Remove all partitions and issues
- """
- raise NotImplementedError
-
- def add_partition(self, label, state, parent_partition_id):
- """
- Add a new partition
-
- @param label: a label used in the UI
- @param state: the initial state descriptor for the partition
- @param parent_partition_id: the partition under which this one should be
- created (None for top-level)
-
- @return: a unique id for the partition
- """
- raise NotImplementedError
-
- def set_partition_state(self, partition_id, state):
- """
- @param partition_id: a partition id as returned by add_partition
- @param state: any string
- """
- raise NotImplementedError
-
- def set_abort_enabled(self, enabled, method):
- """
- @param enabled: if True a job may be aborted
- @param method: a method that is may be called to abort a running job
- """
- raise NotImplementedError
-
- def append_issues(self, partition_id, issues):
- """
- An issue occured
-
- @param issue: an Issue object
- @param partition: a partition id as returned by add_partition
- """
- raise NotImplementedError
-
+ def clear(self):
+ """
+ Remove all partitions and issues
+ """
+ raise NotImplementedError
+
+ def add_partition(self, label, state, parent_partition_id):
+ """
+ Add a new partition
+
+ @param label: a label used in the UI
+ @param state: the initial state descriptor for the partition
+ @param parent_partition_id: the partition under which this one should be
+ created (None for top-level)
+
+ @return: a unique id for the partition
+ """
+ raise NotImplementedError
+
+ def set_partition_state(self, partition_id, state):
+ """
+ @param partition_id: a partition id as returned by add_partition
+ @param state: any string
+ """
+ raise NotImplementedError
+
+ def set_abort_enabled(self, enabled, method):
+ """
+ @param enabled: if True a job may be aborted
+ @param method: a method that is may be called to abort a running job
+ """
+ raise NotImplementedError
+
+ def append_issues(self, partition_id, issues):
+ """
+ An issue occured
+
+ @param issue: an Issue object
+ @param partition: a partition id as returned by add_partition
+ """
+ raise NotImplementedError
+
class MockStructuredIssueHandler(IStructuredIssueHandler):
- """
- Used by the PreviewRenderer
- """
- def clear(self):
- pass
-
- def add_partition(self, label, state, parent_partition_id):
- pass
-
- def set_partition_state(self, partition_id, state):
- pass
-
- def append_issues(self, partition_id, issues):
- pass
-
- def set_abort_enabled(self, enabled, method):
- pass
+ """
+ Used by the PreviewRenderer
+ """
+ def clear(self):
+ pass
+
+ def add_partition(self, label, state, parent_partition_id):
+ pass
+
+ def set_partition_state(self, partition_id, state):
+ pass
+
+ def append_issues(self, partition_id, issues):
+ pass
+
+ def set_abort_enabled(self, enabled, method):
+ pass
class Issue(object):
- """
- An issue can be a warning, an error, an info or a task that occures or is
- recognized during parsing and validation of a source file
- """
-
- SEVERITY_WARNING, SEVERITY_ERROR, SEVERITY_INFO, SEVERITY_TASK = 1, 2, 3, 4
-
- POSITION_OFFSET, POSITION_LINE = 1, 2
-
- def __init__(self, message, start, end, file, severity, position_type=POSITION_OFFSET):
- """
- @param message: a str in Pango markup
- @param start: the start offset of the issue
- @param end: the end offset
- @param file: the File object representing the file the issue occured in
- @param severity: one of SEVERITY_*
- """
- self.message = message
- self.start = start
- self.end = end
- self.file = file
- self.severity = severity
- self.position_type = position_type
-
- def __str__(self):
- return "Issue{'%s', %s, %s, %s, %s}" % (self.message, self.start, self.end, self.file, self.severity)
-
-
-
-
\ No newline at end of file
+ """
+ An issue can be a warning, an error, an info or a task that occures or is
+ recognized during parsing and validation of a source file
+ """
+
+ SEVERITY_WARNING, SEVERITY_ERROR, SEVERITY_INFO, SEVERITY_TASK = 1, 2, 3, 4
+
+ POSITION_OFFSET, POSITION_LINE = 1, 2
+
+ def __init__(self, message, start, end, file, severity, position_type=POSITION_OFFSET):
+ """
+ @param message: a str in Pango markup
+ @param start: the start offset of the issue
+ @param end: the end offset
+ @param file: the File object representing the file the issue occured in
+ @param severity: one of SEVERITY_*
+ """
+ self.message = message
+ self.start = start
+ self.end = end
+ self.file = file
+ self.severity = severity
+ self.position_type = position_type
+
+ def __str__(self):
+ return "Issue{'%s', %s, %s, %s, %s}" % (self.message, self.start, self.end, self.file, self.severity)
+
+
+
diff --git a/latex/latex/__init__.py b/latex/latex/__init__.py
index b9b1edb..f3f8817 100644
--- a/latex/latex/__init__.py
+++ b/latex/latex/__init__.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -23,14 +23,14 @@ latex
"""
class LaTeXSource(object):
- """
- This connects a portion of source with some required packages
- """
- def __init__(self, source, packages=[]):
- self.source = source
- self.packages = packages
-
-
+ """
+ This connects a portion of source with some required packages
+ """
+ def __init__(self, source, packages=[]):
+ self.source = source
+ self.packages = packages
+
+
from logging import getLogger
# TODO: use ElementTree
@@ -41,68 +41,68 @@ from ..base import File
class PropertyFile(dict):
- """
- A property file is a hidden XML file that holds meta data for exactly one file.
- It can be used to store the master file of a LaTeX document fragment.
- """
-
- __log = getLogger("PropertyFile")
-
- # TODO: insert value as TEXT node
-
- def __init__(self, file):
- """
- Create or load the property file for a given File
- """
- self.__file = File("%s/.%s.properties.xml" % (file.dirname, file.basename))
-
- try:
- self.__dom = minidom.parse(self.__file.path)
-
- for property_node in self.__dom.getElementsByTagName("property"):
- k = property_node.getAttribute("key")
- v = property_node.getAttribute("value")
- self.__setitem__(k, v)
-
- except IOError:
- self.__log.debug("File %s not found, creating empty one" % self.__file)
-
- self.__dom = minidom.getDOMImplementation().createDocument(None, "properties", None)
-
- except ExpatError, e:
- self.__log.error("Error parsing %s: %s" % (self.__file, e))
-
-
- def __find_node(self, k):
- for node in self.__dom.getElementsByTagName("property"):
- if node.getAttribute("key") == str(k):
- return node
- raise KeyError
-
- def __getitem__(self, k):
- return self.__find_node(k).getAttribute("value")
-
- def __setitem__(self, k, v):
- try:
- self.__find_node(k).setAttribute("value", str(v))
- except KeyError:
- node = self.__dom.createElement("property")
- node.setAttribute("key", str(k))
- node.setAttribute("value", str(v))
- self.__dom.documentElement.appendChild(node)
-
- def save(self):
- filename = self.__file.path
-
- if self.__file.exists:
- mode = "w"
- else:
- mode = "a"
-
- try:
- f = open(filename, mode)
- f.write(self.__dom.toxml())
- f.close()
- self.__log.debug("Saved to %s" % filename)
- except IOError, e:
- self.__log.error("Error saving %s: %s" % (filename, e))
\ No newline at end of file
+ """
+ A property file is a hidden XML file that holds meta data for exactly one file.
+ It can be used to store the master file of a LaTeX document fragment.
+ """
+
+ __log = getLogger("PropertyFile")
+
+ # TODO: insert value as TEXT node
+
+ def __init__(self, file):
+ """
+ Create or load the property file for a given File
+ """
+ self.__file = File("%s/.%s.properties.xml" % (file.dirname, file.basename))
+
+ try:
+ self.__dom = minidom.parse(self.__file.path)
+
+ for property_node in self.__dom.getElementsByTagName("property"):
+ k = property_node.getAttribute("key")
+ v = property_node.getAttribute("value")
+ self.__setitem__(k, v)
+
+ except IOError:
+ self.__log.debug("File %s not found, creating empty one" % self.__file)
+
+ self.__dom = minidom.getDOMImplementation().createDocument(None, "properties", None)
+
+ except ExpatError, e:
+ self.__log.error("Error parsing %s: %s" % (self.__file, e))
+
+
+ def __find_node(self, k):
+ for node in self.__dom.getElementsByTagName("property"):
+ if node.getAttribute("key") == str(k):
+ return node
+ raise KeyError
+
+ def __getitem__(self, k):
+ return self.__find_node(k).getAttribute("value")
+
+ def __setitem__(self, k, v):
+ try:
+ self.__find_node(k).setAttribute("value", str(v))
+ except KeyError:
+ node = self.__dom.createElement("property")
+ node.setAttribute("key", str(k))
+ node.setAttribute("value", str(v))
+ self.__dom.documentElement.appendChild(node)
+
+ def save(self):
+ filename = self.__file.path
+
+ if self.__file.exists:
+ mode = "w"
+ else:
+ mode = "a"
+
+ try:
+ f = open(filename, mode)
+ f.write(self.__dom.toxml())
+ f.close()
+ self.__log.debug("Saved to %s" % filename)
+ except IOError, e:
+ self.__log.error("Error saving %s: %s" % (filename, e))
\ No newline at end of file
diff --git a/latex/latex/actions.py b/latex/latex/actions.py
index 32f1b1b..c190e79 100644
--- a/latex/latex/actions.py
+++ b/latex/latex/actions.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -34,522 +34,522 @@ from ..tools import ToolRunner
from .editor import LaTeXEditor
from .parser import LaTeXParser, Node
from .dialogs import UseBibliographyDialog, InsertGraphicsDialog, InsertTableDialog, \
- InsertListingDialog, BuildImageDialog, SaveAsTemplateDialog, \
- NewDocumentDialog, ChooseMasterDialog
+ InsertListingDialog, BuildImageDialog, SaveAsTemplateDialog, \
+ NewDocumentDialog, ChooseMasterDialog
from . import LaTeXSource, PropertyFile
class LaTeXAction(Action):
- extensions = Preferences().get("latex-extensions").split(",")
+ extensions = Preferences().get("latex-extensions").split(",")
class LaTeXIconAction(IconAction):
- extensions = Preferences().get("latex-extensions").split(",")
+ extensions = Preferences().get("latex-extensions").split(",")
class LaTeXTemplateAction(LaTeXIconAction):
- """
- Utility base class for quickly defining Actions inserting a LaTeX template
- """
- accelerator = None
-
- icon_name = None
- template_source = None
- packages = []
-
- @property
- def icon(self):
- return File(find_resource("icons/%s.png" % self.icon_name))
-
- def activate(self, context):
- context.active_editor.insert(LaTeXSource(Template(self.template_source), self.packages))
+ """
+ Utility base class for quickly defining Actions inserting a LaTeX template
+ """
+ accelerator = None
+
+ icon_name = None
+ template_source = None
+ packages = []
+
+ @property
+ def icon(self):
+ return File(find_resource("icons/%s.png" % self.icon_name))
+
+ def activate(self, context):
+ context.active_editor.insert(LaTeXSource(Template(self.template_source), self.packages))
class LaTeXMenuAction(LaTeXAction):
-
- label = "LaTeX"
- stock_id = None
- accelerator = None
- tooltip = None
-
- def activate(self, context):
- pass
+
+ label = "LaTeX"
+ stock_id = None
+ accelerator = None
+ tooltip = None
+
+ def activate(self, context):
+ pass
class LaTeXNewAction(Action):
- label = "New LaTeX Document..."
- stock_id = Gtk.STOCK_NEW
- accelerator = "<Ctrl><Alt>N"
- tooltip = "Create a new LaTeX document"
-
- _dialog = None
-
- def activate(self, context):
- if not self._dialog:
- self._dialog = NewDocumentDialog()
-
- # we may not open the empty file and insert a Temlate here
- # because WindowContext.activate_editor calls gedit.Window.create_tab_from_uri
- # which is async
-
- if self._dialog.run() == 1:
- file = self._dialog.file
- file.create(self._dialog.source)
- context.activate_editor(file)
+ label = "New LaTeX Document..."
+ stock_id = Gtk.STOCK_NEW
+ accelerator = "<Ctrl><Alt>N"
+ tooltip = "Create a new LaTeX document"
+
+ _dialog = None
+
+ def activate(self, context):
+ if not self._dialog:
+ self._dialog = NewDocumentDialog()
+
+ # we may not open the empty file and insert a Temlate here
+ # because WindowContext.activate_editor calls gedit.Window.create_tab_from_uri
+ # which is async
+
+ if self._dialog.run() == 1:
+ file = self._dialog.file
+ file.create(self._dialog.source)
+ context.activate_editor(file)
class LaTeXChooseMasterAction(LaTeXAction):
- _log = getLogger("LaTeXChooseMasterAction")
-
- label = "Choose Master Document..."
- stock_id = None
- accelerator = None
- tooltip = None
-
- def activate(self, context):
- editor = context.active_editor
- assert type(editor) is LaTeXEditor
-
- file = editor._file # FIXME: access to private member
-
- # load property file
- property_file = PropertyFile(file)
-
- master_filename = ChooseMasterDialog().run(file.dirname)
- if master_filename:
- # relativize the master filename
- master_filename = File(master_filename).relativize(file.dirname, True)
-
- property_file["MasterFilename"] = master_filename
- property_file.save()
-
-
+ _log = getLogger("LaTeXChooseMasterAction")
+
+ label = "Choose Master Document..."
+ stock_id = None
+ accelerator = None
+ tooltip = None
+
+ def activate(self, context):
+ editor = context.active_editor
+ assert type(editor) is LaTeXEditor
+
+ file = editor._file # FIXME: access to private member
+
+ # load property file
+ property_file = PropertyFile(file)
+
+ master_filename = ChooseMasterDialog().run(file.dirname)
+ if master_filename:
+ # relativize the master filename
+ master_filename = File(master_filename).relativize(file.dirname, True)
+
+ property_file["MasterFilename"] = master_filename
+ property_file.save()
+
+
class LaTeXCloseEnvironmentAction(LaTeXIconAction):
- _log = getLogger("LaTeXCloseEnvironmentAction")
-
- label = "Close Nearest Environment"
- icon = File(find_resource("icons/close_env.png"))
- accelerator = "<Ctrl><Alt>E"
- tooltip = "Close the nearest TeX environment at left of the cursor"
-
- def activate(self, context):
- # FIXME: use the document model of the Editor
-
- editor = context.active_editor
-
- assert type(editor) is LaTeXEditor
-
- # push environments on stack and find nearest one to close
-
- try:
- self._stack = []
- self._find_open_environments(LaTeXParser().parse(editor.content_at_left_of_cursor, None, MockIssueHandler()))
-
- if len(self._stack) > 0:
- editor.insert("\\end{%s}" % self._stack[-1])
- else:
- self._log.debug("No environment to close")
- except ValueError:
- self._log.debug("Environments are malformed")
-
- def _find_open_environments(self, parent_node):
- for node in parent_node:
- recurse = True
- if node.type == Node.COMMAND:
- if node.value == "begin":
- # push environment on stack
- environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- self._stack.append(environ)
-
- elif node.value == "end":
- # pop from stack
- environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- try:
- top_environ = self._stack.pop()
- if top_environ != environ:
- raise ValueError()
- except IndexError:
- raise ValueError()
-
- elif node.value == "newcommand":
- recurse = False
-
- if recurse:
- self._find_open_environments(node)
-
-
+ _log = getLogger("LaTeXCloseEnvironmentAction")
+
+ label = "Close Nearest Environment"
+ icon = File(find_resource("icons/close_env.png"))
+ accelerator = "<Ctrl><Alt>E"
+ tooltip = "Close the nearest TeX environment at left of the cursor"
+
+ def activate(self, context):
+ # FIXME: use the document model of the Editor
+
+ editor = context.active_editor
+
+ assert type(editor) is LaTeXEditor
+
+ # push environments on stack and find nearest one to close
+
+ try:
+ self._stack = []
+ self._find_open_environments(LaTeXParser().parse(editor.content_at_left_of_cursor, None, MockIssueHandler()))
+
+ if len(self._stack) > 0:
+ editor.insert("\\end{%s}" % self._stack[-1])
+ else:
+ self._log.debug("No environment to close")
+ except ValueError:
+ self._log.debug("Environments are malformed")
+
+ def _find_open_environments(self, parent_node):
+ for node in parent_node:
+ recurse = True
+ if node.type == Node.COMMAND:
+ if node.value == "begin":
+ # push environment on stack
+ environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ self._stack.append(environ)
+
+ elif node.value == "end":
+ # pop from stack
+ environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ try:
+ top_environ = self._stack.pop()
+ if top_environ != environ:
+ raise ValueError()
+ except IndexError:
+ raise ValueError()
+
+ elif node.value == "newcommand":
+ recurse = False
+
+ if recurse:
+ self._find_open_environments(node)
+
+
class LaTeXUseBibliographyAction(LaTeXIconAction):
- _log = getLogger("LaTeXUseBibliographyAction")
-
- label = "Use Bibliography..."
- icon = File(find_resource("icons/bib.png"))
- accelerator = None
- tooltip = "Use Bibliography"
-
- _dialog = None
-
- def activate(self, context):
- if not self._dialog:
- self._dialog = UseBibliographyDialog()
-
- source = self._dialog.run_dialog(context.active_editor.edited_file)
- if source:
- editor = context.active_editor
-
- assert type(editor) is LaTeXEditor
-
- editor.insert_at_position(source + "\n\n", LaTeXEditor.POSITION_BIBLIOGRAPHY)
-
+ _log = getLogger("LaTeXUseBibliographyAction")
+
+ label = "Use Bibliography..."
+ icon = File(find_resource("icons/bib.png"))
+ accelerator = None
+ tooltip = "Use Bibliography"
+
+ _dialog = None
+
+ def activate(self, context):
+ if not self._dialog:
+ self._dialog = UseBibliographyDialog()
+
+ source = self._dialog.run_dialog(context.active_editor.edited_file)
+ if source:
+ editor = context.active_editor
+
+ assert type(editor) is LaTeXEditor
+
+ editor.insert_at_position(source + "\n\n", LaTeXEditor.POSITION_BIBLIOGRAPHY)
+
class LaTeXFontFamilyAction(LaTeXIconAction):
- menu_tool_action = True
-
- label = "Font Family"
- accelerator = None
- tooltip = "Font Family"
- icon = File(find_resource("icons/bf.png"))
-
- def activate(self, context):
- pass
+ menu_tool_action = True
+
+ label = "Font Family"
+ accelerator = None
+ tooltip = "Font Family"
+ icon = File(find_resource("icons/bf.png"))
+
+ def activate(self, context):
+ pass
class LaTeXFontFamilyMenuAction(LaTeXAction):
- label = "Font Family"
- accelerator = None
- tooltip = "Font Family"
- stock_id = None
-
- def activate(self, context):
- pass
+ label = "Font Family"
+ accelerator = None
+ tooltip = "Font Family"
+ stock_id = None
+
+ def activate(self, context):
+ pass
class LaTeXBoldAction(LaTeXTemplateAction):
- label = "Bold"
- tooltip = "Bold"
- icon_name = "bf"
- template_source = "\\textbf{$_}"
+ label = "Bold"
+ tooltip = "Bold"
+ icon_name = "bf"
+ template_source = "\\textbf{$_}"
class LaTeXItalicAction(LaTeXTemplateAction):
- label = "Italic"
- tooltip = "Italic"
- icon_name = "it"
- template_source = "\\textit{$_}"
-
+ label = "Italic"
+ tooltip = "Italic"
+ icon_name = "it"
+ template_source = "\\textit{$_}"
+
class LaTeXEmphasizeAction(LaTeXTemplateAction):
- label = "Emphasize"
- tooltip = "Emphasize"
- icon_name = "it"
- template_source = "\\emph{$_}"
-
-
+ label = "Emphasize"
+ tooltip = "Emphasize"
+ icon_name = "it"
+ template_source = "\\emph{$_}"
+
+
class LaTeXUnderlineAction(LaTeXTemplateAction):
- label = "Underline"
- tooltip = "Underline"
- icon_name = "underline"
- template_source = "\\underline{$_}"
-
-
+ label = "Underline"
+ tooltip = "Underline"
+ icon_name = "underline"
+ template_source = "\\underline{$_}"
+
+
class LaTeXSmallCapitalsAction(LaTeXTemplateAction):
- label = "Small Capitals"
- tooltip = "Small Capitals"
- icon_name = "sc"
- template_source = "\\textsc{$_}"
-
-
+ label = "Small Capitals"
+ tooltip = "Small Capitals"
+ icon_name = "sc"
+ template_source = "\\textsc{$_}"
+
+
class LaTeXRomanAction(LaTeXTemplateAction):
- label = "Roman"
- tooltip = "Roman"
- icon_name = "rm"
- template_source = "\\textrm{$_}"
-
-
+ label = "Roman"
+ tooltip = "Roman"
+ icon_name = "rm"
+ template_source = "\\textrm{$_}"
+
+
class LaTeXSansSerifAction(LaTeXTemplateAction):
- label = "Sans Serif"
- tooltip = "Sans Serif"
- icon_name = "sf"
- template_source = "\\textsf{$_}"
-
-
+ label = "Sans Serif"
+ tooltip = "Sans Serif"
+ icon_name = "sf"
+ template_source = "\\textsf{$_}"
+
+
class LaTeXTypewriterAction(LaTeXTemplateAction):
- label = "Typewriter"
- tooltip = "Typewriter"
- icon_name = "tt"
- template_source = "\\texttt{$_}"
+ label = "Typewriter"
+ tooltip = "Typewriter"
+ icon_name = "tt"
+ template_source = "\\texttt{$_}"
class LaTeXBlackboardBoldAction(LaTeXTemplateAction):
- label = "Blackboard Bold"
- tooltip = "Blackboard Bold"
- icon_name = "bb"
- packages = ["amsmath"]
- template_source = "\ensuremath{\mathbb{$_}}"
-
-
+ label = "Blackboard Bold"
+ tooltip = "Blackboard Bold"
+ icon_name = "bb"
+ packages = ["amsmath"]
+ template_source = "\ensuremath{\mathbb{$_}}"
+
+
class LaTeXCaligraphyAction(LaTeXTemplateAction):
- label = "Caligraphy"
- tooltip = "Caligraphy"
- icon_name = "cal"
- template_source = "\ensuremath{\mathcal{$_}}"
+ label = "Caligraphy"
+ tooltip = "Caligraphy"
+ icon_name = "cal"
+ template_source = "\ensuremath{\mathcal{$_}}"
class LaTeXFrakturAction(LaTeXTemplateAction):
- label = "Fraktur"
- tooltip = "Fraktur"
- icon_name = "frak"
- packages = ["amsmath"]
- template_source = "\ensuremath{\mathfrak{$_}}"
+ label = "Fraktur"
+ tooltip = "Fraktur"
+ icon_name = "frak"
+ packages = ["amsmath"]
+ template_source = "\ensuremath{\mathfrak{$_}}"
class LaTeXItemizeAction(LaTeXTemplateAction):
- label = "Itemize"
- tooltip = "Itemize"
- icon_name = "itemize"
- template_source = "\\begin{itemize}\n\t\\item $_\n\\end{itemize}"
+ label = "Itemize"
+ tooltip = "Itemize"
+ icon_name = "itemize"
+ template_source = "\\begin{itemize}\n\t\\item $_\n\\end{itemize}"
class LaTeXEnumerateAction(LaTeXTemplateAction):
- label = "Enumerate"
- tooltip = "Enumerate"
- icon_name = "enumerate"
- template_source = "\\begin{enumerate}\n\t\\item $_\n\\end{enumerate}"
+ label = "Enumerate"
+ tooltip = "Enumerate"
+ icon_name = "enumerate"
+ template_source = "\\begin{enumerate}\n\t\\item $_\n\\end{enumerate}"
class LaTeXDescriptionAction(LaTeXTemplateAction):
- label = "Description"
- tooltip = "Description"
- icon_name = "description"
- template_source = "\\begin{description}\n\t\\item[$_]\n\\end{description}"
-
+ label = "Description"
+ tooltip = "Description"
+ icon_name = "description"
+ template_source = "\\begin{description}\n\t\\item[$_]\n\\end{description}"
+
class LaTeXStructureAction(LaTeXIconAction):
- menu_tool_action = True
-
- label = "Structure"
- accelerator = None
- tooltip = "Structure"
- icon = File(find_resource("icons/section.png"))
-
- def activate(self, context):
- pass
+ menu_tool_action = True
+
+ label = "Structure"
+ accelerator = None
+ tooltip = "Structure"
+ icon = File(find_resource("icons/section.png"))
+
+ def activate(self, context):
+ pass
class LaTeXStructureMenuAction(LaTeXAction):
- label = "Structure"
- accelerator = None
- tooltip = "Structure"
- stock_id = None
-
- def activate(self, context):
- pass
+ label = "Structure"
+ accelerator = None
+ tooltip = "Structure"
+ stock_id = None
+
+ def activate(self, context):
+ pass
class LaTeXPartAction(LaTeXTemplateAction):
- label = "Part"
- tooltip = "Part"
- icon_name = "part"
- template_source = "\\part{$_}"
+ label = "Part"
+ tooltip = "Part"
+ icon_name = "part"
+ template_source = "\\part{$_}"
class LaTeXChapterAction(LaTeXTemplateAction):
- label = "Chapter"
- tooltip = "Chapter"
- icon_name = "chapter"
- template_source = "\\chapter{$_}"
+ label = "Chapter"
+ tooltip = "Chapter"
+ icon_name = "chapter"
+ template_source = "\\chapter{$_}"
+
-
class LaTeXSectionAction(LaTeXTemplateAction):
- label = "Section"
- tooltip = "Section"
- icon_name = "section"
- template_source = "\\section{$_}"
-
+ label = "Section"
+ tooltip = "Section"
+ icon_name = "section"
+ template_source = "\\section{$_}"
+
class LaTeXSubsectionAction(LaTeXTemplateAction):
- label = "Subsection"
- tooltip = "Subsection"
- icon_name = "subsection"
- template_source = "\\subsection{$_}"
-
+ label = "Subsection"
+ tooltip = "Subsection"
+ icon_name = "subsection"
+ template_source = "\\subsection{$_}"
+
class LaTeXParagraphAction(LaTeXTemplateAction):
- label = "Paragraph"
- tooltip = "Paragraph"
- icon_name = "paragraph"
- template_source = "\\paragraph{$_}"
-
-
+ label = "Paragraph"
+ tooltip = "Paragraph"
+ icon_name = "paragraph"
+ template_source = "\\paragraph{$_}"
+
+
class LaTeXSubparagraphAction(LaTeXTemplateAction):
- label = "Subparagraph"
- tooltip = "Subparagraph"
- icon_name = "paragraph"
- template_source = "\\subparagraph{$_}"
-
-
+ label = "Subparagraph"
+ tooltip = "Subparagraph"
+ icon_name = "paragraph"
+ template_source = "\\subparagraph{$_}"
+
+
class LaTeXGraphicsAction(LaTeXIconAction):
- label = "Insert Graphics"
- accelerator = None
- tooltip = "Insert Graphics"
- icon = File(find_resource("icons/graphics.png"))
-
- dialog = None
-
- def activate(self, context):
- if not self.dialog:
- self.dialog = InsertGraphicsDialog()
- source = self.dialog.run(context.active_editor.edited_file)
- if source:
- context.active_editor.insert(source)
+ label = "Insert Graphics"
+ accelerator = None
+ tooltip = "Insert Graphics"
+ icon = File(find_resource("icons/graphics.png"))
+
+ dialog = None
+
+ def activate(self, context):
+ if not self.dialog:
+ self.dialog = InsertGraphicsDialog()
+ source = self.dialog.run(context.active_editor.edited_file)
+ if source:
+ context.active_editor.insert(source)
class LaTeXTableAction(LaTeXIconAction):
- label = "Insert Table or Matrix"
- accelerator = None
- tooltip = "Insert Table or Matrix"
- icon = File(find_resource("icons/table.png"))
-
- dialog = None
-
- def activate(self, context):
- if not self.dialog:
- self.dialog = InsertTableDialog()
- source = self.dialog.run()
- if source:
- context.active_editor.insert(source)
-
-
+ label = "Insert Table or Matrix"
+ accelerator = None
+ tooltip = "Insert Table or Matrix"
+ icon = File(find_resource("icons/table.png"))
+
+ dialog = None
+
+ def activate(self, context):
+ if not self.dialog:
+ self.dialog = InsertTableDialog()
+ source = self.dialog.run()
+ if source:
+ context.active_editor.insert(source)
+
+
class LaTeXListingAction(LaTeXIconAction):
- label = "Insert Source Code Listing"
- accelerator = None
- tooltip = "Insert Source Code Listing"
- icon = File(find_resource("icons/listing.png"))
-
- dialog = None
-
- def activate(self, context):
- if not self.dialog:
- self.dialog = InsertListingDialog()
- source = self.dialog.run(context.active_editor.edited_file)
- if source:
- context.active_editor.insert(source)
+ label = "Insert Source Code Listing"
+ accelerator = None
+ tooltip = "Insert Source Code Listing"
+ icon = File(find_resource("icons/listing.png"))
+
+ dialog = None
+
+ def activate(self, context):
+ if not self.dialog:
+ self.dialog = InsertListingDialog()
+ source = self.dialog.run(context.active_editor.edited_file)
+ if source:
+ context.active_editor.insert(source)
class LaTeXBuildImageAction(LaTeXIconAction):
- label = "Build Image"
- accelerator = None
- tooltip = "Build an image from the LaTeX document"
- icon = File(find_resource("icons/build-image.png"))
-
- dialog = None
-
- def activate(self, context):
- if not self.dialog:
- self.dialog = BuildImageDialog()
-
- tool = self.dialog.run()
- if tool is not None:
- tool_view = context.find_view(None, "ToolView")
-
- if context.active_editor:
- ToolRunner().run(context.active_editor.file, tool, tool_view)
-
-
+ label = "Build Image"
+ accelerator = None
+ tooltip = "Build an image from the LaTeX document"
+ icon = File(find_resource("icons/build-image.png"))
+
+ dialog = None
+
+ def activate(self, context):
+ if not self.dialog:
+ self.dialog = BuildImageDialog()
+
+ tool = self.dialog.run()
+ if tool is not None:
+ tool_view = context.find_view(None, "ToolView")
+
+ if context.active_editor:
+ ToolRunner().run(context.active_editor.file, tool, tool_view)
+
+
class LaTeXJustifyLeftAction(LaTeXTemplateAction):
- label = "Justify Left"
- tooltip = "Justify Left"
- icon_name = "justify-left"
- template_source = "\\begin{flushleft}$_\\end{flushleft}"
-
-
+ label = "Justify Left"
+ tooltip = "Justify Left"
+ icon_name = "justify-left"
+ template_source = "\\begin{flushleft}$_\\end{flushleft}"
+
+
class LaTeXJustifyCenterAction(LaTeXTemplateAction):
- label = "Justify Center"
- tooltip = "Justify Center"
- icon_name = "justify-center"
- template_source = "\\begin{center}$_\\end{center}"
+ label = "Justify Center"
+ tooltip = "Justify Center"
+ icon_name = "justify-center"
+ template_source = "\\begin{center}$_\\end{center}"
class LaTeXJustifyRightAction(LaTeXTemplateAction):
- label = "Justify Right"
- tooltip = "Justify Right"
- icon_name = "justify-right"
- template_source = "\\begin{flushright}$_\\end{flushright}"
-
+ label = "Justify Right"
+ tooltip = "Justify Right"
+ icon_name = "justify-right"
+ template_source = "\\begin{flushright}$_\\end{flushright}"
+
class LaTeXMathMenuAction(LaTeXAction):
- label = "Math"
- accelerator = None
- tooltip = "Math"
- stock_id = None
-
- def activate(self, context):
- pass
+ label = "Math"
+ accelerator = None
+ tooltip = "Math"
+ stock_id = None
+
+ def activate(self, context):
+ pass
class LaTeXMathAction(LaTeXTemplateAction):
- menu_tool_action = True
-
- label = "Mathematical Environment"
- tooltip = "Mathematical Environment"
- icon_name = "math"
- template_source = "$ $_ $"
-
-
+ menu_tool_action = True
+
+ label = "Mathematical Environment"
+ tooltip = "Mathematical Environment"
+ icon_name = "math"
+ template_source = "$ $_ $"
+
+
class LaTeXDisplayMathAction(LaTeXTemplateAction):
- label = "Centered Formula"
- tooltip = "Centered Formula"
- icon_name = "displaymath"
- template_source = "\\[ $_ \\]"
-
-
+ label = "Centered Formula"
+ tooltip = "Centered Formula"
+ icon_name = "displaymath"
+ template_source = "\\[ $_ \\]"
+
+
class LaTeXEquationAction(LaTeXTemplateAction):
- label = "Numbered Equation"
- tooltip = "Numbered Equation"
- icon_name = "equation"
- template_source = """\\begin{equation}
- $_
+ label = "Numbered Equation"
+ tooltip = "Numbered Equation"
+ icon_name = "equation"
+ template_source = """\\begin{equation}
+ $_
\\end{equation}"""
class LaTeXUnEqnArrayAction(LaTeXTemplateAction):
- label = "Array of Equations"
- tooltip = "Array of Equations"
- icon_name = "uneqnarray"
- packages = ["amsmath"]
- template_source = """\\begin{align*}
- $_
+ label = "Array of Equations"
+ tooltip = "Array of Equations"
+ icon_name = "uneqnarray"
+ packages = ["amsmath"]
+ template_source = """\\begin{align*}
+ $_
\\end{align*}"""
class LaTeXEqnArrayAction(LaTeXTemplateAction):
- label = "Numbered Array of Equations"
- tooltip = "Numbered Array of Equations"
- icon_name = "eqnarray"
- packages = ["amsmath"]
- template_source = """\\begin{align}
- $_
+ label = "Numbered Array of Equations"
+ tooltip = "Numbered Array of Equations"
+ icon_name = "eqnarray"
+ packages = ["amsmath"]
+ template_source = """\\begin{align}
+ $_
\\end{align}"""
-
-
+
+
class LaTeXSaveAsTemplateAction(LaTeXAction):
- label = "Save As Template..."
- accelerator = None
- tooltip = "Save the current document as a template"
- stock_id = Gtk.STOCK_SAVE_AS
-
- def activate(self, context):
- dialog = SaveAsTemplateDialog()
- file = dialog.run()
-
- content = context.active_editor.content
-
- fo = open(file.path, "w")
- fo.write(content)
- fo.close
-
-
-
-
-
-
-
-
+ label = "Save As Template..."
+ accelerator = None
+ tooltip = "Save the current document as a template"
+ stock_id = Gtk.STOCK_SAVE_AS
+
+ def activate(self, context):
+ dialog = SaveAsTemplateDialog()
+ file = dialog.run()
+
+ content = context.active_editor.content
+
+ fo = open(file.path, "w")
+ fo.write(content)
+ fo.close
+
+
+
+
+
+
+
+
diff --git a/latex/latex/archive.py b/latex/latex/archive.py
index a361a0f..be7efc9 100644
--- a/latex/latex/archive.py
+++ b/latex/latex/archive.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -28,86 +28,86 @@ from ..issues import MockIssueHandler
class Dependency:
- def __init__(self, original_filename):
- self.original_filename = original_filename
+ def __init__(self, original_filename):
+ self.original_filename = original_filename
class LaTeXArchiver:
- def archive(self, file):
- """
- @param file: the master document File object
- """
- self._file = file
- self._scan()
-
- def _scan(self):
- """
- Scan the document for dependencies
- """
- self._dependencies = []
- scanner = LaTeXDependencyScanner()
- for filename in scanner.scan(self._file):
- self._dependencies.append(Dependency(filename))
-
- def _copy_to_sandbox(self):
- """
- Copy the document and its dependencies to a sandbox folder
- """
- pass
-
- def _repair_references(self):
- """
- Repair eventually broken absolute paths
- """
- pass
-
- def _pack(self):
- """
- Pack the document and its deps into an archive
- """
- pass
+ def archive(self, file):
+ """
+ @param file: the master document File object
+ """
+ self._file = file
+ self._scan()
+
+ def _scan(self):
+ """
+ Scan the document for dependencies
+ """
+ self._dependencies = []
+ scanner = LaTeXDependencyScanner()
+ for filename in scanner.scan(self._file):
+ self._dependencies.append(Dependency(filename))
+
+ def _copy_to_sandbox(self):
+ """
+ Copy the document and its dependencies to a sandbox folder
+ """
+ pass
+
+ def _repair_references(self):
+ """
+ Repair eventually broken absolute paths
+ """
+ pass
+
+ def _pack(self):
+ """
+ Pack the document and its deps into an archive
+ """
+ pass
class LaTeXDependencyScanner:
- """
- This analyzes a document and recognizes its dependent files
-
- @deprecated:
- """
- def __init__(self):
- self._parser = LaTeXParser()
- self._expander = LaTeXReferenceExpander()
-
- def scan(self, file):
- # parse
- content = open(file.path, "r").read()
- self._document = self._parser.parse(content, file, MockIssueHandler())
- self._expander.expand(self._document, file, MockIssueHandler(), None)
- # search
- self._filenames = []
- self._search(self._document)
-
- # TODO: add all filenames that match a regex
-
- return self._filenames
-
- def _search(self, parent):
- # search the model for all \in* commands
- for node in parent:
- if node.type == Node.COMMAND:
- if node.value.startswith("in"):
- try:
- argument = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- # TODO: match against regex for filenames and create File object
- if argument.startswith("/"):
- filename = argument
- else:
- filename = node.file.dirname + "/" + argument
- if node.value in ["include", "input"]:
- filename += ".tex"
- self._filenames.append(filename)
- except IndexError:
- pass
- self._search(node)
-
-
+ """
+ This analyzes a document and recognizes its dependent files
+
+ @deprecated:
+ """
+ def __init__(self):
+ self._parser = LaTeXParser()
+ self._expander = LaTeXReferenceExpander()
+
+ def scan(self, file):
+ # parse
+ content = open(file.path, "r").read()
+ self._document = self._parser.parse(content, file, MockIssueHandler())
+ self._expander.expand(self._document, file, MockIssueHandler(), None)
+ # search
+ self._filenames = []
+ self._search(self._document)
+
+ # TODO: add all filenames that match a regex
+
+ return self._filenames
+
+ def _search(self, parent):
+ # search the model for all \in* commands
+ for node in parent:
+ if node.type == Node.COMMAND:
+ if node.value.startswith("in"):
+ try:
+ argument = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ # TODO: match against regex for filenames and create File object
+ if argument.startswith("/"):
+ filename = argument
+ else:
+ filename = node.file.dirname + "/" + argument
+ if node.value in ["include", "input"]:
+ filename += ".tex"
+ self._filenames.append(filename)
+ except IndexError:
+ pass
+ self._search(node)
+
+
diff --git a/latex/latex/cache.py b/latex/latex/cache.py
index 94520cf..1a10dd5 100644
--- a/latex/latex/cache.py
+++ b/latex/latex/cache.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -31,127 +31,126 @@ from ..issues import IIssueHandler
class CacheIssueHandler(IIssueHandler):
- """
- This is used to catch the issues occuring while parsing a document
- on cache fault.
- """
- def __init__(self):
- self.issues = []
-
- def clear(self):
- self.issues = []
-
- def issue(self, issue):
- self.issues.append(issue)
+ """
+ This is used to catch the issues occuring while parsing a document
+ on cache fault.
+ """
+ def __init__(self):
+ self.issues = []
+
+ def clear(self):
+ self.issues = []
+
+ def issue(self, issue):
+ self.issues.append(issue)
class LaTeXDocumentCache(object):
- """
- This caches LaTeX document models. It used to speed up
- the LaTeXReferenceExpander.
- """
-
- # FIXME: we need a global character set
-
- # TODO: serialize the cache on shutdown
-
- _log = getLogger("LaTeXDocumentCache")
-
- class Entry(object):
- """
- An entry in the cache
- """
- _log = getLogger("LaTeXDocumentCache.Entry")
-
- def __init__(self, file, charset):
- self.__file = file
- self.__parser = LaTeXParser()
- self.__issue_handler = CacheIssueHandler()
- self.__mtime = 0
- self.__document = None
- self.__charset = charset
-
- self.synchronize()
-
- @property
- def modified(self):
- return (self.__file.mtime > self.__mtime)
-
- @property
- def document(self):
- return self.__document
-
- @property
- def issues(self):
- return self.__issue_handler.issues
-
- def synchronize(self):
- """
- Synchronize document model with file contents.
-
- @raise OSError: if the file is not found
- """
- # update timestamp
- self.__mtime = self.__file.mtime
-
- # clear previous data
- self.__issue_handler.clear()
- if self.__document != None:
- self.__document.destroy()
- self.__document = None
-
- # read file
- try:
- f = open(self.__file.path, "r")
- try:
- content = f.read()
- finally:
- f.close()
- except IOError:
- return
-
- if self.__charset is not None:
- content = content.decode(self.__charset)
-
- # parse
- self.__document = self.__parser.parse(content, self.__file, self.__issue_handler)
-
- def __new__(cls):
- if not '_instance' in cls.__dict__:
- cls._instance = object.__new__(cls)
- return cls._instance
-
- def __init__(self):
- if not '_ready' in dir(self):
- self._entries = {}
- self._ready = True
-
- def get_document(self, file, charset, issue_handler):
- """
- Return the (hopefully) cached document model for a given file
-
- @param file: a File object
- @param charset: character set
- @param issue_handler: an IIssueHandler to use
- """
- try:
- # update entry if necessary
- entry = self._entries[file.uri]
- self._log.debug("Reading '%s' from cache" % file)
- if entry.modified:
- self._log.debug("File '%s' modified, synchronizing..." % file)
- entry.synchronize()
- except KeyError:
- self._log.debug("Cache fault for '%s'" % file)
- # create new entry
- entry = self.Entry(file, charset)
- self._entries[file.uri] = entry
-
- # pass cached issues to the issue handler
- for issue in entry.issues:
- issue_handler.issue(issue)
-
- return entry.document
-
-
-
\ No newline at end of file
+ """
+ This caches LaTeX document models. It used to speed up
+ the LaTeXReferenceExpander.
+ """
+
+ # FIXME: we need a global character set
+
+ # TODO: serialize the cache on shutdown
+
+ _log = getLogger("LaTeXDocumentCache")
+
+ class Entry(object):
+ """
+ An entry in the cache
+ """
+ _log = getLogger("LaTeXDocumentCache.Entry")
+
+ def __init__(self, file, charset):
+ self.__file = file
+ self.__parser = LaTeXParser()
+ self.__issue_handler = CacheIssueHandler()
+ self.__mtime = 0
+ self.__document = None
+ self.__charset = charset
+
+ self.synchronize()
+
+ @property
+ def modified(self):
+ return (self.__file.mtime > self.__mtime)
+
+ @property
+ def document(self):
+ return self.__document
+
+ @property
+ def issues(self):
+ return self.__issue_handler.issues
+
+ def synchronize(self):
+ """
+ Synchronize document model with file contents.
+
+ @raise OSError: if the file is not found
+ """
+ # update timestamp
+ self.__mtime = self.__file.mtime
+
+ # clear previous data
+ self.__issue_handler.clear()
+ if self.__document != None:
+ self.__document.destroy()
+ self.__document = None
+
+ # read file
+ try:
+ f = open(self.__file.path, "r")
+ try:
+ content = f.read()
+ finally:
+ f.close()
+ except IOError:
+ return
+
+ if self.__charset is not None:
+ content = content.decode(self.__charset)
+
+ # parse
+ self.__document = self.__parser.parse(content, self.__file, self.__issue_handler)
+
+ def __new__(cls):
+ if not '_instance' in cls.__dict__:
+ cls._instance = object.__new__(cls)
+ return cls._instance
+
+ def __init__(self):
+ if not '_ready' in dir(self):
+ self._entries = {}
+ self._ready = True
+
+ def get_document(self, file, charset, issue_handler):
+ """
+ Return the (hopefully) cached document model for a given file
+
+ @param file: a File object
+ @param charset: character set
+ @param issue_handler: an IIssueHandler to use
+ """
+ try:
+ # update entry if necessary
+ entry = self._entries[file.uri]
+ self._log.debug("Reading '%s' from cache" % file)
+ if entry.modified:
+ self._log.debug("File '%s' modified, synchronizing..." % file)
+ entry.synchronize()
+ except KeyError:
+ self._log.debug("Cache fault for '%s'" % file)
+ # create new entry
+ entry = self.Entry(file, charset)
+ self._entries[file.uri] = entry
+
+ # pass cached issues to the issue handler
+ for issue in entry.issues:
+ issue_handler.issue(issue)
+
+ return entry.document
+
+
diff --git a/latex/latex/completion.py b/latex/latex/completion.py
index fad7bb1..f334bef 100644
--- a/latex/latex/completion.py
+++ b/latex/latex/completion.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -32,62 +32,62 @@ from ..base import ICompletionHandler, Proposal, Template
class LaTeXCommandProposal(Proposal):
- """
- A proposal inserting a Template when activated
- """
- icon = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/i_command.png"))
-
- def __init__(self, overlap, template, label):
- self._template = template
- self._label = label
- self._overlap = overlap
-
- @property
- def source(self):
- return self._template
-
- @property
- def label(self):
- return self._label
-
- @property
- def details(self):
- return None
-
- @property
- def overlap(self):
- return self._overlap
-
+ """
+ A proposal inserting a Template when activated
+ """
+ icon = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/i_command.png"))
+
+ def __init__(self, overlap, template, label):
+ self._template = template
+ self._label = label
+ self._overlap = overlap
+
+ @property
+ def source(self):
+ return self._template
+
+ @property
+ def label(self):
+ return self._label
+
+ @property
+ def details(self):
+ return None
+
+ @property
+ def overlap(self):
+ return self._overlap
+
class LaTeXChoiceProposal(Proposal):
- """
- A proposal inserting a simple string when activated
- """
- icon = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/i_choice.png"))
-
- def __init__(self, overlap, source, label, details):
- self._source = source
- self._details = details
- self._overlap = overlap
- self._label = label
-
- @property
- def source(self):
- return self._source
-
- @property
- def label(self):
- return self._label
-
- @property
- def details(self):
- return self._details
-
- @property
- def overlap(self):
- return self._overlap
-
-
+ """
+ A proposal inserting a simple string when activated
+ """
+ icon = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/i_choice.png"))
+
+ def __init__(self, overlap, source, label, details):
+ self._source = source
+ self._details = details
+ self._overlap = overlap
+ self._label = label
+
+ @property
+ def source(self):
+ return self._source
+
+ @property
+ def label(self):
+ return self._label
+
+ @property
+ def details(self):
+ return self._details
+
+ @property
+ def overlap(self):
+ return self._overlap
+
+
from model import LanguageModelFactory, Command, Choice, MandatoryArgument, OptionalArgument
from parser import PrefixParser, Node
@@ -95,303 +95,303 @@ from ..bibtex.cache import BibTeXDocumentCache
class LaTeXCompletionHandler(ICompletionHandler):
- """
- This implements the LaTeX-specific code completion
- """
- _log = getLogger("LaTeXCompletionHandler")
-
- trigger_keys = ["backslash", "braceleft"]
- prefix_delimiters = ["\\"]
-
- def __init__(self):
- self._log.debug("init")
-
- self._language_model = LanguageModelFactory().create_language_model()
- self._bibtex_document_cache = BibTeXDocumentCache()
-
- def set_outline(self, outline):
- """
- Process a LaTeX outline model
-
- @param outline: a latex.outline.Outline instance
- """
-
- # labels
- label_choices = [Choice(None, label.value) for label in outline.labels]
- self._language_model.fill_placeholder("Labels", label_choices)
-
- # colors
- color_choices = [Choice(None, color) for color in outline.colors]
- self._language_model.fill_placeholder("Colors", color_choices)
-
- # newcommands
- newcommands = []
- for n in outline.newcommands:
- command = Command(None, n.value)
- for i in range(n.numOfArgs):
- command.children.append(MandatoryArgument(None, "#%s" % (i + 1)))
- newcommands.append(command)
- self._language_model.set_newcommands(newcommands)
-
- # newenvironments
- newenvironments = []
- for n in outline.newenvironments:
- choice = Choice(None, n.value)
- newenvironments.append(choice)
- self._language_model.fill_placeholder("Newenvironments", newenvironments)
-
- #
- # bibtex entries
- #
- try:
-
- entry_choices = []
-
- for bib_file in outline.bibliographies:
- try:
- bibtex_document = self._bibtex_document_cache.get_document(bib_file)
-
- # generate choices from entries
-
- for entry in bibtex_document.entries:
-
- # build table data for DetailsPopup
- rows = []
- for field in entry.fields:
- rows.append([field.name, field.valueMarkup])
-
- entry_choices.append(Choice(None, entry.key, rows))
-
- except OSError:
- # BibTeX file not found
- self._log.error("Not found: %s" % bib_file)
-
- # attach to placeholders in CommandStore
- self._language_model.fill_placeholder("Bibitems", entry_choices)
-
- except IOError:
- self._log.debug("Failed to provide BibTeX completion due to IOError")
-
-
- def set_neighbors(self, tex_files, bib_files, graphic_files):
- """
- Populate the lists of neighbor files
-
- @param tex_files: list of neighbor TeX files
- @param bib_files: list of neighbor BibTeX files
- @param graphic_files: list of neighbor graphics
- """
- tex_choices = [Choice(None, file.shortbasename) for file in tex_files]
- self._language_model.fill_placeholder("TexFiles", tex_choices)
-
- bib_choices = [Choice(None, file.shortbasename) for file in bib_files]
- self._language_model.fill_placeholder("BibFiles", bib_choices)
-
- graphic_choices = [Choice(None, file.basename) for file in graphic_files]
- self._language_model.fill_placeholder("ImageFiles", graphic_choices)
-
- def complete(self, prefix):
- """
- Try to complete a given prefix
- """
- self._log.debug("complete: '%s'" % prefix)
-
- #proposals = [LaTeXTemplateProposal(Template("Hello[${One}][${Two}][${Three}]"), "Hello[Some]"), LaTeXProposal("\\world")]
-
- fragment = Node(Node.DOCUMENT)
- parser = PrefixParser()
-
- try:
- parser.parse(prefix, fragment)
-
- modelParser = PrefixModelParser(self._language_model)
- proposals = modelParser.parse(fragment)
-
- self._log.debug("Generated %s proposals" % len(proposals))
-
- return proposals
-
- except Exception, e:
- self._log.debug(e)
-
- return []
-
- def __del__(self):
- self._log.debug("Properly destroyed %s" % self)
-
+ """
+ This implements the LaTeX-specific code completion
+ """
+ _log = getLogger("LaTeXCompletionHandler")
+
+ trigger_keys = ["backslash", "braceleft"]
+ prefix_delimiters = ["\\"]
+
+ def __init__(self):
+ self._log.debug("init")
+
+ self._language_model = LanguageModelFactory().create_language_model()
+ self._bibtex_document_cache = BibTeXDocumentCache()
+
+ def set_outline(self, outline):
+ """
+ Process a LaTeX outline model
+
+ @param outline: a latex.outline.Outline instance
+ """
+
+ # labels
+ label_choices = [Choice(None, label.value) for label in outline.labels]
+ self._language_model.fill_placeholder("Labels", label_choices)
+
+ # colors
+ color_choices = [Choice(None, color) for color in outline.colors]
+ self._language_model.fill_placeholder("Colors", color_choices)
+
+ # newcommands
+ newcommands = []
+ for n in outline.newcommands:
+ command = Command(None, n.value)
+ for i in range(n.numOfArgs):
+ command.children.append(MandatoryArgument(None, "#%s" % (i + 1)))
+ newcommands.append(command)
+ self._language_model.set_newcommands(newcommands)
+
+ # newenvironments
+ newenvironments = []
+ for n in outline.newenvironments:
+ choice = Choice(None, n.value)
+ newenvironments.append(choice)
+ self._language_model.fill_placeholder("Newenvironments", newenvironments)
+
+ #
+ # bibtex entries
+ #
+ try:
+
+ entry_choices = []
+
+ for bib_file in outline.bibliographies:
+ try:
+ bibtex_document = self._bibtex_document_cache.get_document(bib_file)
+
+ # generate choices from entries
+
+ for entry in bibtex_document.entries:
+
+ # build table data for DetailsPopup
+ rows = []
+ for field in entry.fields:
+ rows.append([field.name, field.valueMarkup])
+
+ entry_choices.append(Choice(None, entry.key, rows))
+
+ except OSError:
+ # BibTeX file not found
+ self._log.error("Not found: %s" % bib_file)
+
+ # attach to placeholders in CommandStore
+ self._language_model.fill_placeholder("Bibitems", entry_choices)
+
+ except IOError:
+ self._log.debug("Failed to provide BibTeX completion due to IOError")
+
+
+ def set_neighbors(self, tex_files, bib_files, graphic_files):
+ """
+ Populate the lists of neighbor files
+
+ @param tex_files: list of neighbor TeX files
+ @param bib_files: list of neighbor BibTeX files
+ @param graphic_files: list of neighbor graphics
+ """
+ tex_choices = [Choice(None, file.shortbasename) for file in tex_files]
+ self._language_model.fill_placeholder("TexFiles", tex_choices)
+
+ bib_choices = [Choice(None, file.shortbasename) for file in bib_files]
+ self._language_model.fill_placeholder("BibFiles", bib_choices)
+
+ graphic_choices = [Choice(None, file.basename) for file in graphic_files]
+ self._language_model.fill_placeholder("ImageFiles", graphic_choices)
+
+ def complete(self, prefix):
+ """
+ Try to complete a given prefix
+ """
+ self._log.debug("complete: '%s'" % prefix)
+
+ #proposals = [LaTeXTemplateProposal(Template("Hello[${One}][${Two}][${Three}]"), "Hello[Some]"), LaTeXProposal("\\world")]
+
+ fragment = Node(Node.DOCUMENT)
+ parser = PrefixParser()
+
+ try:
+ parser.parse(prefix, fragment)
+
+ modelParser = PrefixModelParser(self._language_model)
+ proposals = modelParser.parse(fragment)
+
+ self._log.debug("Generated %s proposals" % len(proposals))
+
+ return proposals
+
+ except Exception, e:
+ self._log.debug(e)
+
+ return []
+
+ def __del__(self):
+ self._log.debug("Properly destroyed %s" % self)
+
from ..preferences import Preferences
from . import LaTeXSource
class PrefixModelParser(object):
- """
- This parses the dcoument model of a prefix and generates proposals accordingly
-
- This is used by the LatexProposalGenerator class
- """
-
- # FIXME: Completion doesn't work at \includegraphics[]{_} but at \includegraphics{_}
-
- _log = getLogger("PrefixModelParser")
-
- def __init__(self, language_model):
- self.__language_model = language_model
- self.__light_foreground = Preferences().get("light-foreground-color")
-
- def __create_proposals_from_commands(self, commands, overlap):
- """
- Generate proposals for commands
- """
- proposals = []
-
- for command in commands:
- label = command.name
- templateSource = "\\" + command.name
-
- for argument in command.children:
- if type(argument) is MandatoryArgument:
- label += "{<span color='%s'>%s</span>}" % (self.__light_foreground, argument.label)
- templateSource += "{${%s}}" % argument.label
- elif type(argument) is OptionalArgument:
- label += "[<span color='%s'>%s</span>]" % (self.__light_foreground, argument.label)
- templateSource += "[${%s}]" % argument.label
-
- if command.package:
- label += " <small><b>%s</b></small>" % command.package
-
- # workaround for latex.model.Element.package may be None
- # TODO: latex.model.Element.package should be a list of packages
- if command.package is None:
- packages = []
- else:
- packages = [command.package]
- proposal = LaTeXCommandProposal(overlap, LaTeXSource(Template(templateSource), packages), label)
- proposals.append(proposal)
-
- return proposals
-
- def __create_proposals_from_choices(self, choices, overlap):
- """
- Generate proposals for argument choices
- """
- proposals = []
-
- for choice in choices:
- label = choice.value
- if choice.package:
- label += " <small><b>%s</b></small>" % choice.package
-
- # see above
- if choice.package is None:
- packages = []
- else:
- packages = [choice.package]
- proposal = LaTeXChoiceProposal(overlap, LaTeXSource(choice.value, packages), label, choice.details)
- proposals.append(proposal)
-
- return proposals
-
- def parse(self, prefixFragment):
- """
- Returns choices
- """
-
- # root node of the prefix model must be COMMAND
- commandNode = prefixFragment[-1]
- if commandNode.type != Node.COMMAND:
- return []
-
- commandName = commandNode.value
-
- if len(commandNode) == 0:
- # command has no arguments...
-
- if len(commandName) == 0:
- # no name, so propose all commands
- commands = self.__language_model.commands.values()
- overlap = 1 # only "\"
- else:
- commands = self.__language_model.find_command(commandName)
-
- if len(commands) == 1 and commands[0].name == commandName:
- # don't propose when only one command is found and that one
- # matches the typed one
- return []
-
- overlap = len(commandName) + 1 # "\begi"
-
- return self.__create_proposals_from_commands(commands, overlap)
-
- # ...command has arguments
-
- try:
- self._log.debug(commandNode.xml)
-
- # find the language model of the command
- storedCommand = self.__language_model.commands[commandName]
-
-
- try:
- argumentNode, storedArgument = self.__match_argument(commandNode, storedCommand)
- except Exception, e:
- self._log.error(e)
- return []
-
-
- choices = storedArgument.children
-
- # filter argument matching the already typed argument text
-
- argumentValue = argumentNode.innerText
-
- if len(argumentValue):
- choices = [choice for choice in choices if choice.value.startswith(argumentValue)]
- overlap = len(argumentValue)
- else:
- overlap = 0
-
- return self.__create_proposals_from_choices(choices, overlap)
-
- except KeyError:
- self._log.debug("Command not found: %s" % commandName)
- return []
-
- def __match_argument(self, command, model_command):
- """
- @param command: the parsed command Node
- @param model_command: the according model command
- @return: (matched argument, model argument)
- """
- # push the arguments of the model command on a stack
- model_argument_stack = []
- model_argument_stack.extend(model_command.children)
- model_argument_stack.reverse()
-
- for argument in command:
- if argument.type == Node.MANDATORY_ARGUMENT:
-
- # optional arguments in the model may be skipped
- while True:
- try:
- model_argument = model_argument_stack.pop()
- if model_argument.type != Node.OPTIONAL_ARGUMENT:
- break
- except IndexError:
- # no more optional arguments to skip - signatures can't match
- raise Exception("Signatures don't match")
-
- if not argument.closed:
- return (argument, model_argument)
-
- elif argument.type == Node.OPTIONAL_ARGUMENT:
- model_argument = model_argument_stack.pop()
-
- if model_argument.type != Node.OPTIONAL_ARGUMENT:
- raise Exception("Signatures don't match")
-
- if not argument.closed:
- return (argument, model_argument)
-
- raise Exception("No matching model argument found")
+ """
+ This parses the dcoument model of a prefix and generates proposals accordingly
+
+ This is used by the LatexProposalGenerator class
+ """
+
+ # FIXME: Completion doesn't work at \includegraphics[]{_} but at \includegraphics{_}
+
+ _log = getLogger("PrefixModelParser")
+
+ def __init__(self, language_model):
+ self.__language_model = language_model
+ self.__light_foreground = Preferences().get("light-foreground-color")
+
+ def __create_proposals_from_commands(self, commands, overlap):
+ """
+ Generate proposals for commands
+ """
+ proposals = []
+
+ for command in commands:
+ label = command.name
+ templateSource = "\\" + command.name
+
+ for argument in command.children:
+ if type(argument) is MandatoryArgument:
+ label += "{<span color='%s'>%s</span>}" % (self.__light_foreground, argument.label)
+ templateSource += "{${%s}}" % argument.label
+ elif type(argument) is OptionalArgument:
+ label += "[<span color='%s'>%s</span>]" % (self.__light_foreground, argument.label)
+ templateSource += "[${%s}]" % argument.label
+
+ if command.package:
+ label += " <small><b>%s</b></small>" % command.package
+
+ # workaround for latex.model.Element.package may be None
+ # TODO: latex.model.Element.package should be a list of packages
+ if command.package is None:
+ packages = []
+ else:
+ packages = [command.package]
+ proposal = LaTeXCommandProposal(overlap, LaTeXSource(Template(templateSource), packages), label)
+ proposals.append(proposal)
+
+ return proposals
+
+ def __create_proposals_from_choices(self, choices, overlap):
+ """
+ Generate proposals for argument choices
+ """
+ proposals = []
+
+ for choice in choices:
+ label = choice.value
+ if choice.package:
+ label += " <small><b>%s</b></small>" % choice.package
+
+ # see above
+ if choice.package is None:
+ packages = []
+ else:
+ packages = [choice.package]
+ proposal = LaTeXChoiceProposal(overlap, LaTeXSource(choice.value, packages), label, choice.details)
+ proposals.append(proposal)
+
+ return proposals
+
+ def parse(self, prefixFragment):
+ """
+ Returns choices
+ """
+
+ # root node of the prefix model must be COMMAND
+ commandNode = prefixFragment[-1]
+ if commandNode.type != Node.COMMAND:
+ return []
+
+ commandName = commandNode.value
+
+ if len(commandNode) == 0:
+ # command has no arguments...
+
+ if len(commandName) == 0:
+ # no name, so propose all commands
+ commands = self.__language_model.commands.values()
+ overlap = 1 # only "\"
+ else:
+ commands = self.__language_model.find_command(commandName)
+
+ if len(commands) == 1 and commands[0].name == commandName:
+ # don't propose when only one command is found and that one
+ # matches the typed one
+ return []
+
+ overlap = len(commandName) + 1 # "\begi"
+
+ return self.__create_proposals_from_commands(commands, overlap)
+
+ # ...command has arguments
+
+ try:
+ self._log.debug(commandNode.xml)
+
+ # find the language model of the command
+ storedCommand = self.__language_model.commands[commandName]
+
+
+ try:
+ argumentNode, storedArgument = self.__match_argument(commandNode, storedCommand)
+ except Exception, e:
+ self._log.error(e)
+ return []
+
+
+ choices = storedArgument.children
+
+ # filter argument matching the already typed argument text
+
+ argumentValue = argumentNode.innerText
+
+ if len(argumentValue):
+ choices = [choice for choice in choices if choice.value.startswith(argumentValue)]
+ overlap = len(argumentValue)
+ else:
+ overlap = 0
+
+ return self.__create_proposals_from_choices(choices, overlap)
+
+ except KeyError:
+ self._log.debug("Command not found: %s" % commandName)
+ return []
+
+ def __match_argument(self, command, model_command):
+ """
+ @param command: the parsed command Node
+ @param model_command: the according model command
+ @return: (matched argument, model argument)
+ """
+ # push the arguments of the model command on a stack
+ model_argument_stack = []
+ model_argument_stack.extend(model_command.children)
+ model_argument_stack.reverse()
+
+ for argument in command:
+ if argument.type == Node.MANDATORY_ARGUMENT:
+
+ # optional arguments in the model may be skipped
+ while True:
+ try:
+ model_argument = model_argument_stack.pop()
+ if model_argument.type != Node.OPTIONAL_ARGUMENT:
+ break
+ except IndexError:
+ # no more optional arguments to skip - signatures can't match
+ raise Exception("Signatures don't match")
+
+ if not argument.closed:
+ return (argument, model_argument)
+
+ elif argument.type == Node.OPTIONAL_ARGUMENT:
+ model_argument = model_argument_stack.pop()
+
+ if model_argument.type != Node.OPTIONAL_ARGUMENT:
+ raise Exception("Signatures don't match")
+
+ if not argument.closed:
+ return (argument, model_argument)
+
+ raise Exception("No matching model argument found")
diff --git a/latex/latex/dialogs.py b/latex/latex/dialogs.py
index 4b726c7..75e6222 100644
--- a/latex/latex/dialogs.py
+++ b/latex/latex/dialogs.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -42,1149 +42,1149 @@ from .listing import LanguagesParser
from . import LaTeXSource
class AbstractProxy(object):
- """
- This may simplify the use of a widget and it serves for state saving
- using the plugins preferences.
- """
- def __init__(self, widget, key):
- """
- @param widget: the widget to proxy
- @param key: a preferences key to use for state saving
- """
- self._widget = widget
- self._key = key
- self._preferences = Preferences()
-
- def restore(self, default):
- """
- Restore the last state of the proxied widget
-
- @param default: a default value if no state is found
- """
- raise NotImplementedError
-
- def save(self):
- """
- Save the state of the proxied widget
- """
- self._preferences.set(self._key, self.value)
-
- @property
- def value(self):
- """
- @return: the current value of the proxied widget
- """
- raise NotImplementedError
+ """
+ This may simplify the use of a widget and it serves for state saving
+ using the plugins preferences.
+ """
+ def __init__(self, widget, key):
+ """
+ @param widget: the widget to proxy
+ @param key: a preferences key to use for state saving
+ """
+ self._widget = widget
+ self._key = key
+ self._preferences = Preferences()
+
+ def restore(self, default):
+ """
+ Restore the last state of the proxied widget
+
+ @param default: a default value if no state is found
+ """
+ raise NotImplementedError
+
+ def save(self):
+ """
+ Save the state of the proxied widget
+ """
+ self._preferences.set(self._key, self.value)
+
+ @property
+ def value(self):
+ """
+ @return: the current value of the proxied widget
+ """
+ raise NotImplementedError
class ComboBoxProxy(AbstractProxy):
- """
- This proxies a ComboBox widget:
-
- p = ComboBoxProxy(self.find_widget("myCombo"), "SomeSetting")
- p.add_option("thing_1", "First Thing")
- p.add_option("thing_2", "Second Thing")
- p.restore("thing_1")
- ...
- """
- def __init__(self, widget, key):
- AbstractProxy.__init__(self, widget, key)
-
- self._store = Gtk.ListStore(str, str) # value, label
- self._widget.set_model(self._store)
- cell = Gtk.CellRendererText()
- self._widget.pack_start(cell, True)
- self._widget.add_attribute(cell, "markup", 1)
-
- self._options = []
-
- def restore(self, default):
- restored_value = self._preferences.get(self._key, default)
- restored_index = 0
- i = 0
- for value, label in self._options:
- if value == restored_value:
- restored_index = i
- break
- i += 1
- self._widget.set_active(restored_index)
-
- self._widget.connect("changed", self._on_changed)
-
- def _on_changed(self, combobox):
- self.save()
-
- # accepts(object, String, String, bool)
- def add_option(self, value, label, show_value=True):
- """
- Add an option to the widget
-
- @param value: a unique value
- @param label: a label text that may contain markup
- """
- self._options.append((value, label))
-
- label_markup = ""
-
- if show_value:
- if not value is None and len(value) > 0:
- label_markup = "%s <span color='%s'>%s</span>" % (value, self._preferences.get("light-foreground-color"), label)
- else:
- label_markup = "<span color='%s'>%s</span>" % (self._preferences.get("light-foreground-color"), label)
- else:
- label_markup = label
-
- self._store.append([value, label_markup])
-
- @property
- def value(self):
- index = self._widget.get_active()
- return self._options[index][0]
-
-
+ """
+ This proxies a ComboBox widget:
+
+ p = ComboBoxProxy(self.find_widget("myCombo"), "SomeSetting")
+ p.add_option("thing_1", "First Thing")
+ p.add_option("thing_2", "Second Thing")
+ p.restore("thing_1")
+ ...
+ """
+ def __init__(self, widget, key):
+ AbstractProxy.__init__(self, widget, key)
+
+ self._store = Gtk.ListStore(str, str) # value, label
+ self._widget.set_model(self._store)
+ cell = Gtk.CellRendererText()
+ self._widget.pack_start(cell, True)
+ self._widget.add_attribute(cell, "markup", 1)
+
+ self._options = []
+
+ def restore(self, default):
+ restored_value = self._preferences.get(self._key, default)
+ restored_index = 0
+ i = 0
+ for value, label in self._options:
+ if value == restored_value:
+ restored_index = i
+ break
+ i += 1
+ self._widget.set_active(restored_index)
+
+ self._widget.connect("changed", self._on_changed)
+
+ def _on_changed(self, combobox):
+ self.save()
+
+ # accepts(object, String, String, bool)
+ def add_option(self, value, label, show_value=True):
+ """
+ Add an option to the widget
+
+ @param value: a unique value
+ @param label: a label text that may contain markup
+ """
+ self._options.append((value, label))
+
+ label_markup = ""
+
+ if show_value:
+ if not value is None and len(value) > 0:
+ label_markup = "%s <span color='%s'>%s</span>" % (value, self._preferences.get("light-foreground-color"), label)
+ else:
+ label_markup = "<span color='%s'>%s</span>" % (self._preferences.get("light-foreground-color"), label)
+ else:
+ label_markup = label
+
+ self._store.append([value, label_markup])
+
+ @property
+ def value(self):
+ index = self._widget.get_active()
+ return self._options[index][0]
+
+
class EntryProxy(AbstractProxy):
- def __init__(self, widget, key):
- AbstractProxy.__init__(self, widget, key)
-
- def restore(self, default):
- self._widget.set_text(self._preferences.get(self._key, default))
-
- @property
- def value(self):
- return self._widget.get_text()
-
+ def __init__(self, widget, key):
+ AbstractProxy.__init__(self, widget, key)
+
+ def restore(self, default):
+ self._widget.set_text(self._preferences.get(self._key, default))
+
+ @property
+ def value(self):
+ return self._widget.get_text()
+
class ChooseMasterDialog(GladeInterface):
- """
- Dialog for choosing a master file to a LaTeX fragment file
- """
- filename = find_resource("ui/choose_master_dialog.ui")
-
- def run(self, folder):
- """
- Runs the dialog and returns the selected filename
-
- @param folder: a folder to initially place the file chooser
- """
- dialog = self.find_widget("dialogSelectMaster")
- file_chooser_button = self.find_widget("filechooserbutton")
- file_chooser_button.set_current_folder(folder)
-
- if dialog.run() == 1:
- filename = file_chooser_button.get_filename()
- else:
- filename = None
- dialog.hide()
-
- return filename
+ """
+ Dialog for choosing a master file to a LaTeX fragment file
+ """
+ filename = find_resource("ui/choose_master_dialog.ui")
+
+ def run(self, folder):
+ """
+ Runs the dialog and returns the selected filename
+
+ @param folder: a folder to initially place the file chooser
+ """
+ dialog = self.find_widget("dialogSelectMaster")
+ file_chooser_button = self.find_widget("filechooserbutton")
+ file_chooser_button.set_current_folder(folder)
+
+ if dialog.run() == 1:
+ filename = file_chooser_button.get_filename()
+ else:
+ filename = None
+ dialog.hide()
+
+ return filename
class NewDocumentDialog(GladeInterface):
- """
- Dialog for creating the body of a new LaTeX document
- """
- filename = find_resource("ui/new_document_template_dialog.ui")
-
- _log = logging.getLogger("NewDocumentWizard")
-
- _PAPER_SIZES = (
- ("a4paper", "A4"),
- ("a5paper", "A5"),
- ("b5paper", "B5"),
- ("executivepaper", "US-Executive"),
- ("legalbl_paper", "US-Legal"),
- ("letterpaper", "US-Letter") )
-
- _DEFAULT_FONT_FAMILIES = (
- ("\\rmdefault", "<span font_family=\"serif\">Roman</span>"),
- ("\\sfdefault", "<span font_family=\"sans\">Sans Serif</span>"),
- ("\\ttdefault", "<span font_family=\"monospace\">Typerwriter</span>") )
-
- _LOCALE_MAPPINGS = {
- "af" : "afrikaans",
- "af_ZA" : "afrikaans",
- "sq" : "albanian",
- "sq_AL" : "albanian",
- "eu" : "basque",
- "eu_ES" : "basque",
- "id" : "bahasai",
- "id_ID" : "bahasai",
- "ms" : "bahasam",
- "ms_MY" : "bahasam",
- "br" : "breton",
- "bg" : "bulgarian",
- "bg_BG" : "bulgarian",
- "ca" : "catalan",
- "ca_ES" : "catalan",
- "hr" : "croatian",
- "hr_HR" : "croatian",
- "cz" : "czech",
- "da" : "danish",
- "da_DK" : "danish",
- "nl" : "dutch",
- "nl_BE" : "dutch",
- "nl_NL" : "dutch",
- "eo" : "esperanto",
- "et" : "estonian",
- "et_EE" : "estonian",
- "en" : "USenglish",
- "en_AU" : "australian",
- "en_CA" : "canadian",
- "en_GB" : "UKenglish",
- "en_US" : "USenglish",
- "en_ZA" : "UKenglish",
- "fi" : "finnish",
- "fi_FI" : "finnish",
- "fr" : "frenchb",
- "fr_FR" : "frenchb",
- "fr_CA" : "canadien",
- "gl" : "galician",
- "gl_ES" : "galician",
- "el" : "greek",
- "el_GR" : "greek",
- "he" : "hebrew",
- "he_IL" : "hebrew",
- "hu" : "magyar",
- "hu_HU" : "magyar",
- "is" : "icelandic",
- "is_IS" : "icelandic",
- "it" : "italian",
- "it_CH" : "italian",
- "it_IT" : "italian",
- "ga" : "irish",
- "la" : "latin",
- "dsb" : "lsorbian",
- "de_DE" : "ngermanb",
- "de_AT" : "naustrian",
- "de_CH" : "ngermanb",
- "nb" : "norsk",
- "no" : "norsk",
- "no_NO" : "norsk",
- "no_NY" : "norsk",
- "nn" : "nynorsk",
- "nn_NO" : "nynorsk",
- "pl" : "polish",
- "pl_PL" : "polish",
- "pt" : "portuguese",
- "pt_BR" : "brazilian",
- "pt_PT" : "portuguese",
- "ro" : "romanian",
- "ro_RO" : "romanian",
- "ru" : "russianb",
- "ru_RU" : "russianb",
- "gd" : "scottish",
- "se" : "samin",
- "se_NO" : "samin",
- "sr" : "serbian",
- "sr_YU" : "serbian",
- "sl" : "slovene",
- "sl_SL" : "slovene",
- "sl_SI" : "slovene",
- "sk" : "slovak",
- "sk_SK" : "slovak",
- "es" : "spanish",
- "es_AR" : "spanish",
- "es_CL" : "spanish",
- "es_CO" : "spanish",
- "es_DO" : "spanish",
- "es_EC" : "spanish",
- "es_ES" : "spanish",
- "es_GT" : "spanish",
- "es_HN" : "spanish",
- "es_LA" : "spanish",
- "es_MX" : "spanish",
- "es_NI" : "spanish",
- "es_PA" : "spanish",
- "es_PE" : "spanish",
- "es_PR" : "spanish",
- "es_SV" : "spanish",
- "es_UY" : "spanish",
- "es_VE" : "spanish",
- "es_UY" : "spanish",
- "sv" : "swedish",
- "sv_SE" : "swedish",
- "sv_SV" : "swedish",
- "tr" : "turkish",
- "tr_TR" : "turkish",
- "uk" : "ukraineb",
- "uk_UA" : "ukraineb",
- "hsb" : "usorbian",
- "cy" : "welsh",
- "cy_GB" : "welsh"
- }
-
- dialog = None
-
- def get_dialog(self):
- """
- Build and return the dialog
- """
- if self.dialog == None:
- preferences = Preferences()
- environment = Environment()
-
- self.dialog = self.find_widget("dialogNewDocument")
-
- #
- # file
- #
- self._entry_name = self.find_widget("entryName")
- self._button_dir = self.find_widget("buttonDirectory")
- self._button_dir.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
-
- #
- # templates
- #
- self._proxy_template = ComboBoxProxy(self.find_widget("comboTemplate"), "RecentTemplate")
-
- folder = Folder(preferences.TEMPLATE_DIR)
-
- templates = folder.files
- templates.sort()
- for template in templates:
- self._proxy_template.add_option(template.path, template.shortbasename, show_value=False)
- self._proxy_template.restore("Default")
-
- #
- # metadata
- #
- self._entry_title = self.find_widget("entryTitle")
- self._entry_author = self.find_widget("entryAuthor")
- self._entry_author.set_text(preferences.get("RecentAuthor", environment.username))
- self._radio_date_custom = self.find_widget("radioCustom")
- self._entry_date = self.find_widget("entryDate")
-
- #
- # document classes
- #
- self._proxy_document_class = ComboBoxProxy(self.find_widget("comboClass"), "RecentDocumentClass")
- document_classes = environment.document_classes
- document_classes.sort(key=lambda x: x.name.lower())
- for c in document_classes:
- self._proxy_document_class.add_option(c.name, c.label)
- self._proxy_document_class.restore("article")
-
- #
- # paper
- #
- self._proxy_paper_size = ComboBoxProxy(self.find_widget("comboPaperSize"), "RecentPaperSize")
- self._proxy_paper_size.add_option("", "Default")
- for size, label in self._PAPER_SIZES:
- self._proxy_paper_size.add_option(size, label)
- self._proxy_paper_size.restore("")
-
-
- self._check_landscape = self.find_widget("checkLandscape")
- self._check_landscape.set_active(preferences.get_bool("RecentPaperLandscape", False))
-
- #
- # font size
- #
- self._radio_font_user = self.find_widget("radioFontUser")
- self._spin_font_size = self.find_widget("spinFontSize")
- self._labelFontSize = self.find_widget("labelFontSize")
-
- #
- # font family
- #
- self._proxy_font_family = ComboBoxProxy(self.find_widget("comboFontFamily"), "RecentDefaultFontFamily")
- for command, label in self._DEFAULT_FONT_FAMILIES:
- self._proxy_font_family.add_option(command, label, False)
- self._proxy_font_family.restore("\\rmdefault")
-
-
- #
- # input encodings
- #
- self._proxy_encoding = ComboBoxProxy(self.find_widget("comboEncoding"), "RecentInputEncoding")
- input_encodings = environment.input_encodings
- input_encodings.sort(key=lambda x: x.name.lower())
- for e in input_encodings:
- self._proxy_encoding.add_option(e.name, e.label)
- self._proxy_encoding.restore("utf8")
-
- #
- # babel packages
- #
- self._proxy_babel = ComboBoxProxy(self.find_widget("comboBabel"), "RecentBabelPackage")
- language_definitions = environment.language_definitions
- language_definitions.sort(key=lambda x: x.name.lower())
- for l in language_definitions:
- self._proxy_babel.add_option(l.name, l.label)
-
- try:
- self._proxy_babel.restore(self._LOCALE_MAPPINGS[environment.language_code])
- except Exception, e:
- self._log.error("Failed to guess babel package: %s" % e)
- self._proxy_babel.restore("english")
-
- #
- # connect signals
- #
- self.connect_signals({ "on_radioCustom_toggled" : self._on_custom_date_toggled,
- "on_radioFontUser_toggled" : self._on_user_font_toggled })
-
- return self.dialog
-
- def _on_custom_date_toggled(self, toggle_button):
- self._entry_date.set_sensitive(toggle_button.get_active())
-
- def _on_user_font_toggled(self, toggle_button):
- self._spin_font_size.set_sensitive(toggle_button.get_active())
- self._labelFontSize.set_sensitive(toggle_button.get_active())
-
- @property
- def source(self):
- """
- Compose and return the source resulting from the dialog
- """
- # document class options
- documentOptions = []
-
- if self._radio_font_user.get_active():
- documentOptions.append("%spt" % self._spin_font_size.get_value_as_int())
-
-# paperSize = self._store_paper_size[self._combo_paper_size.get_active()][0]
-# if len(paperSize) > 0:
-# documentOptions.append(paperSize)
- paperSize = self._proxy_paper_size.value
- if paperSize != "":
- documentOptions.append(paperSize)
-
- if self._check_landscape.get_active():
- documentOptions.append("landscape")
-
- if len(documentOptions) > 0:
- documentOptions = "[" + ",".join(documentOptions) + "]"
- else:
- documentOptions = ""
-
-
-# documentClass = self._store_class[self._combo_class.get_active()][0]
- documentClass = self._proxy_document_class.value
-
- title = self._entry_title.get_text()
- author = self._entry_author.get_text()
-# babelPackage = self._store_babel[self._combo_babel.get_active()][0]
-# inputEncoding = self._store_encoding[self._combo_encoding.get_active()][0]
- babelPackage = self._proxy_babel.value
- inputEncoding = self._proxy_encoding.value
-
- if self._radio_date_custom.get_active():
- date = self._entry_date.get_text()
- else:
- date = "\\today"
-
- if self.find_widget("checkPDFMeta").get_active():
- ifpdf = "\n\\usepackage{ifpdf}\n\\usepackage{hyperref}"
-
- # pdfinfo is discouraged because it
- # - doesn't support special characters like umlauts
- # - is not supported by XeTeX
- # see http://sourceforge.net/tracker/index.php?func=detail&aid=2809478&group_id=204144&atid=988431
-
-# pdfinfo = """
+ """
+ Dialog for creating the body of a new LaTeX document
+ """
+ filename = find_resource("ui/new_document_template_dialog.ui")
+
+ _log = logging.getLogger("NewDocumentWizard")
+
+ _PAPER_SIZES = (
+ ("a4paper", "A4"),
+ ("a5paper", "A5"),
+ ("b5paper", "B5"),
+ ("executivepaper", "US-Executive"),
+ ("legalbl_paper", "US-Legal"),
+ ("letterpaper", "US-Letter") )
+
+ _DEFAULT_FONT_FAMILIES = (
+ ("\\rmdefault", "<span font_family=\"serif\">Roman</span>"),
+ ("\\sfdefault", "<span font_family=\"sans\">Sans Serif</span>"),
+ ("\\ttdefault", "<span font_family=\"monospace\">Typerwriter</span>") )
+
+ _LOCALE_MAPPINGS = {
+ "af" : "afrikaans",
+ "af_ZA" : "afrikaans",
+ "sq" : "albanian",
+ "sq_AL" : "albanian",
+ "eu" : "basque",
+ "eu_ES" : "basque",
+ "id" : "bahasai",
+ "id_ID" : "bahasai",
+ "ms" : "bahasam",
+ "ms_MY" : "bahasam",
+ "br" : "breton",
+ "bg" : "bulgarian",
+ "bg_BG" : "bulgarian",
+ "ca" : "catalan",
+ "ca_ES" : "catalan",
+ "hr" : "croatian",
+ "hr_HR" : "croatian",
+ "cz" : "czech",
+ "da" : "danish",
+ "da_DK" : "danish",
+ "nl" : "dutch",
+ "nl_BE" : "dutch",
+ "nl_NL" : "dutch",
+ "eo" : "esperanto",
+ "et" : "estonian",
+ "et_EE" : "estonian",
+ "en" : "USenglish",
+ "en_AU" : "australian",
+ "en_CA" : "canadian",
+ "en_GB" : "UKenglish",
+ "en_US" : "USenglish",
+ "en_ZA" : "UKenglish",
+ "fi" : "finnish",
+ "fi_FI" : "finnish",
+ "fr" : "frenchb",
+ "fr_FR" : "frenchb",
+ "fr_CA" : "canadien",
+ "gl" : "galician",
+ "gl_ES" : "galician",
+ "el" : "greek",
+ "el_GR" : "greek",
+ "he" : "hebrew",
+ "he_IL" : "hebrew",
+ "hu" : "magyar",
+ "hu_HU" : "magyar",
+ "is" : "icelandic",
+ "is_IS" : "icelandic",
+ "it" : "italian",
+ "it_CH" : "italian",
+ "it_IT" : "italian",
+ "ga" : "irish",
+ "la" : "latin",
+ "dsb" : "lsorbian",
+ "de_DE" : "ngermanb",
+ "de_AT" : "naustrian",
+ "de_CH" : "ngermanb",
+ "nb" : "norsk",
+ "no" : "norsk",
+ "no_NO" : "norsk",
+ "no_NY" : "norsk",
+ "nn" : "nynorsk",
+ "nn_NO" : "nynorsk",
+ "pl" : "polish",
+ "pl_PL" : "polish",
+ "pt" : "portuguese",
+ "pt_BR" : "brazilian",
+ "pt_PT" : "portuguese",
+ "ro" : "romanian",
+ "ro_RO" : "romanian",
+ "ru" : "russianb",
+ "ru_RU" : "russianb",
+ "gd" : "scottish",
+ "se" : "samin",
+ "se_NO" : "samin",
+ "sr" : "serbian",
+ "sr_YU" : "serbian",
+ "sl" : "slovene",
+ "sl_SL" : "slovene",
+ "sl_SI" : "slovene",
+ "sk" : "slovak",
+ "sk_SK" : "slovak",
+ "es" : "spanish",
+ "es_AR" : "spanish",
+ "es_CL" : "spanish",
+ "es_CO" : "spanish",
+ "es_DO" : "spanish",
+ "es_EC" : "spanish",
+ "es_ES" : "spanish",
+ "es_GT" : "spanish",
+ "es_HN" : "spanish",
+ "es_LA" : "spanish",
+ "es_MX" : "spanish",
+ "es_NI" : "spanish",
+ "es_PA" : "spanish",
+ "es_PE" : "spanish",
+ "es_PR" : "spanish",
+ "es_SV" : "spanish",
+ "es_UY" : "spanish",
+ "es_VE" : "spanish",
+ "es_UY" : "spanish",
+ "sv" : "swedish",
+ "sv_SE" : "swedish",
+ "sv_SV" : "swedish",
+ "tr" : "turkish",
+ "tr_TR" : "turkish",
+ "uk" : "ukraineb",
+ "uk_UA" : "ukraineb",
+ "hsb" : "usorbian",
+ "cy" : "welsh",
+ "cy_GB" : "welsh"
+ }
+
+ dialog = None
+
+ def get_dialog(self):
+ """
+ Build and return the dialog
+ """
+ if self.dialog == None:
+ preferences = Preferences()
+ environment = Environment()
+
+ self.dialog = self.find_widget("dialogNewDocument")
+
+ #
+ # file
+ #
+ self._entry_name = self.find_widget("entryName")
+ self._button_dir = self.find_widget("buttonDirectory")
+ self._button_dir.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
+
+ #
+ # templates
+ #
+ self._proxy_template = ComboBoxProxy(self.find_widget("comboTemplate"), "RecentTemplate")
+
+ folder = Folder(preferences.TEMPLATE_DIR)
+
+ templates = folder.files
+ templates.sort()
+ for template in templates:
+ self._proxy_template.add_option(template.path, template.shortbasename, show_value=False)
+ self._proxy_template.restore("Default")
+
+ #
+ # metadata
+ #
+ self._entry_title = self.find_widget("entryTitle")
+ self._entry_author = self.find_widget("entryAuthor")
+ self._entry_author.set_text(preferences.get("RecentAuthor", environment.username))
+ self._radio_date_custom = self.find_widget("radioCustom")
+ self._entry_date = self.find_widget("entryDate")
+
+ #
+ # document classes
+ #
+ self._proxy_document_class = ComboBoxProxy(self.find_widget("comboClass"), "RecentDocumentClass")
+ document_classes = environment.document_classes
+ document_classes.sort(key=lambda x: x.name.lower())
+ for c in document_classes:
+ self._proxy_document_class.add_option(c.name, c.label)
+ self._proxy_document_class.restore("article")
+
+ #
+ # paper
+ #
+ self._proxy_paper_size = ComboBoxProxy(self.find_widget("comboPaperSize"), "RecentPaperSize")
+ self._proxy_paper_size.add_option("", "Default")
+ for size, label in self._PAPER_SIZES:
+ self._proxy_paper_size.add_option(size, label)
+ self._proxy_paper_size.restore("")
+
+
+ self._check_landscape = self.find_widget("checkLandscape")
+ self._check_landscape.set_active(preferences.get_bool("RecentPaperLandscape", False))
+
+ #
+ # font size
+ #
+ self._radio_font_user = self.find_widget("radioFontUser")
+ self._spin_font_size = self.find_widget("spinFontSize")
+ self._labelFontSize = self.find_widget("labelFontSize")
+
+ #
+ # font family
+ #
+ self._proxy_font_family = ComboBoxProxy(self.find_widget("comboFontFamily"), "RecentDefaultFontFamily")
+ for command, label in self._DEFAULT_FONT_FAMILIES:
+ self._proxy_font_family.add_option(command, label, False)
+ self._proxy_font_family.restore("\\rmdefault")
+
+
+ #
+ # input encodings
+ #
+ self._proxy_encoding = ComboBoxProxy(self.find_widget("comboEncoding"), "RecentInputEncoding")
+ input_encodings = environment.input_encodings
+ input_encodings.sort(key=lambda x: x.name.lower())
+ for e in input_encodings:
+ self._proxy_encoding.add_option(e.name, e.label)
+ self._proxy_encoding.restore("utf8")
+
+ #
+ # babel packages
+ #
+ self._proxy_babel = ComboBoxProxy(self.find_widget("comboBabel"), "RecentBabelPackage")
+ language_definitions = environment.language_definitions
+ language_definitions.sort(key=lambda x: x.name.lower())
+ for l in language_definitions:
+ self._proxy_babel.add_option(l.name, l.label)
+
+ try:
+ self._proxy_babel.restore(self._LOCALE_MAPPINGS[environment.language_code])
+ except Exception, e:
+ self._log.error("Failed to guess babel package: %s" % e)
+ self._proxy_babel.restore("english")
+
+ #
+ # connect signals
+ #
+ self.connect_signals({ "on_radioCustom_toggled" : self._on_custom_date_toggled,
+ "on_radioFontUser_toggled" : self._on_user_font_toggled })
+
+ return self.dialog
+
+ def _on_custom_date_toggled(self, toggle_button):
+ self._entry_date.set_sensitive(toggle_button.get_active())
+
+ def _on_user_font_toggled(self, toggle_button):
+ self._spin_font_size.set_sensitive(toggle_button.get_active())
+ self._labelFontSize.set_sensitive(toggle_button.get_active())
+
+ @property
+ def source(self):
+ """
+ Compose and return the source resulting from the dialog
+ """
+ # document class options
+ documentOptions = []
+
+ if self._radio_font_user.get_active():
+ documentOptions.append("%spt" % self._spin_font_size.get_value_as_int())
+
+# paperSize = self._store_paper_size[self._combo_paper_size.get_active()][0]
+# if len(paperSize) > 0:
+# documentOptions.append(paperSize)
+ paperSize = self._proxy_paper_size.value
+ if paperSize != "":
+ documentOptions.append(paperSize)
+
+ if self._check_landscape.get_active():
+ documentOptions.append("landscape")
+
+ if len(documentOptions) > 0:
+ documentOptions = "[" + ",".join(documentOptions) + "]"
+ else:
+ documentOptions = ""
+
+
+# documentClass = self._store_class[self._combo_class.get_active()][0]
+ documentClass = self._proxy_document_class.value
+
+ title = self._entry_title.get_text()
+ author = self._entry_author.get_text()
+# babelPackage = self._store_babel[self._combo_babel.get_active()][0]
+# inputEncoding = self._store_encoding[self._combo_encoding.get_active()][0]
+ babelPackage = self._proxy_babel.value
+ inputEncoding = self._proxy_encoding.value
+
+ if self._radio_date_custom.get_active():
+ date = self._entry_date.get_text()
+ else:
+ date = "\\today"
+
+ if self.find_widget("checkPDFMeta").get_active():
+ ifpdf = "\n\\usepackage{ifpdf}\n\\usepackage{hyperref}"
+
+ # pdfinfo is discouraged because it
+ # - doesn't support special characters like umlauts
+ # - is not supported by XeTeX
+ # see http://sourceforge.net/tracker/index.php?func=detail&aid=2809478&group_id=204144&atid=988431
+
+# pdfinfo = """
#\\ifpdf
-# \\pdfinfo {
-# /Author (%s)
-# /Title (%s)
-# /Subject (SUBJECT)
-# /Keywords (KEYWORDS)
-# /CreationDate (D:%s)
-# }
+# \\pdfinfo {
+# /Author (%s)
+# /Title (%s)
+# /Subject (SUBJECT)
+# /Keywords (KEYWORDS)
+# /CreationDate (D:%s)
+# }
#\\fi""" % (author, title, time.strftime('%Y%m%d%H%M%S'))
- pdfinfo = """
+ pdfinfo = """
\\ifpdf
\\hypersetup{
- pdfauthor={%s},
- pdftitle={%s},
+ pdfauthor={%s},
+ pdftitle={%s},
}
\\fi""" % (author, title)
- else:
- ifpdf = ""
- pdfinfo = ""
-
- if self._proxy_font_family.value == "\\rmdefault":
- default_font_family = "" # \rmdefault is the default value of \familydefault
- else:
- default_font_family = "\n\\renewcommand{\\familydefault}{%s}" % self._proxy_font_family.value
-
- template_string = open(self._proxy_template.value).read()
- template = string.Template(template_string)
- s = template.safe_substitute({
- "DocumentOptions" : documentOptions,
- "DocumentClass" : documentClass,
- "InputEncoding" : inputEncoding,
- "BabelPackage" : babelPackage,
- "AdditionalPackages" : default_font_family + ifpdf,
- "Title" : title,
- "Author" : author,
- "Date" : date,
- "AdditionalPreamble" : pdfinfo})
-
-# s = """\\documentclass%s{%s}
+ else:
+ ifpdf = ""
+ pdfinfo = ""
+
+ if self._proxy_font_family.value == "\\rmdefault":
+ default_font_family = "" # \rmdefault is the default value of \familydefault
+ else:
+ default_font_family = "\n\\renewcommand{\\familydefault}{%s}" % self._proxy_font_family.value
+
+ template_string = open(self._proxy_template.value).read()
+ template = string.Template(template_string)
+ s = template.safe_substitute({
+ "DocumentOptions" : documentOptions,
+ "DocumentClass" : documentClass,
+ "InputEncoding" : inputEncoding,
+ "BabelPackage" : babelPackage,
+ "AdditionalPackages" : default_font_family + ifpdf,
+ "Title" : title,
+ "Author" : author,
+ "Date" : date,
+ "AdditionalPreamble" : pdfinfo})
+
+# s = """\\documentclass%s{%s}
#\\usepackage[%s]{inputenc}
#\\usepackage[%s]{babel}%s%s
#\\title{%s}
#\\author{%s}
#\\date{%s}%s
#\\begin{document}
-#
+#
#\\end{document}""" % (documentOptions, documentClass, inputEncoding, babelPackage, default_font_family, ifpdf, title, author, date, pdfinfo)
-
- return s
-
- @property
- def file(self):
- """
- Return the File object
- """
- return File("%s/%s.tex" % (self._button_dir.get_filename(), self._entry_name.get_text()))
-
- def run(self):
- """
- Runs the dialog
- """
- dialog = self.get_dialog()
- r = dialog.run()
- dialog.hide()
- return r
+
+ return s
+
+ @property
+ def file(self):
+ """
+ Return the File object
+ """
+ return File("%s/%s.tex" % (self._button_dir.get_filename(), self._entry_name.get_text()))
+
+ def run(self):
+ """
+ Runs the dialog
+ """
+ dialog = self.get_dialog()
+ r = dialog.run()
+ dialog.hide()
+ return r
class UseBibliographyDialog(GladeInterface, PreviewRenderer):
- """
- Dialog for inserting a reference to a bibliography
- """
- filename = find_resource("ui/use_bibliography_dialog.ui")
-
- _log = logging.getLogger("UseBibliographyWizard")
-
-
- # sample bibtex content used for rendering the preview
- _BIBTEX = """@book{ dijkstra76,
- title={{A Discipline of Programming}},
- author={Edsger W. Dijkstra},
- year=1976 }
+ """
+ Dialog for inserting a reference to a bibliography
+ """
+ filename = find_resource("ui/use_bibliography_dialog.ui")
+
+ _log = logging.getLogger("UseBibliographyWizard")
+
+
+ # sample bibtex content used for rendering the preview
+ _BIBTEX = """@book{ dijkstra76,
+ title={{A Discipline of Programming}},
+ author={Edsger W. Dijkstra},
+ year=1976 }
@article{ dijkstra68,
- title={{Go to statement considered harmful}},
- author={Edsger W. Dijkstra},
- year=1968 }"""
-
- dialog = None
-
- def run_dialog(self, edited_file):
- """
- Run the dialog
-
- @param edited_file: the File edited the active Editor
- @return: the source resulting from the dialog
- """
- source = None
- dialog = self._get_dialog()
-
- self._file_chooser_button.set_current_folder(edited_file.dirname)
-
- if dialog.run() == 1: # TODO: use gtk constant
- base_dir = edited_file.dirname
-
- file = File(self._file_chooser_button.get_filename())
- source = "\\bibliography{%s}\n\\bibliographystyle{%s}" % (file.relativize_shortname(base_dir),
- self._storeStyle[self._comboStyle.get_active()][0])
-
- dialog.hide()
-
- return source
-
- def _get_dialog(self):
- if not self.dialog:
-
- self.dialog = self.find_widget("dialogUseBibliography")
-
- # bib file
-
- self._file_chooser_button = self.find_widget("filechooserbutton")
-
- # styles
-
- self._storeStyle = Gtk.ListStore(str)
-
- styles = Environment().bibtex_styles
- for style in styles:
- self._storeStyle.append([style.name])
-
- self._comboStyle = self.find_widget("comboboxStyle")
- self._comboStyle.set_model(self._storeStyle)
-
- try:
- recent = styles.index(Preferences().get("RecentBibtexStyle", "plain"))
- except ValueError:
- recent = 0
- self._comboStyle.set_active(recent)
-
- self._imagePreview = self.find_widget("previewimage")
- self._imagePreview.show()
-
- self.connect_signals({ "on_buttonRefresh_clicked" : self._on_refresh_clicked })
-
- return self.dialog
-
- def _on_refresh_clicked(self, widget):
- """
- The button for refreshing the preview has been clicked
- """
- index = self._comboStyle.get_active()
- if index < 0:
- self._log.error("No style selected")
- return
-
- style = self._storeStyle[index][0]
-
- self._imagePreview.set_from_stock(Gtk.STOCK_EXECUTE, Gtk.IconSize.BUTTON)
-
- # create temporary bibtex file
- self._tempFile = tempfile.NamedTemporaryFile(mode="w", suffix=".bib")
- self._tempFile.write(self._BIBTEX)
- self._tempFile.flush()
-
- filename = self._tempFile.name
- self._filenameBase = os.path.splitext(os.path.basename(filename))[0]
-
- # build preview image
- self.render("Book \\cite{dijkstra76} Article \\cite{dijkstra68} \\bibliography{%s}\\bibliographystyle{%s}" % (self._filenameBase,
- style))
- def _on_render_succeeded(self, pixbuf):
- # PreviewRenderer._on_render_succeeded
- self._imagePreview.set_from_pixbuf(pixbuf)
- # remove the temp bib file
- self._tempFile.close()
-
- def _on_render_failed(self):
- # PreviewRenderer._on_render_failed
-
- # set a default icon as preview
- self._imagePreview.set_from_stock(Gtk.STOCK_STOP, Gtk.IconSize.BUTTON)
- # remove the temp bib file
- self._tempFile.close()
+ title={{Go to statement considered harmful}},
+ author={Edsger W. Dijkstra},
+ year=1968 }"""
+
+ dialog = None
+
+ def run_dialog(self, edited_file):
+ """
+ Run the dialog
+
+ @param edited_file: the File edited the active Editor
+ @return: the source resulting from the dialog
+ """
+ source = None
+ dialog = self._get_dialog()
+
+ self._file_chooser_button.set_current_folder(edited_file.dirname)
+
+ if dialog.run() == 1: # TODO: use gtk constant
+ base_dir = edited_file.dirname
+
+ file = File(self._file_chooser_button.get_filename())
+ source = "\\bibliography{%s}\n\\bibliographystyle{%s}" % (file.relativize_shortname(base_dir),
+ self._storeStyle[self._comboStyle.get_active()][0])
+
+ dialog.hide()
+
+ return source
+
+ def _get_dialog(self):
+ if not self.dialog:
+
+ self.dialog = self.find_widget("dialogUseBibliography")
+
+ # bib file
+
+ self._file_chooser_button = self.find_widget("filechooserbutton")
+
+ # styles
+
+ self._storeStyle = Gtk.ListStore(str)
+
+ styles = Environment().bibtex_styles
+ for style in styles:
+ self._storeStyle.append([style.name])
+
+ self._comboStyle = self.find_widget("comboboxStyle")
+ self._comboStyle.set_model(self._storeStyle)
+
+ try:
+ recent = styles.index(Preferences().get("RecentBibtexStyle", "plain"))
+ except ValueError:
+ recent = 0
+ self._comboStyle.set_active(recent)
+
+ self._imagePreview = self.find_widget("previewimage")
+ self._imagePreview.show()
+
+ self.connect_signals({ "on_buttonRefresh_clicked" : self._on_refresh_clicked })
+
+ return self.dialog
+
+ def _on_refresh_clicked(self, widget):
+ """
+ The button for refreshing the preview has been clicked
+ """
+ index = self._comboStyle.get_active()
+ if index < 0:
+ self._log.error("No style selected")
+ return
+
+ style = self._storeStyle[index][0]
+
+ self._imagePreview.set_from_stock(Gtk.STOCK_EXECUTE, Gtk.IconSize.BUTTON)
+
+ # create temporary bibtex file
+ self._tempFile = tempfile.NamedTemporaryFile(mode="w", suffix=".bib")
+ self._tempFile.write(self._BIBTEX)
+ self._tempFile.flush()
+
+ filename = self._tempFile.name
+ self._filenameBase = os.path.splitext(os.path.basename(filename))[0]
+
+ # build preview image
+ self.render("Book \\cite{dijkstra76} Article \\cite{dijkstra68} \\bibliography{%s}\\bibliographystyle{%s}" % (self._filenameBase,
+ style))
+ def _on_render_succeeded(self, pixbuf):
+ # PreviewRenderer._on_render_succeeded
+ self._imagePreview.set_from_pixbuf(pixbuf)
+ # remove the temp bib file
+ self._tempFile.close()
+
+ def _on_render_failed(self):
+ # PreviewRenderer._on_render_failed
+
+ # set a default icon as preview
+ self._imagePreview.set_from_stock(Gtk.STOCK_STOP, Gtk.IconSize.BUTTON)
+ # remove the temp bib file
+ self._tempFile.close()
class InsertGraphicsDialog(GladeInterface):
-
- _PREVIEW_WIDTH, _PREVIEW_HEIGHT = 128, 128
- filename = find_resource("ui/insert_graphics_dialog.ui")
- _dialog = None
-
- def run(self, edited_file):
- """
- @param edited_file: the File currently edited
- """
- dialog = self.__get_dialog()
-
- source = None
-
- if dialog.run() == 1:
- #filename = self._fileChooser.get_filename()
-
- file = File(self._fileChooser.get_filename())
- relative_filename = file.relativize(edited_file.dirname)
-
- # for eps and pdf the extension should be omitted
- # (see http://www.tex.ac.uk/cgi-bin/texfaq2html?label=graph-pspdf)
- ext = os.path.splitext(relative_filename)[1]
- if ext == ".pdf" or ext == ".eps":
- relative_filename = os.path.splitext(relative_filename)[0]
-
- width = "%.2f" % round(self.find_widget("spinbuttonWidth").get_value() / 100.0, 2)
- caption = self.find_widget("entryCaption").get_text()
- label = self.find_widget("entryLabel").get_text()
- floating = self.find_widget("checkbuttonFloat").get_active()
- spread = self.find_widget("checkbuttonSpread").get_active()
- relativeTo = self._comboRelative.get_active()
- rotate = self.find_widget("spinRotate").get_value()
- flip = self.find_widget("checkFlip").get_active()
-
- source = ""
- packages = ["graphicx"]
-
- if relativeTo == 0: # relative to image size
- options = "scale=%s" % width
- else: # relative to text width
- options = "width=%s\\textwidth" % width
-
- if rotate != 0:
- options += ", angle=%s" % rotate
-
- includegraphics = "\\includegraphics[%s]{%s}" % (options, relative_filename)
-
- if flip:
- includegraphics = "\\reflectbox{%s}" % includegraphics
-
- if floating:
- if spread:
- ast = "*"
- else:
- ast = ""
- source += "\\begin{figure%s}[ht]\n\t\\centering\n\t%s" % (ast, includegraphics)
- if len(caption):
- source += "\n\t\\caption{%s}" % caption
- if len(label) and label != "fig:":
- source += "\n\t\\label{%s}" % label
- source += "\n\\end{figure%s}" % ast
- else:
- source += "\\begin{center}\n\t%s" % includegraphics
- if len(caption):
- source += "\n\t\\captionof{figure}{%s}" % caption
- packages.append("caption")
- if len(label) and label != "fig:":
- source += "\n\t\\label{%s}" % label
- source += "\n\\end{center}"
-
-# viewAdapter.insertText(source)
-# viewAdapter.ensurePackages(packages)
-
- source = LaTeXSource(source, packages)
-
- dialog.hide()
-
- return source
-
- def __get_dialog(self):
- if not self._dialog:
-
- # setup the dialog
-
- self._dialog = self.find_widget("dialogInsertGraphics")
-
- previewImage = Gtk.Image()
- self._fileChooser = self.find_widget("FileChooser")
- self._fileChooser.set_preview_widget(previewImage)
- self._fileChooser.connect("update-preview", self.__update_preview, previewImage)
-
- #self._fileChooser.get_property("dialog").connect("response", self._fileChooserResponse) # FIXME: not readable
- #self._fileChooser.connect("file-activated", self._fileActivated) # FIXME: doesn't work
-
- self._okayButton = self.find_widget("buttonOkay")
-
- self._comboRelative = self.find_widget("comboRelative")
- self._comboRelative.set_active(0)
-
- return self._dialog
-
- def __update_preview(self, fileChooser, previewImage):
- """
- Update the FileChooser's preview image
- """
- filename = fileChooser.get_preview_filename()
-
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, self._PREVIEW_WIDTH, self._PREVIEW_HEIGHT)
- previewImage.set_from_pixbuf(pixbuf)
- fileChooser.set_preview_widget_active(True)
- except:
- fileChooser.set_preview_widget_active(False)
+
+ _PREVIEW_WIDTH, _PREVIEW_HEIGHT = 128, 128
+ filename = find_resource("ui/insert_graphics_dialog.ui")
+ _dialog = None
+
+ def run(self, edited_file):
+ """
+ @param edited_file: the File currently edited
+ """
+ dialog = self.__get_dialog()
+
+ source = None
+
+ if dialog.run() == 1:
+ #filename = self._fileChooser.get_filename()
+
+ file = File(self._fileChooser.get_filename())
+ relative_filename = file.relativize(edited_file.dirname)
+
+ # for eps and pdf the extension should be omitted
+ # (see http://www.tex.ac.uk/cgi-bin/texfaq2html?label=graph-pspdf)
+ ext = os.path.splitext(relative_filename)[1]
+ if ext == ".pdf" or ext == ".eps":
+ relative_filename = os.path.splitext(relative_filename)[0]
+
+ width = "%.2f" % round(self.find_widget("spinbuttonWidth").get_value() / 100.0, 2)
+ caption = self.find_widget("entryCaption").get_text()
+ label = self.find_widget("entryLabel").get_text()
+ floating = self.find_widget("checkbuttonFloat").get_active()
+ spread = self.find_widget("checkbuttonSpread").get_active()
+ relativeTo = self._comboRelative.get_active()
+ rotate = self.find_widget("spinRotate").get_value()
+ flip = self.find_widget("checkFlip").get_active()
+
+ source = ""
+ packages = ["graphicx"]
+
+ if relativeTo == 0: # relative to image size
+ options = "scale=%s" % width
+ else: # relative to text width
+ options = "width=%s\\textwidth" % width
+
+ if rotate != 0:
+ options += ", angle=%s" % rotate
+
+ includegraphics = "\\includegraphics[%s]{%s}" % (options, relative_filename)
+
+ if flip:
+ includegraphics = "\\reflectbox{%s}" % includegraphics
+
+ if floating:
+ if spread:
+ ast = "*"
+ else:
+ ast = ""
+ source += "\\begin{figure%s}[ht]\n\t\\centering\n\t%s" % (ast, includegraphics)
+ if len(caption):
+ source += "\n\t\\caption{%s}" % caption
+ if len(label) and label != "fig:":
+ source += "\n\t\\label{%s}" % label
+ source += "\n\\end{figure%s}" % ast
+ else:
+ source += "\\begin{center}\n\t%s" % includegraphics
+ if len(caption):
+ source += "\n\t\\captionof{figure}{%s}" % caption
+ packages.append("caption")
+ if len(label) and label != "fig:":
+ source += "\n\t\\label{%s}" % label
+ source += "\n\\end{center}"
+
+# viewAdapter.insertText(source)
+# viewAdapter.ensurePackages(packages)
+
+ source = LaTeXSource(source, packages)
+
+ dialog.hide()
+
+ return source
+
+ def __get_dialog(self):
+ if not self._dialog:
+
+ # setup the dialog
+
+ self._dialog = self.find_widget("dialogInsertGraphics")
+
+ previewImage = Gtk.Image()
+ self._fileChooser = self.find_widget("FileChooser")
+ self._fileChooser.set_preview_widget(previewImage)
+ self._fileChooser.connect("update-preview", self.__update_preview, previewImage)
+
+ #self._fileChooser.get_property("dialog").connect("response", self._fileChooserResponse) # FIXME: not readable
+ #self._fileChooser.connect("file-activated", self._fileActivated) # FIXME: doesn't work
+
+ self._okayButton = self.find_widget("buttonOkay")
+
+ self._comboRelative = self.find_widget("comboRelative")
+ self._comboRelative.set_active(0)
+
+ return self._dialog
+
+ def __update_preview(self, fileChooser, previewImage):
+ """
+ Update the FileChooser's preview image
+ """
+ filename = fileChooser.get_preview_filename()
+
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, self._PREVIEW_WIDTH, self._PREVIEW_HEIGHT)
+ previewImage.set_from_pixbuf(pixbuf)
+ fileChooser.set_preview_widget_active(True)
+ except:
+ fileChooser.set_preview_widget_active(False)
class InsertTableDialog(GladeInterface):
- """
- This is used to include tables and matrices
- """
-
- filename = find_resource("ui/insert_table_dialog.ui")
- _dialog = None
-
- def run(self):
- dialog = self.__get_dialog()
-
- source = None
-
- if dialog.run() == 1:
- floating = self.find_widget("checkbuttonFloat").get_active()
- rows = int(self.find_widget("spinbuttonRows").get_value())
- cols = int(self.find_widget("spinbuttonColumns").get_value())
- caption = self.find_widget("entryCaption").get_text()
- label = self.find_widget("entryLabel").get_text()
-
- s = ""
-
- if self.find_widget("radiobuttonTable").get_active():
- # table
-
- layout = "l" * cols
-
- if floating:
- s += "\\begin{table}[ht]\n\t\\centering\n\t\\begin{tabular}{%s}%s\n\t\\end{tabular}" % (layout,
- self.__build_table_body(rows, cols, "\t\t"))
-
- if len(caption):
- s += "\n\t\\caption{%s}" % caption
-
- if len(label) and label != "tab:":
- s += "\n\t\\label{%s}" % label
-
- s += "\n\\end{table}"
-
- else:
- s += "\\begin{center}\n\t\\begin{tabular}{%s}%s\n\t\\end{tabular}" % (layout,
- self.__build_table_body(rows, cols, "\t\t"))
-
- if len(caption):
- s += "\n\t\\captionof{table}{%s}" % caption
-
- if len(label):
- s += "\n\t\\label{%s}" % label
-
- s += "\n\\end{center}"
- packages = []
- else:
- environ = self._storeDelims[self._comboDelims.get_active()][2]
- s = "\\begin{%s}%s\n\\end{%s}" % (environ, self.__build_table_body(rows, cols, "\t\t"), environ)
- packages = ["amsmath"]
-
- source = LaTeXSource(Template(s), packages)
-
- dialog.hide()
-
- return source
-
- def __get_dialog(self):
- if not self._dialog:
- self._dialog = self.find_widget("dialogInsertTable")
-
- # delimiters
- self._storeDelims = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) # icon, label, environment
-
- self._storeDelims.append([None, "None", "matrix"])
-
- delimiters = [("parantheses", "Parantheses", "pmatrix"),
- ("brackets", "Brackets", "bmatrix"),
- ("braces", "Braces", "Bmatrix"),
- ("vbars", "Vertical Bars", "vmatrix"),
- ("dvbars", "Double Vertical Bars", "Vmatrix")]
-
- for d in delimiters:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/%s.png" % d[0]))
- self._storeDelims.append([pixbuf, d[1], d[2]])
-
- self._comboDelims = self.find_widget("comboDelims")
- self._comboDelims.set_model(self._storeDelims)
-
- cellPixbuf = Gtk.CellRendererPixbuf()
- self._comboDelims.pack_start(cellPixbuf, False)
- self._comboDelims.add_attribute(cellPixbuf, 'pixbuf', 0)
-
- cellText = Gtk.CellRendererText()
- self._comboDelims.pack_start(cellText, False)
- self._comboDelims.add_attribute(cellText, 'text', 1)
-
- self._comboDelims.set_active(0)
-
- self.connect_signals({ "on_radioMatrix_toggled" : self.__matrix_toggled })
-
- return self._dialog
-
- def __matrix_toggled(self, toggleButton):
- self._comboDelims.set_sensitive(toggleButton.get_active())
-
- def __build_table_body(self, rows, cols, indent):
- """
- This builds the body of a table template according to the number of rows and
- columns.
- """
-
- # FIXME: create unique placeholders for each cell for multi-placeholder feature
-
- s = ""
- for i in range(rows):
- colList = []
- for j in range(cols):
- colList.append("${%s%s}" % (i + 1, j + 1))
- s += "\n" + indent + " & ".join(colList) + " \\\\"
- return s
+ """
+ This is used to include tables and matrices
+ """
+
+ filename = find_resource("ui/insert_table_dialog.ui")
+ _dialog = None
+
+ def run(self):
+ dialog = self.__get_dialog()
+
+ source = None
+
+ if dialog.run() == 1:
+ floating = self.find_widget("checkbuttonFloat").get_active()
+ rows = int(self.find_widget("spinbuttonRows").get_value())
+ cols = int(self.find_widget("spinbuttonColumns").get_value())
+ caption = self.find_widget("entryCaption").get_text()
+ label = self.find_widget("entryLabel").get_text()
+
+ s = ""
+
+ if self.find_widget("radiobuttonTable").get_active():
+ # table
+
+ layout = "l" * cols
+
+ if floating:
+ s += "\\begin{table}[ht]\n\t\\centering\n\t\\begin{tabular}{%s}%s\n\t\\end{tabular}" % (layout,
+ self.__build_table_body(rows, cols, "\t\t"))
+
+ if len(caption):
+ s += "\n\t\\caption{%s}" % caption
+
+ if len(label) and label != "tab:":
+ s += "\n\t\\label{%s}" % label
+
+ s += "\n\\end{table}"
+
+ else:
+ s += "\\begin{center}\n\t\\begin{tabular}{%s}%s\n\t\\end{tabular}" % (layout,
+ self.__build_table_body(rows, cols, "\t\t"))
+
+ if len(caption):
+ s += "\n\t\\captionof{table}{%s}" % caption
+
+ if len(label):
+ s += "\n\t\\label{%s}" % label
+
+ s += "\n\\end{center}"
+ packages = []
+ else:
+ environ = self._storeDelims[self._comboDelims.get_active()][2]
+ s = "\\begin{%s}%s\n\\end{%s}" % (environ, self.__build_table_body(rows, cols, "\t\t"), environ)
+ packages = ["amsmath"]
+
+ source = LaTeXSource(Template(s), packages)
+
+ dialog.hide()
+
+ return source
+
+ def __get_dialog(self):
+ if not self._dialog:
+ self._dialog = self.find_widget("dialogInsertTable")
+
+ # delimiters
+ self._storeDelims = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) # icon, label, environment
+
+ self._storeDelims.append([None, "None", "matrix"])
+
+ delimiters = [("parantheses", "Parantheses", "pmatrix"),
+ ("brackets", "Brackets", "bmatrix"),
+ ("braces", "Braces", "Bmatrix"),
+ ("vbars", "Vertical Bars", "vmatrix"),
+ ("dvbars", "Double Vertical Bars", "Vmatrix")]
+
+ for d in delimiters:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/%s.png" % d[0]))
+ self._storeDelims.append([pixbuf, d[1], d[2]])
+
+ self._comboDelims = self.find_widget("comboDelims")
+ self._comboDelims.set_model(self._storeDelims)
+
+ cellPixbuf = Gtk.CellRendererPixbuf()
+ self._comboDelims.pack_start(cellPixbuf, False)
+ self._comboDelims.add_attribute(cellPixbuf, 'pixbuf', 0)
+
+ cellText = Gtk.CellRendererText()
+ self._comboDelims.pack_start(cellText, False)
+ self._comboDelims.add_attribute(cellText, 'text', 1)
+
+ self._comboDelims.set_active(0)
+
+ self.connect_signals({ "on_radioMatrix_toggled" : self.__matrix_toggled })
+
+ return self._dialog
+
+ def __matrix_toggled(self, toggleButton):
+ self._comboDelims.set_sensitive(toggleButton.get_active())
+
+ def __build_table_body(self, rows, cols, indent):
+ """
+ This builds the body of a table template according to the number of rows and
+ columns.
+ """
+
+ # FIXME: create unique placeholders for each cell for multi-placeholder feature
+
+ s = ""
+ for i in range(rows):
+ colList = []
+ for j in range(cols):
+ colList.append("${%s%s}" % (i + 1, j + 1))
+ s += "\n" + indent + " & ".join(colList) + " \\\\"
+ return s
class InsertListingDialog(GladeInterface):
- """
- """
-
- filename = find_resource("ui/insert_listing_dialog.ui")
- _dialog = None
-
- def run(self, edited_file):
- """
- @param edited_file: the File currently edited
- """
- dialog = self.__get_dialog()
-
- source = None
-
- if dialog.run() == 1:
-
- lstset = ""
- options = ""
-
- language = self._storeLanguages[self._comboLanguage.get_active()][0]
- try:
- dialect = self._storeDialects[self._comboDialect.get_active()][0]
- except IndexError:
- dialect = ""
-
- if self._dialectsEnabled and len(dialect):
- # we need the lstset command
- lstset = "\\lstset{language=[%s]%s}\n" % (dialect, language)
- else:
- options = "[language=%s]" % language
-
-
- if self._checkFile.get_active():
- file = File(self._fileChooserButton.get_filename())
- relative_filename = file.relativize(edited_file.dirname)
-
- source = "%s\\lstinputlisting%s{%s}" % (lstset, options, relative_filename)
-
- else:
- source = "%s\\begin{lstlisting}%s\n\t$_\n\\end{lstlisting}" % (lstset, options)
-
- source = LaTeXSource(Template(source), ["listings"])
-
- dialog.hide()
-
- return source
-
- def __get_dialog(self):
- if not self._dialog:
- self._dialog = self.find_widget("dialogListing")
-
- self._fileChooserButton = self.find_widget("fileChooserButton")
-
- #
- # languages
- #
- self._languages = []
- parser = LanguagesParser()
- parser.parse(self._languages, find_resource("listings.xml"))
-
- recentLanguage = Preferences().get("RecentListingLanguage", "Java")
-
- self._storeLanguages = Gtk.ListStore(str)
- recentLanguageIndex = 0
- i = 0
- for l in self._languages:
- self._storeLanguages.append([l.name])
- if l.name == recentLanguage:
- recentLanguageIndex = i
- i += 1
-
- self._comboLanguage = self.find_widget("comboLanguage")
- self._comboLanguage.set_model(self._storeLanguages)
- cell = Gtk.CellRendererText()
- self._comboLanguage.pack_start(cell, True)
- self._comboLanguage.add_attribute(cell, "text", 0)
-
- #
- # dialects
- #
- self._labelDialect = self.find_widget("labelDialect")
-
- self._storeDialects = Gtk.ListStore(str)
-
- self._comboDialect = self.find_widget("comboDialect")
- self._comboDialect.set_model(self._storeDialects)
- cell = Gtk.CellRendererText()
- self._comboDialect.pack_start(cell, True)
- self._comboDialect.add_attribute(cell, "text", 0)
-
- self._checkFile = self.find_widget("checkFile")
-
- self.connect_signals({ "on_checkFile_toggled" : self._fileToggled,
- "on_comboLanguage_changed" : self._languagesChanged })
-
-
- self._comboLanguage.set_active(recentLanguageIndex)
-
- self._dialectsEnabled = False
-
- return self._dialog
-
- def _languagesChanged(self, comboBox):
- language = self._languages[comboBox.get_active()]
-
- self._storeDialects.clear()
-
- if len(language.dialects):
- i = 0
- for dialect in language.dialects:
- self._storeDialects.append([dialect.name])
- if dialect.default:
- self._comboDialect.set_active(i)
- i += 1
-
- self._labelDialect.set_sensitive(True)
- self._comboDialect.set_sensitive(True)
-
- self._dialectsEnabled = True
- else:
- self._labelDialect.set_sensitive(False)
- self._comboDialect.set_sensitive(False)
-
- self._dialectsEnabled = False
-
- def _fileToggled(self, toggleButton):
- self._fileChooserButton.set_sensitive(toggleButton.get_active())
-
- def _specificToggled(self, toggleButton):
- self._comboLanguage.set_sensitive(toggleButton.get_active())
- self._labelDialect.set_sensitive(toggleButton.get_active() and self._dialectsEnabled)
- self._comboDialect.set_sensitive(toggleButton.get_active() and self._dialectsEnabled)
+ """
+ """
+
+ filename = find_resource("ui/insert_listing_dialog.ui")
+ _dialog = None
+
+ def run(self, edited_file):
+ """
+ @param edited_file: the File currently edited
+ """
+ dialog = self.__get_dialog()
+
+ source = None
+
+ if dialog.run() == 1:
+
+ lstset = ""
+ options = ""
+
+ language = self._storeLanguages[self._comboLanguage.get_active()][0]
+ try:
+ dialect = self._storeDialects[self._comboDialect.get_active()][0]
+ except IndexError:
+ dialect = ""
+
+ if self._dialectsEnabled and len(dialect):
+ # we need the lstset command
+ lstset = "\\lstset{language=[%s]%s}\n" % (dialect, language)
+ else:
+ options = "[language=%s]" % language
+
+
+ if self._checkFile.get_active():
+ file = File(self._fileChooserButton.get_filename())
+ relative_filename = file.relativize(edited_file.dirname)
+
+ source = "%s\\lstinputlisting%s{%s}" % (lstset, options, relative_filename)
+
+ else:
+ source = "%s\\begin{lstlisting}%s\n\t$_\n\\end{lstlisting}" % (lstset, options)
+
+ source = LaTeXSource(Template(source), ["listings"])
+
+ dialog.hide()
+
+ return source
+
+ def __get_dialog(self):
+ if not self._dialog:
+ self._dialog = self.find_widget("dialogListing")
+
+ self._fileChooserButton = self.find_widget("fileChooserButton")
+
+ #
+ # languages
+ #
+ self._languages = []
+ parser = LanguagesParser()
+ parser.parse(self._languages, find_resource("listings.xml"))
+
+ recentLanguage = Preferences().get("RecentListingLanguage", "Java")
+
+ self._storeLanguages = Gtk.ListStore(str)
+ recentLanguageIndex = 0
+ i = 0
+ for l in self._languages:
+ self._storeLanguages.append([l.name])
+ if l.name == recentLanguage:
+ recentLanguageIndex = i
+ i += 1
+
+ self._comboLanguage = self.find_widget("comboLanguage")
+ self._comboLanguage.set_model(self._storeLanguages)
+ cell = Gtk.CellRendererText()
+ self._comboLanguage.pack_start(cell, True)
+ self._comboLanguage.add_attribute(cell, "text", 0)
+
+ #
+ # dialects
+ #
+ self._labelDialect = self.find_widget("labelDialect")
+
+ self._storeDialects = Gtk.ListStore(str)
+
+ self._comboDialect = self.find_widget("comboDialect")
+ self._comboDialect.set_model(self._storeDialects)
+ cell = Gtk.CellRendererText()
+ self._comboDialect.pack_start(cell, True)
+ self._comboDialect.add_attribute(cell, "text", 0)
+
+ self._checkFile = self.find_widget("checkFile")
+
+ self.connect_signals({ "on_checkFile_toggled" : self._fileToggled,
+ "on_comboLanguage_changed" : self._languagesChanged })
+
+
+ self._comboLanguage.set_active(recentLanguageIndex)
+
+ self._dialectsEnabled = False
+
+ return self._dialog
+
+ def _languagesChanged(self, comboBox):
+ language = self._languages[comboBox.get_active()]
+
+ self._storeDialects.clear()
+
+ if len(language.dialects):
+ i = 0
+ for dialect in language.dialects:
+ self._storeDialects.append([dialect.name])
+ if dialect.default:
+ self._comboDialect.set_active(i)
+ i += 1
+
+ self._labelDialect.set_sensitive(True)
+ self._comboDialect.set_sensitive(True)
+
+ self._dialectsEnabled = True
+ else:
+ self._labelDialect.set_sensitive(False)
+ self._comboDialect.set_sensitive(False)
+
+ self._dialectsEnabled = False
+
+ def _fileToggled(self, toggleButton):
+ self._fileChooserButton.set_sensitive(toggleButton.get_active())
+
+ def _specificToggled(self, toggleButton):
+ self._comboLanguage.set_sensitive(toggleButton.get_active())
+ self._labelDialect.set_sensitive(toggleButton.get_active() and self._dialectsEnabled)
+ self._comboDialect.set_sensitive(toggleButton.get_active() and self._dialectsEnabled)
class BuildImageDialog(GladeInterface):
- """
- Render the document to an image
- """
-
- filename = find_resource("ui/build_image_dialog.ui")
- _dialog = None
- _generator = ImageToolGenerator()
-
- def run(self):
- dialog = self._getDialog()
-
- if dialog.run() == 1:
-
- if self.find_widget("radioPNG").get_active():
- self._generator.format = ImageToolGenerator.FORMAT_PNG
- self._generator.pngMode = self._storeMode[self._comboMode.get_active()][1]
- elif self.find_widget("radioJPEG").get_active():
- self._generator.format = ImageToolGenerator.FORMAT_JPEG
- elif self.find_widget("radioGIF").get_active():
- self._generator.format = ImageToolGenerator.FORMAT_GIF
-
- self._generator.open = True
- self._generator.resolution = self._spinResolution.get_value_as_int()
- self._generator.antialiasFactor = self._storeAntialias[self._comboAntialias.get_active()][1]
- self._generator.render_box = self.find_widget("radioBox").get_active()
-
- generate = True
- else:
- generate = False
-
- dialog.hide()
-
- if generate:
- return self._generator.generate()
- else:
- return None
-
- def _getDialog(self):
- if not self._dialog:
- self._dialog = self.find_widget("dialogRenderImage")
- self.find_widget("table").set_col_spacing(0, 20)
-
- # PNG mode
-
- self._storeMode = Gtk.ListStore(str, int) # label, mode constant
- self._storeMode.append(["Monochrome", ImageToolGenerator.PNG_MODE_MONOCHROME])
- self._storeMode.append(["Grayscale", ImageToolGenerator.PNG_MODE_GRAYSCALE])
- self._storeMode.append(["RGB", ImageToolGenerator.PNG_MODE_RGB])
- self._storeMode.append(["RGBA", ImageToolGenerator.PNG_MODE_RGBA])
-
- self._comboMode = self.find_widget("comboMode")
- self._comboMode.set_model(self._storeMode)
- cell = Gtk.CellRendererText()
- self._comboMode.pack_start(cell, True)
- self._comboMode.add_attribute(cell, "text", 0)
- self._comboMode.set_active(3)
-
- # anti-alias
-
- self._storeAntialias = Gtk.ListStore(str, int) # label, factor
- self._storeAntialias.append(["Off", 0])
- self._storeAntialias.append(["1x", 1])
- self._storeAntialias.append(["2x", 2])
- self._storeAntialias.append(["4x", 4])
- self._storeAntialias.append(["8x", 8])
-
- self._comboAntialias = self.find_widget("comboAntialias")
- self._comboAntialias.set_model(self._storeAntialias)
- cell = Gtk.CellRendererText()
- self._comboAntialias.pack_start(cell, True)
- self._comboAntialias.add_attribute(cell, "text", 0)
- self._comboAntialias.set_active(3)
-
- # resolution
-
- self._spinResolution = self.find_widget("spinResolution")
- self._spinResolution.set_value(Environment().screen_dpi)
-
- return self._dialog
-
+ """
+ Render the document to an image
+ """
+
+ filename = find_resource("ui/build_image_dialog.ui")
+ _dialog = None
+ _generator = ImageToolGenerator()
+
+ def run(self):
+ dialog = self._getDialog()
+
+ if dialog.run() == 1:
+
+ if self.find_widget("radioPNG").get_active():
+ self._generator.format = ImageToolGenerator.FORMAT_PNG
+ self._generator.pngMode = self._storeMode[self._comboMode.get_active()][1]
+ elif self.find_widget("radioJPEG").get_active():
+ self._generator.format = ImageToolGenerator.FORMAT_JPEG
+ elif self.find_widget("radioGIF").get_active():
+ self._generator.format = ImageToolGenerator.FORMAT_GIF
+
+ self._generator.open = True
+ self._generator.resolution = self._spinResolution.get_value_as_int()
+ self._generator.antialiasFactor = self._storeAntialias[self._comboAntialias.get_active()][1]
+ self._generator.render_box = self.find_widget("radioBox").get_active()
+
+ generate = True
+ else:
+ generate = False
+
+ dialog.hide()
+
+ if generate:
+ return self._generator.generate()
+ else:
+ return None
+
+ def _getDialog(self):
+ if not self._dialog:
+ self._dialog = self.find_widget("dialogRenderImage")
+ self.find_widget("table").set_col_spacing(0, 20)
+
+ # PNG mode
+
+ self._storeMode = Gtk.ListStore(str, int) # label, mode constant
+ self._storeMode.append(["Monochrome", ImageToolGenerator.PNG_MODE_MONOCHROME])
+ self._storeMode.append(["Grayscale", ImageToolGenerator.PNG_MODE_GRAYSCALE])
+ self._storeMode.append(["RGB", ImageToolGenerator.PNG_MODE_RGB])
+ self._storeMode.append(["RGBA", ImageToolGenerator.PNG_MODE_RGBA])
+
+ self._comboMode = self.find_widget("comboMode")
+ self._comboMode.set_model(self._storeMode)
+ cell = Gtk.CellRendererText()
+ self._comboMode.pack_start(cell, True)
+ self._comboMode.add_attribute(cell, "text", 0)
+ self._comboMode.set_active(3)
+
+ # anti-alias
+
+ self._storeAntialias = Gtk.ListStore(str, int) # label, factor
+ self._storeAntialias.append(["Off", 0])
+ self._storeAntialias.append(["1x", 1])
+ self._storeAntialias.append(["2x", 2])
+ self._storeAntialias.append(["4x", 4])
+ self._storeAntialias.append(["8x", 8])
+
+ self._comboAntialias = self.find_widget("comboAntialias")
+ self._comboAntialias.set_model(self._storeAntialias)
+ cell = Gtk.CellRendererText()
+ self._comboAntialias.pack_start(cell, True)
+ self._comboAntialias.add_attribute(cell, "text", 0)
+ self._comboAntialias.set_active(3)
+
+ # resolution
+
+ self._spinResolution = self.find_widget("spinResolution")
+ self._spinResolution.set_value(Environment().screen_dpi)
+
+ return self._dialog
+
class SaveAsTemplateDialog(GladeInterface):
- filename = find_resource("ui/save_as_template_dialog.ui")
- _dialog = None
-
- def get_dialog(self):
- self._folder = Preferences().TEMPLATE_DIR
-
- if self._dialog is None:
- self._dialog = self.find_widget("dialogSaveAsTemplate")
- self._entry_name = self.find_widget("entryName")
- self._label = self.find_widget("labelLocation")
- self._button_okay = self.find_widget("buttonOkay")
- self._icon = self.find_widget("imageIcon")
-
- self.connect_signals({ "on_entryName_changed" : self._on_name_changed })
-
- return self._dialog
-
- def _escape_name(self, name):
- return name.replace(" ", "_")
-
- def _on_name_changed(self, entry):
- self._validate()
-
- def _validate(self):
- name = self._entry_name.get_text()
- valid = True
-
- if len(name) == 0:
- self._label.set_markup("A name is required.")
- self._icon.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.MENU)
- self._icon.show()
-
- valid = False
- else:
- filename = "%s/%s.template" % (self._folder, self._escape_name(name))
- if File(filename).exists:
- self._label.set_markup("The file <tt>%s</tt> already exists." % filename)
- self._icon.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.MENU)
- self._icon.show()
-
- valid = False
- else:
- self._label.set_markup("The file will be saved as <tt>%s</tt>." % filename)
- self._icon.set_from_stock(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.MENU)
- self._icon.show()
-
- self._button_okay.set_sensitive(valid)
-
- def run(self):
- """
- @param template_location: the folder in which the template will be saved
- """
- dialog = self.get_dialog()
-
- self._entry_name.set_text("")
- self._validate()
-
- if dialog.run() == 1:
- confirmed = True
- else:
- confirmed = False
-
- dialog.hide()
-
- if confirmed:
- return File("%s/%s.template" % (self._folder, self._escape_name(self._entry_name.get_text())))
- else:
- return None
-
+ filename = find_resource("ui/save_as_template_dialog.ui")
+ _dialog = None
+
+ def get_dialog(self):
+ self._folder = Preferences().TEMPLATE_DIR
+
+ if self._dialog is None:
+ self._dialog = self.find_widget("dialogSaveAsTemplate")
+ self._entry_name = self.find_widget("entryName")
+ self._label = self.find_widget("labelLocation")
+ self._button_okay = self.find_widget("buttonOkay")
+ self._icon = self.find_widget("imageIcon")
+
+ self.connect_signals({ "on_entryName_changed" : self._on_name_changed })
+
+ return self._dialog
+
+ def _escape_name(self, name):
+ return name.replace(" ", "_")
+
+ def _on_name_changed(self, entry):
+ self._validate()
+
+ def _validate(self):
+ name = self._entry_name.get_text()
+ valid = True
+
+ if len(name) == 0:
+ self._label.set_markup("A name is required.")
+ self._icon.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.MENU)
+ self._icon.show()
+
+ valid = False
+ else:
+ filename = "%s/%s.template" % (self._folder, self._escape_name(name))
+ if File(filename).exists:
+ self._label.set_markup("The file <tt>%s</tt> already exists." % filename)
+ self._icon.set_from_stock(Gtk.STOCK_DIALOG_ERROR, Gtk.IconSize.MENU)
+ self._icon.show()
+
+ valid = False
+ else:
+ self._label.set_markup("The file will be saved as <tt>%s</tt>." % filename)
+ self._icon.set_from_stock(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.MENU)
+ self._icon.show()
+
+ self._button_okay.set_sensitive(valid)
+
+ def run(self):
+ """
+ @param template_location: the folder in which the template will be saved
+ """
+ dialog = self.get_dialog()
+
+ self._entry_name.set_text("")
+ self._validate()
+
+ if dialog.run() == 1:
+ confirmed = True
+ else:
+ confirmed = False
+
+ dialog.hide()
+
+ if confirmed:
+ return File("%s/%s.template" % (self._folder, self._escape_name(self._entry_name.get_text())))
+ else:
+ return None
+
diff --git a/latex/latex/editor.py b/latex/latex/editor.py
index 7dd622b..ad124ac 100644
--- a/latex/latex/editor.py
+++ b/latex/latex/editor.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -48,374 +48,374 @@ from ..preferences import Preferences
class LaTeXEditor(Editor, IIssueHandler):
-
- _log = getLogger("LaTeXEditor")
-
- #extensions = [".tex"]
- extensions = Preferences().get("latex-extensions").split(",")
-
- dnd_extensions = [".png", ".pdf", ".bib", ".tex"]
-
- @property
- def completion_handlers(self):
- self.__latex_completion_handler = LaTeXCompletionHandler()
-
- return [ self.__latex_completion_handler ]
-
- def init(self, file, context):
- """
- @param file: base.File
- @param context: base.WindowContext
- """
- self._log.debug("init(%s)" % file)
-
- self._file = file
- self._context = context
-
- self._preferences = Preferences()
- self._preferences.connect("preferences-changed", self._on_preferences_changed)
-
- self.register_marker_type("latex-error", self._preferences.get("error-background-color"))
- self.register_marker_type("latex-warning", self._preferences.get("warning-background-color"))
-
- self._issue_view = context.find_view(self, "IssueView")
- self._outline_view = context.find_view(self, "LaTeXOutlineView")
-
- self._parser = LaTeXParser()
- self._outline_generator = LaTeXOutlineGenerator()
- self._validator = LaTeXValidator()
- self._document = None
-
- self._document_dirty = True
-
- # if the document is no master we display an info message on the packages to
- # include - _ensured_packages holds the already mentioned packages to not
- # annoy the user
- self._ensured_packages = []
-
- #
- # initially parse
- #
- self._change_reference = self.initial_timestamp
-
- self.__parse()
- self.__update_neighbors()
-
- def _on_preferences_changed(self, prefs, key, new_value):
- if key in ["outline-show-labels", "outline-show-tables", "outline-show-graphics"]:
- # regenerate outline model
- if self._document_is_master:
- self._outline = self._outline_generator.generate(self._document, self)
- self._outline_view.set_outline(self._outline)
- else:
- # FIXME: self._document contains the full model of child and master
- # so we may not use it for regenerating the outline here
- self.__parse()
- elif key == "show-latex-toolbar":
- show_toolbar = self._preferences.get_bool("show-latex-toolbar")
- if show_toolbar:
- self._window_context._window_decorator._toolbar.show()
- else:
- self._window_context._window_decorator._toolbar.hide()
-
- def drag_drop_received(self, files):
- # see base.Editor.drag_drop_received
-
- # TODO: we need to insert the source at the drop location - so pass it here
-
- self._log.debug("drag_drop: %s" % files)
-
-# if len(files) == 1:
-# file = files[0]
-# self._log.debug("Got one file: %s, extension: %s" % (file.path, file.extension))
-# if file.extension == ".png":
-# self._log.debug("PNG image - including...")
-# source = "\\includegraphics{%s}" % file.path
-# self.insert(source)
-
- def insert(self, source):
- # see base.Editor.insert
-
- if type(source) is LaTeXSource:
- if source.packages and len(source.packages) > 0:
- self.ensure_packages(source.packages)
-
- Editor.insert(self, source.source)
- else:
- Editor.insert(self, source)
-
- POSITION_PACKAGES, POSITION_BIBLIOGRAPHY = 1, 2
-
- def insert_at_position(self, source, position):
- """
- Insert source at a certain position in the LaTeX document:
-
- * POSITION_PACKAGES: after the last \usepackage statement
- * POSITION_BIBLIOGRAPHY: before \end{document}
-
- @param source: a LaTeXSource object
- @param position: POSITION_PACKAGES | POSITION_BIBLIOGRAPHY
- """
-
- if position == self.POSITION_BIBLIOGRAPHY:
- offset = self._document.end_of_document
- Editor.insert_at_offset(self, offset, source, True)
- elif position == self.POSITION_PACKAGES:
- offset = self._document.end_of_packages
- Editor.insert_at_offset(self, offset, source, False)
- else:
- raise NotImplementedError
-
- def ensure_packages(self, packages):
- """
- Ensure that certain packages are included
-
- @param packages: a list of package names
- """
- self.__parse() # ensure up-to-date document model
-
- if not self._document_is_master:
- self._log.debug("ensure_packages: document is not a master")
-
- # find the packages that haven't already been mentioned
- info_packages = [p for p in packages if not p in self._ensured_packages]
-
- if len(info_packages) > 0:
- # generate markup
- li_tags = "\n".join([" â <tt>%s</tt>" % p for p in info_packages])
-
- from ..util import open_info
- open_info("LaTeX Package Required",
- "Please make sure that the following packages are included in the master document per <tt>\\usepackage</tt>: \n\n%s" % li_tags)
-
- # extend the already mentioned packages
- self._ensured_packages.extend(info_packages)
-
- return
-
- # find missing packages
- present_package_names = [p.value for p in self._outline.packages]
- package_names = [p for p in packages if not p in present_package_names]
-
- # insert the necessary \usepackage commands
- if len(package_names) > 0:
- source = "\n" + "\n".join(["\\usepackage{%s}" % n for n in package_names])
- self.insert_at_position(source, self.POSITION_PACKAGES)
-
- def on_save(self):
- """
- The file has been saved
-
- Update models
- """
-
-# from multiprocessing import Process
-#
-# p_parse = Process(target=self.__parse)
-# p_parse.start()
-
- self.__parse()
-
- def __update_neighbors(self):
- """
- Find all files in the working directory that are relevant for LaTeX, e.g.
- other *.tex files or images.
- """
-
- # TODO: this is only needed to feed the LaTeXCompletionHandler. So maybe it should
- # know the edited file and the Editor should call an update() method of the handler
- # when the file is saved.
-
- tex_files = self._file.find_neighbors(".tex")
- bib_files = self._file.find_neighbors(".bib")
-
- graphic_files = []
- for extension in [".ps", ".pdf", ".png", ".jpg", ".eps"]:
- graphic_files.extend(self._file.find_neighbors(extension))
-
- self.__latex_completion_handler.set_neighbors(tex_files, bib_files, graphic_files)
-
- # verbose
- def __parse(self):
- """
- Ensure that the document model is up-to-date
- """
- if self.content_changed(self._change_reference):
- # content has changed so document model may be dirty
- self._change_reference = self.current_timestamp
-
- self._log.debug("Parsing document...")
-
- # reset highlight
- self.remove_markers("latex-error")
- self.remove_markers("latex-warning")
-
- # reset issues
- self._issue_view.clear()
-
- if BENCHMARK: t = time.clock()
-
- # parse document
- if self._document != None:
- self._document.destroy()
- del self._document
- self._document = self._parser.parse(self.content, self._file, self)
-
- if BENCHMARK: self._log.info("LaTeXParser.parse: %f" % (time.clock() - t))
-
- # create a copy that won't be expanded (e.g. for spell check)
- #self._local_document = deepcopy(self._document)
-
- self._log.debug("Parsed %s bytes of content" % len(self.content))
-
- # FIXME: the LaTeXChooseMasterAction enabled state has to be updated on tab change, too!
-
- if self._document.is_master:
-
- self._context.set_action_enabled("LaTeXChooseMasterAction", False)
- self._document_is_master = True
-
- # expand child documents
- expander = LaTeXReferenceExpander()
- expander.expand(self._document, self._file, self, self.charset)
-
- # generate outline from the expanded model
- self._outline = self._outline_generator.generate(self._document, self)
-
- # pass to view
- self._outline_view.set_outline(self._outline)
-
- # validate
- self._validator.validate(self._document, self._outline, self)
- else:
- self._log.debug("Document is not a master")
-
- self._context.set_action_enabled("LaTeXChooseMasterAction", True)
- self._document_is_master = False
-
- # the outline used by the outline view has to be created only from the child model
- # otherwise we see the outline of the master and get wrong offsets
- self._outline = self._outline_generator.generate(self._document, self)
- self._outline_view.set_outline(self._outline)
-
- # find master
- master_file = self.__master_file
-
- if master_file is None:
- return
-
- # parse master
- master_content = open(master_file.path).read()
- self._document = self._parser.parse(master_content, master_file, self)
-
- # expand its child documents
- expander = LaTeXReferenceExpander()
- expander.expand(self._document, master_file, self, self.charset)
-
- # create another outline of the expanded master model to make elements
- # from the master available (labels, colors, BibTeX files etc.)
- self._outline = self._outline_generator.generate(self._document, self)
-
- # validate
- self._validator.validate(self._document, self._outline, self)
-
- # pass outline to completion
- self.__latex_completion_handler.set_outline(self._outline)
-
- # pass neighbor files to completion
- self.__update_neighbors()
-
- self._log.debug("Parsing finished")
-
- #print self._document.xml
-
- @property
- # returns(File)
- def __master_file(self):
- """
- Find the LaTeX master of this child
-
- @return: base.File
- """
- # TODO: cache result
-
- property_file = PropertyFile(self._file)
- try:
- #return File(property_file["MasterFilename"])
-
- path = property_file["MasterFilename"]
- # the property file may contain absolute and relative paths
- # because we switched in 0.2rc2
- if File.is_absolute(path):
- self._log.debug("Path is absolute")
- return File(path)
- else:
- self._log.debug("Path is relative")
- return File.create_from_relative_path(path, self._file.dirname)
- except KeyError: # master filename not found
- # ask user
- master_filename = ChooseMasterDialog().run(self._file.dirname)
- if master_filename:
- # relativize the master filename
- master_filename = File(master_filename).relativize(self._file.dirname, True)
-
- property_file["MasterFilename"] = master_filename
- property_file.save()
- return File(master_filename)
- else:
- # no master file chosen
- return None
-
- def issue(self, issue):
- # see IIssueHandler.issue
-
- local = (issue.file == self._file)
-
- self._issue_view.append_issue(issue, local)
-
- if issue.file == self._file:
- if issue.severity == Issue.SEVERITY_ERROR:
- self.create_marker("latex-error", issue.start, issue.end)
- elif issue.severity == Issue.SEVERITY_WARNING:
- self.create_marker("latex-warning", issue.start, issue.end)
-
- def on_cursor_moved(self, offset):
- """
- The cursor has moved
- """
- if self._preferences.get_bool("outline-connect-to-editor"):
- self._outline_view.select_path_by_offset(offset)
-
- @property
- def file(self):
- # overrides Editor.file
-
- # we may not call self._document.is_master because _document is always
- # replaced by the master model
- if self._document_is_master:
- return self._file
- else:
- return self.__master_file
-
- @property
- def edited_file(self):
- """
- Always returns the really edited file instead of the master
-
- This is called by the outline view to identify foreign nodes
- """
- return self._file
-
- def destroy(self):
- # unreference the window context
- del self._context
-
- # destroy the cached document
- self._document.destroy()
- del self._document
-
- Editor.destroy(self)
-
- def __del__(self):
- self._log.debug("Properly destroyed %s" % self)
-
+
+ _log = getLogger("LaTeXEditor")
+
+ #extensions = [".tex"]
+ extensions = Preferences().get("latex-extensions").split(",")
+
+ dnd_extensions = [".png", ".pdf", ".bib", ".tex"]
+
+ @property
+ def completion_handlers(self):
+ self.__latex_completion_handler = LaTeXCompletionHandler()
+
+ return [ self.__latex_completion_handler ]
+
+ def init(self, file, context):
+ """
+ @param file: base.File
+ @param context: base.WindowContext
+ """
+ self._log.debug("init(%s)" % file)
+
+ self._file = file
+ self._context = context
+
+ self._preferences = Preferences()
+ self._preferences.connect("preferences-changed", self._on_preferences_changed)
+
+ self.register_marker_type("latex-error", self._preferences.get("error-background-color"))
+ self.register_marker_type("latex-warning", self._preferences.get("warning-background-color"))
+
+ self._issue_view = context.find_view(self, "IssueView")
+ self._outline_view = context.find_view(self, "LaTeXOutlineView")
+
+ self._parser = LaTeXParser()
+ self._outline_generator = LaTeXOutlineGenerator()
+ self._validator = LaTeXValidator()
+ self._document = None
+
+ self._document_dirty = True
+
+ # if the document is no master we display an info message on the packages to
+ # include - _ensured_packages holds the already mentioned packages to not
+ # annoy the user
+ self._ensured_packages = []
+
+ #
+ # initially parse
+ #
+ self._change_reference = self.initial_timestamp
+
+ self.__parse()
+ self.__update_neighbors()
+
+ def _on_preferences_changed(self, prefs, key, new_value):
+ if key in ["outline-show-labels", "outline-show-tables", "outline-show-graphics"]:
+ # regenerate outline model
+ if self._document_is_master:
+ self._outline = self._outline_generator.generate(self._document, self)
+ self._outline_view.set_outline(self._outline)
+ else:
+ # FIXME: self._document contains the full model of child and master
+ # so we may not use it for regenerating the outline here
+ self.__parse()
+ elif key == "show-latex-toolbar":
+ show_toolbar = self._preferences.get_bool("show-latex-toolbar")
+ if show_toolbar:
+ self._window_context._window_decorator._toolbar.show()
+ else:
+ self._window_context._window_decorator._toolbar.hide()
+
+ def drag_drop_received(self, files):
+ # see base.Editor.drag_drop_received
+
+ # TODO: we need to insert the source at the drop location - so pass it here
+
+ self._log.debug("drag_drop: %s" % files)
+
+# if len(files) == 1:
+# file = files[0]
+# self._log.debug("Got one file: %s, extension: %s" % (file.path, file.extension))
+# if file.extension == ".png":
+# self._log.debug("PNG image - including...")
+# source = "\\includegraphics{%s}" % file.path
+# self.insert(source)
+
+ def insert(self, source):
+ # see base.Editor.insert
+
+ if type(source) is LaTeXSource:
+ if source.packages and len(source.packages) > 0:
+ self.ensure_packages(source.packages)
+
+ Editor.insert(self, source.source)
+ else:
+ Editor.insert(self, source)
+
+ POSITION_PACKAGES, POSITION_BIBLIOGRAPHY = 1, 2
+
+ def insert_at_position(self, source, position):
+ """
+ Insert source at a certain position in the LaTeX document:
+
+ * POSITION_PACKAGES: after the last \usepackage statement
+ * POSITION_BIBLIOGRAPHY: before \end{document}
+
+ @param source: a LaTeXSource object
+ @param position: POSITION_PACKAGES | POSITION_BIBLIOGRAPHY
+ """
+
+ if position == self.POSITION_BIBLIOGRAPHY:
+ offset = self._document.end_of_document
+ Editor.insert_at_offset(self, offset, source, True)
+ elif position == self.POSITION_PACKAGES:
+ offset = self._document.end_of_packages
+ Editor.insert_at_offset(self, offset, source, False)
+ else:
+ raise NotImplementedError
+
+ def ensure_packages(self, packages):
+ """
+ Ensure that certain packages are included
+
+ @param packages: a list of package names
+ """
+ self.__parse() # ensure up-to-date document model
+
+ if not self._document_is_master:
+ self._log.debug("ensure_packages: document is not a master")
+
+ # find the packages that haven't already been mentioned
+ info_packages = [p for p in packages if not p in self._ensured_packages]
+
+ if len(info_packages) > 0:
+ # generate markup
+ li_tags = "\n".join([" â <tt>%s</tt>" % p for p in info_packages])
+
+ from ..util import open_info
+ open_info("LaTeX Package Required",
+ "Please make sure that the following packages are included in the master document per <tt>\\usepackage</tt>: \n\n%s" % li_tags)
+
+ # extend the already mentioned packages
+ self._ensured_packages.extend(info_packages)
+
+ return
+
+ # find missing packages
+ present_package_names = [p.value for p in self._outline.packages]
+ package_names = [p for p in packages if not p in present_package_names]
+
+ # insert the necessary \usepackage commands
+ if len(package_names) > 0:
+ source = "\n" + "\n".join(["\\usepackage{%s}" % n for n in package_names])
+ self.insert_at_position(source, self.POSITION_PACKAGES)
+
+ def on_save(self):
+ """
+ The file has been saved
+
+ Update models
+ """
+
+# from multiprocessing import Process
+#
+# p_parse = Process(target=self.__parse)
+# p_parse.start()
+
+ self.__parse()
+
+ def __update_neighbors(self):
+ """
+ Find all files in the working directory that are relevant for LaTeX, e.g.
+ other *.tex files or images.
+ """
+
+ # TODO: this is only needed to feed the LaTeXCompletionHandler. So maybe it should
+ # know the edited file and the Editor should call an update() method of the handler
+ # when the file is saved.
+
+ tex_files = self._file.find_neighbors(".tex")
+ bib_files = self._file.find_neighbors(".bib")
+
+ graphic_files = []
+ for extension in [".ps", ".pdf", ".png", ".jpg", ".eps"]:
+ graphic_files.extend(self._file.find_neighbors(extension))
+
+ self.__latex_completion_handler.set_neighbors(tex_files, bib_files, graphic_files)
+
+ # verbose
+ def __parse(self):
+ """
+ Ensure that the document model is up-to-date
+ """
+ if self.content_changed(self._change_reference):
+ # content has changed so document model may be dirty
+ self._change_reference = self.current_timestamp
+
+ self._log.debug("Parsing document...")
+
+ # reset highlight
+ self.remove_markers("latex-error")
+ self.remove_markers("latex-warning")
+
+ # reset issues
+ self._issue_view.clear()
+
+ if BENCHMARK: t = time.clock()
+
+ # parse document
+ if self._document != None:
+ self._document.destroy()
+ del self._document
+ self._document = self._parser.parse(self.content, self._file, self)
+
+ if BENCHMARK: self._log.info("LaTeXParser.parse: %f" % (time.clock() - t))
+
+ # create a copy that won't be expanded (e.g. for spell check)
+ #self._local_document = deepcopy(self._document)
+
+ self._log.debug("Parsed %s bytes of content" % len(self.content))
+
+ # FIXME: the LaTeXChooseMasterAction enabled state has to be updated on tab change, too!
+
+ if self._document.is_master:
+
+ self._context.set_action_enabled("LaTeXChooseMasterAction", False)
+ self._document_is_master = True
+
+ # expand child documents
+ expander = LaTeXReferenceExpander()
+ expander.expand(self._document, self._file, self, self.charset)
+
+ # generate outline from the expanded model
+ self._outline = self._outline_generator.generate(self._document, self)
+
+ # pass to view
+ self._outline_view.set_outline(self._outline)
+
+ # validate
+ self._validator.validate(self._document, self._outline, self)
+ else:
+ self._log.debug("Document is not a master")
+
+ self._context.set_action_enabled("LaTeXChooseMasterAction", True)
+ self._document_is_master = False
+
+ # the outline used by the outline view has to be created only from the child model
+ # otherwise we see the outline of the master and get wrong offsets
+ self._outline = self._outline_generator.generate(self._document, self)
+ self._outline_view.set_outline(self._outline)
+
+ # find master
+ master_file = self.__master_file
+
+ if master_file is None:
+ return
+
+ # parse master
+ master_content = open(master_file.path).read()
+ self._document = self._parser.parse(master_content, master_file, self)
+
+ # expand its child documents
+ expander = LaTeXReferenceExpander()
+ expander.expand(self._document, master_file, self, self.charset)
+
+ # create another outline of the expanded master model to make elements
+ # from the master available (labels, colors, BibTeX files etc.)
+ self._outline = self._outline_generator.generate(self._document, self)
+
+ # validate
+ self._validator.validate(self._document, self._outline, self)
+
+ # pass outline to completion
+ self.__latex_completion_handler.set_outline(self._outline)
+
+ # pass neighbor files to completion
+ self.__update_neighbors()
+
+ self._log.debug("Parsing finished")
+
+ #print self._document.xml
+
+ @property
+ # returns(File)
+ def __master_file(self):
+ """
+ Find the LaTeX master of this child
+
+ @return: base.File
+ """
+ # TODO: cache result
+
+ property_file = PropertyFile(self._file)
+ try:
+ #return File(property_file["MasterFilename"])
+
+ path = property_file["MasterFilename"]
+ # the property file may contain absolute and relative paths
+ # because we switched in 0.2rc2
+ if File.is_absolute(path):
+ self._log.debug("Path is absolute")
+ return File(path)
+ else:
+ self._log.debug("Path is relative")
+ return File.create_from_relative_path(path, self._file.dirname)
+ except KeyError: # master filename not found
+ # ask user
+ master_filename = ChooseMasterDialog().run(self._file.dirname)
+ if master_filename:
+ # relativize the master filename
+ master_filename = File(master_filename).relativize(self._file.dirname, True)
+
+ property_file["MasterFilename"] = master_filename
+ property_file.save()
+ return File(master_filename)
+ else:
+ # no master file chosen
+ return None
+
+ def issue(self, issue):
+ # see IIssueHandler.issue
+
+ local = (issue.file == self._file)
+
+ self._issue_view.append_issue(issue, local)
+
+ if issue.file == self._file:
+ if issue.severity == Issue.SEVERITY_ERROR:
+ self.create_marker("latex-error", issue.start, issue.end)
+ elif issue.severity == Issue.SEVERITY_WARNING:
+ self.create_marker("latex-warning", issue.start, issue.end)
+
+ def on_cursor_moved(self, offset):
+ """
+ The cursor has moved
+ """
+ if self._preferences.get_bool("outline-connect-to-editor"):
+ self._outline_view.select_path_by_offset(offset)
+
+ @property
+ def file(self):
+ # overrides Editor.file
+
+ # we may not call self._document.is_master because _document is always
+ # replaced by the master model
+ if self._document_is_master:
+ return self._file
+ else:
+ return self.__master_file
+
+ @property
+ def edited_file(self):
+ """
+ Always returns the really edited file instead of the master
+
+ This is called by the outline view to identify foreign nodes
+ """
+ return self._file
+
+ def destroy(self):
+ # unreference the window context
+ del self._context
+
+ # destroy the cached document
+ self._document.destroy()
+ del self._document
+
+ Editor.destroy(self)
+
+ def __del__(self):
+ self._log.debug("Properly destroyed %s" % self)
+
diff --git a/latex/latex/environment.py b/latex/latex/environment.py
index 4742464..7b987a8 100644
--- a/latex/latex/environment.py
+++ b/latex/latex/environment.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -32,149 +32,149 @@ from logging import getLogger
class CnfFile(dict):
- """
- This parses a .cnf file and provides its contents as a dictionary
- """
- def __init__(self, filename):
- """
- @raise IOError: if file is not found
- """
- for line in open(filename).readlines():
- if not line.startswith("%"):
- try:
- key, value = line.split("=")
- self[key.strip()] = value.strip()
- except:
- pass
+ """
+ This parses a .cnf file and provides its contents as a dictionary
+ """
+ def __init__(self, filename):
+ """
+ @raise IOError: if file is not found
+ """
+ for line in open(filename).readlines():
+ if not line.startswith("%"):
+ try:
+ key, value = line.split("=")
+ self[key.strip()] = value.strip()
+ except:
+ pass
_INPUT_ENCODINGS = {
- "utf8" : "UTF-8 (Unicode)",
- "ascii" : "US-ASCII",
- "next" : "ASCII (NeXT)",
- "ansinew" : "ASCII (Windows)",
- "applemac" : "ASCII (Apple)",
- "macce" : "MacCE (Apple Central European)",
- "latin1" : "Latin-1 (Western European)",
- "latin2" : "Latin-2 (Danubian European)",
- "latin3" : "Latin-3 (South European)",
- "latin4" : "Latin-4 (North European)",
- "latin5" : "Latin-5 (Turkish)",
- "latin6" : "Latin-6 (Nordic)",
- "latin7" : "Latin-7 (Baltic)",
- "latin8" : "Latin-8 (Celtic)",
- "latin9" : "Latin-9 (extended Latin-1)",
- "latin10" : "Latin-10 (South-Eastern European)",
- "cp1250" : "CP1250 (Windows Central European)",
- "cp1252" : "CP1252 (Windows Western European)",
- "cp1257" : "CP1257 (Windows Baltic)",
- "cp437" : "CP437 (DOS US with Î)",
- "cp437de" : "CP437 (DOS US with Ã)",
- "cp850" : "CP850 (DOS Latin-1)",
- "cp852" : "CP852 (DOS Central European)",
- "cp858" : "CP858 (DOS Western European)",
- "cp865" : "CP865 (DOS Nordic)",
- "decmulti" : "DEC Multinational Character Set"
+ "utf8" : "UTF-8 (Unicode)",
+ "ascii" : "US-ASCII",
+ "next" : "ASCII (NeXT)",
+ "ansinew" : "ASCII (Windows)",
+ "applemac" : "ASCII (Apple)",
+ "macce" : "MacCE (Apple Central European)",
+ "latin1" : "Latin-1 (Western European)",
+ "latin2" : "Latin-2 (Danubian European)",
+ "latin3" : "Latin-3 (South European)",
+ "latin4" : "Latin-4 (North European)",
+ "latin5" : "Latin-5 (Turkish)",
+ "latin6" : "Latin-6 (Nordic)",
+ "latin7" : "Latin-7 (Baltic)",
+ "latin8" : "Latin-8 (Celtic)",
+ "latin9" : "Latin-9 (extended Latin-1)",
+ "latin10" : "Latin-10 (South-Eastern European)",
+ "cp1250" : "CP1250 (Windows Central European)",
+ "cp1252" : "CP1252 (Windows Western European)",
+ "cp1257" : "CP1257 (Windows Baltic)",
+ "cp437" : "CP437 (DOS US with Î)",
+ "cp437de" : "CP437 (DOS US with Ã)",
+ "cp850" : "CP850 (DOS Latin-1)",
+ "cp852" : "CP852 (DOS Central European)",
+ "cp858" : "CP858 (DOS Western European)",
+ "cp865" : "CP865 (DOS Nordic)",
+ "decmulti" : "DEC Multinational Character Set"
}
_BABEL_PACKAGES = {
- "albanian" : "Albanian",
- "afrikaans" : "Afrikaans", #dutch dialect
- "austrian" : "Austrian",
- "naustrian" : "Austrian (new spelling)",
- "bahasai" : "Bahasa Indonesia",
- "bahasam" : "Bahasa Malaysia",
- "basque" : "Basque",
- "breton" : "Breton",
- "bulgarian" : "Bulgarian",
- "catalan" : "Catalan",
- "croatian" : "Croatian",
- "czech" : "Czech",
- "danish" : "Danish",
- "dutch" : "Dutch",
- "australian" : "English (AU)",
- "canadian" : "English (CA)",
- "newzealand" : "English (NZ)",
- "UKenglish" : "English (UK)",
- "english" : "English (US)",
- "esperanto" : "Esperanto",
- "estonian" : "Estonian",
- "finnish" : "Finnish",
- "frenchb" : "French",
- "acadian" : "French (Acadian)",
- "canadien" : "French (CA)",
- "galician" : "Galician",
- "germanb" : "German",
- "ngermanb" : "German (new spelling)",
- "greek" : "Greek",
- "polutonikogreek" : "Greek (polytonic)",
-# "athnum" : "Greek (Athens numbering)",
- "hebrew" : "Hebrew",
- "magyar" : "Hungarian",
- "icelandic" : "Icelandic",
- "interlingua" : "Interlingua",
- "irish" : "Irish Gaelic",
- "italian" : "Italian",
- "latin" : "Latin",
- "lsorbian" : "Lower Sorbian",
- "norsk" : "Norwegian BokmÃl",
- "nynorsk" : "Norwegian Nynorsk",
- "polish" : "Polish",
- "portuges" : "Portuguese (PT)",
- "brazilian" : "Portuguese (BR)",
- "romanian" : "Romanian",
- "russianb" : "Russian",
- "samin" : "North Sami",
- "scottish" : "Scottish Gaelic",
- "serbian" : "Serbian",
- "slovak" : "Slovak",
- "slovene" : "Slovene",
- "spanish" : "Spanish",
- "swedish" : "Swedish",
- "turkish" : "Turkish",
- "ukraineb" : "Ukraine",
- "usorbian" : "Upper Sorbian",
- "welsh" : "Welsh"
+ "albanian" : "Albanian",
+ "afrikaans" : "Afrikaans", #dutch dialect
+ "austrian" : "Austrian",
+ "naustrian" : "Austrian (new spelling)",
+ "bahasai" : "Bahasa Indonesia",
+ "bahasam" : "Bahasa Malaysia",
+ "basque" : "Basque",
+ "breton" : "Breton",
+ "bulgarian" : "Bulgarian",
+ "catalan" : "Catalan",
+ "croatian" : "Croatian",
+ "czech" : "Czech",
+ "danish" : "Danish",
+ "dutch" : "Dutch",
+ "australian" : "English (AU)",
+ "canadian" : "English (CA)",
+ "newzealand" : "English (NZ)",
+ "UKenglish" : "English (UK)",
+ "english" : "English (US)",
+ "esperanto" : "Esperanto",
+ "estonian" : "Estonian",
+ "finnish" : "Finnish",
+ "frenchb" : "French",
+ "acadian" : "French (Acadian)",
+ "canadien" : "French (CA)",
+ "galician" : "Galician",
+ "germanb" : "German",
+ "ngermanb" : "German (new spelling)",
+ "greek" : "Greek",
+ "polutonikogreek" : "Greek (polytonic)",
+# "athnum" : "Greek (Athens numbering)",
+ "hebrew" : "Hebrew",
+ "magyar" : "Hungarian",
+ "icelandic" : "Icelandic",
+ "interlingua" : "Interlingua",
+ "irish" : "Irish Gaelic",
+ "italian" : "Italian",
+ "latin" : "Latin",
+ "lsorbian" : "Lower Sorbian",
+ "norsk" : "Norwegian BokmÃl",
+ "nynorsk" : "Norwegian Nynorsk",
+ "polish" : "Polish",
+ "portuges" : "Portuguese (PT)",
+ "brazilian" : "Portuguese (BR)",
+ "romanian" : "Romanian",
+ "russianb" : "Russian",
+ "samin" : "North Sami",
+ "scottish" : "Scottish Gaelic",
+ "serbian" : "Serbian",
+ "slovak" : "Slovak",
+ "slovene" : "Slovene",
+ "spanish" : "Spanish",
+ "swedish" : "Swedish",
+ "turkish" : "Turkish",
+ "ukraineb" : "Ukraine",
+ "usorbian" : "Upper Sorbian",
+ "welsh" : "Welsh"
}
_DOCUMENT_CLASSES = {
- "abstbook" : _("Book of abstracts"),
- "article" : _("Article"),
- "amsart" : _("Article (AMS)"),
- "amsbook" : _("Book (AMS)"),
- "amsdtx" : _("AMS Documentation"),
- "amsproc" : _("Proceedings (AMS)"),
- "report" : _("Report"),
- "beamer" : _("Beamer slides"),
- "beletter" : _("Belgian letter"),
- "book" : _("Book"),
- "flashcard" : _("Flashcard"),
- "iagproc" : _("Proceedings (IAG)"),
- "letter" : _("Letter"),
- "ltnews" : _("LaTeX News"),
- "ltxdoc" : _("LaTeX Documentation"),
- "ltxguide" : _("LaTeX Guide"),
- "proc" : _("Proceedings"),
- "scrartcl" : _("Article (KOMA-Script)"),
- "scrreport" : _("Report (KOMA-Script)"),
- "scrbook" : _("Book (KOMA-Script)"),
- "scrlettr" : _("Letter (KOMA-Script)"),
- "scrlttr2" : _("Letter 2 (KOMA-Script)"),
- "scrreprt" : _("Report (KOMA-Script)"),
- "slides" : _("Slides")
+ "abstbook" : _("Book of abstracts"),
+ "article" : _("Article"),
+ "amsart" : _("Article (AMS)"),
+ "amsbook" : _("Book (AMS)"),
+ "amsdtx" : _("AMS Documentation"),
+ "amsproc" : _("Proceedings (AMS)"),
+ "report" : _("Report"),
+ "beamer" : _("Beamer slides"),
+ "beletter" : _("Belgian letter"),
+ "book" : _("Book"),
+ "flashcard" : _("Flashcard"),
+ "iagproc" : _("Proceedings (IAG)"),
+ "letter" : _("Letter"),
+ "ltnews" : _("LaTeX News"),
+ "ltxdoc" : _("LaTeX Documentation"),
+ "ltxguide" : _("LaTeX Guide"),
+ "proc" : _("Proceedings"),
+ "scrartcl" : _("Article (KOMA-Script)"),
+ "scrreport" : _("Report (KOMA-Script)"),
+ "scrbook" : _("Book (KOMA-Script)"),
+ "scrlettr" : _("Letter (KOMA-Script)"),
+ "scrlttr2" : _("Letter 2 (KOMA-Script)"),
+ "scrreprt" : _("Report (KOMA-Script)"),
+ "slides" : _("Slides")
}
class TeXResource(object):
- def __init__(self, file, name, label):
- """
- @param file: a File object
- @param name: the identifier of this resource (e.g. 'ams' for 'ams.bib')
- @param label: a descriptive label
- """
- self.file = file
- self.name = name
- self.label = label
+ def __init__(self, file, name, label):
+ """
+ @param file: a File object
+ @param name: the identifier of this resource (e.g. 'ams' for 'ams.bib')
+ @param label: a descriptive label
+ """
+ self.file = file
+ self.name = name
+ self.label = label
from os.path import expanduser
@@ -183,192 +183,192 @@ from ..base import File
class Environment(object):
-
- _CONFIG_FILENAME = "/etc/texmf/texmf.cnf"
- _DEFAULT_TEXMF_DIR = "/usr/share/texmf-texlive"
- _DEFAULT_TEXMF_DIR_HOME = "~/texmf"
-
- """
- This encapsulates the user's LaTeX distribution and provides methods
- for searching it
- """
-
- _log = getLogger("Environment")
-
- def __new__(cls):
- if not '_instance' in cls.__dict__:
- cls._instance = object.__new__(cls)
- return cls._instance
-
- def __init__(self):
- if not '_ready' in dir(self):
- self._bibtex_styles = None
- self._classes = None
- self._language_definitions = None
- self._input_encodings = None
- self._screen_dpi = None
- self._kpsewhich_checked = False
- self._kpsewhich_installed = None
- self._file_exists_cache = {}
-
- self._search_paths = []
- default_search_paths = [self._DEFAULT_TEXMF_DIR, expanduser(self._DEFAULT_TEXMF_DIR_HOME)]
-
- try:
- cnf_file = CnfFile(self._CONFIG_FILENAME)
-
- path_found = False
-
- for key in ["TEXMFMAIN", "TEXMFDIST", "TEXMFHOME"]:
- try:
- self._search_paths.append(cnf_file[key])
- path_found = True
- except KeyError:
- # config key not found
- self._log.error("Key %s not found in %s" % (key, self._CONFIG_FILENAME))
-
- if not path_found:
- self._log.error("No search paths found in %s, using default search paths %s" % (self._CONFIG_FILENAME, default_search_paths))
- self._search_paths = default_search_paths
-
- except IOError:
- # file _CONFIG_FILENAME not found - use default path
- self._log.error("%s not found, using default search paths %s" % (self._CONFIG_FILENAME, default_search_paths))
- self._search_paths = default_search_paths
-
- self._ready = True
-
- @property
- def kpsewhich_installed(self):
- """
- Return whether kpsewhich is installed
- """
- if not self._kpsewhich_checked:
- self._kpsewhich_installed = bool(system("kpsewhich --version $2>/dev/null") == 0)
- self._kpsewhich_checked = True
- return self._kpsewhich_installed
-
- def file_exists(self, filename):
- """
- Uses kpsewhich to check if a TeX related file (.bst, .sty etc.) exists. The result
- is cached to minimize 'kpsewhich' calls.
- """
- if not self.kpsewhich_installed:
- return True
-
- try:
- return self._file_exists_cache[filename]
- except KeyError:
- found = popen("kpsewhich %s" % filename).read().splitlines()
- exists = bool(len(found))
- self._file_exists_cache[filename] = exists
- return exists
-
- @property
- def bibtex_styles(self):
- """
- Return the available .bst files
- """
- if not self._bibtex_styles:
- self._bibtex_styles = self._find_resources("", ".bst", {})
- return self._bibtex_styles
-
- @property
- def document_classes(self):
- """
- Return the available document classes
- """
- if not self._classes:
- self._classes = self._find_resources("", ".cls", _DOCUMENT_CLASSES)
- return self._classes
-
- @property
- def language_definitions(self):
- if not self._language_definitions:
- self._language_definitions = self._find_resources("/tex/generic/babel/", ".ldf", _BABEL_PACKAGES)
- return self._language_definitions
-
- @property
- def input_encodings(self):
- """
- Return a list of all available input encodings
- """
- if not self._input_encodings:
- self._input_encodings = self._find_resources("/tex/latex/base/", ".def", _INPUT_ENCODINGS)
- return self._input_encodings
-
- def _find_resources(self, relative, extension, labels):
- """
- Find TeX resources
-
- @param relative: a path relative to TEXMF... search path, e.g. '/tex/latex/base/'
- @param extension: the file extension of the resources, e.g. '.bst'
- @param labels: the dictionary to be searched for labels
- """
- resources = []
- files = []
-
- for search_path in self._search_paths:
- files += [File(f) for f in popen("find %s%s -name '*%s'" % (search_path, relative, extension)).readlines()]
-
- if len(files) > 0:
- for file in files:
- name = file.shortbasename
- try:
- label = labels[name]
- except KeyError:
- label = ""
- resources.append(TeXResource(file, name, label))
- else:
- # no files found
- self._log.error("No %s-files found in %s%s" % (extension, search_path, relative))
-
- for name, label in labels.iteritems():
- found = False
- for resource in resources:
- if resource.name == name:
- found = True
- if not found:
- resources.append(TeXResource(None, name, label))
-
- return resources
-
- @property
- def screen_dpi(self):
- if not self._screen_dpi:
- screen = Gdk.Screen.get_default()
- dpi_x = screen.width() / screen.width_mm() * 25.4
- dpi_y = screen.height() / screen.height_mm() * 25.4
-
- self._screen_dpi = (dpi_x + dpi_y) / 2.0
-
- return self._screen_dpi
-
- @property
- def username(self):
- """
- Return user name derived from pwd entry
- """
- record = getpwnam(getuser()) # get pwd entry
-
- self._log.debug("Found user pw entry: " + str(record))
-
- if len(record[4]):
- return record[4].split(",")[0]
- else:
- return record[0].title()
-
- @property
- def date_format(self):
- """
- Return localized date format for use in strftime()
- """
- return nl_langinfo(D_FMT)
-
- @property
- def language_code(self):
- """
- Return language code like 'de'
- """
- return getdefaultlocale()[0]
+
+ _CONFIG_FILENAME = "/etc/texmf/texmf.cnf"
+ _DEFAULT_TEXMF_DIR = "/usr/share/texmf-texlive"
+ _DEFAULT_TEXMF_DIR_HOME = "~/texmf"
+
+ """
+ This encapsulates the user's LaTeX distribution and provides methods
+ for searching it
+ """
+
+ _log = getLogger("Environment")
+
+ def __new__(cls):
+ if not '_instance' in cls.__dict__:
+ cls._instance = object.__new__(cls)
+ return cls._instance
+
+ def __init__(self):
+ if not '_ready' in dir(self):
+ self._bibtex_styles = None
+ self._classes = None
+ self._language_definitions = None
+ self._input_encodings = None
+ self._screen_dpi = None
+ self._kpsewhich_checked = False
+ self._kpsewhich_installed = None
+ self._file_exists_cache = {}
+
+ self._search_paths = []
+ default_search_paths = [self._DEFAULT_TEXMF_DIR, expanduser(self._DEFAULT_TEXMF_DIR_HOME)]
+
+ try:
+ cnf_file = CnfFile(self._CONFIG_FILENAME)
+
+ path_found = False
+
+ for key in ["TEXMFMAIN", "TEXMFDIST", "TEXMFHOME"]:
+ try:
+ self._search_paths.append(cnf_file[key])
+ path_found = True
+ except KeyError:
+ # config key not found
+ self._log.error("Key %s not found in %s" % (key, self._CONFIG_FILENAME))
+
+ if not path_found:
+ self._log.error("No search paths found in %s, using default search paths %s" % (self._CONFIG_FILENAME, default_search_paths))
+ self._search_paths = default_search_paths
+
+ except IOError:
+ # file _CONFIG_FILENAME not found - use default path
+ self._log.error("%s not found, using default search paths %s" % (self._CONFIG_FILENAME, default_search_paths))
+ self._search_paths = default_search_paths
+
+ self._ready = True
+
+ @property
+ def kpsewhich_installed(self):
+ """
+ Return whether kpsewhich is installed
+ """
+ if not self._kpsewhich_checked:
+ self._kpsewhich_installed = bool(system("kpsewhich --version $2>/dev/null") == 0)
+ self._kpsewhich_checked = True
+ return self._kpsewhich_installed
+
+ def file_exists(self, filename):
+ """
+ Uses kpsewhich to check if a TeX related file (.bst, .sty etc.) exists. The result
+ is cached to minimize 'kpsewhich' calls.
+ """
+ if not self.kpsewhich_installed:
+ return True
+
+ try:
+ return self._file_exists_cache[filename]
+ except KeyError:
+ found = popen("kpsewhich %s" % filename).read().splitlines()
+ exists = bool(len(found))
+ self._file_exists_cache[filename] = exists
+ return exists
+
+ @property
+ def bibtex_styles(self):
+ """
+ Return the available .bst files
+ """
+ if not self._bibtex_styles:
+ self._bibtex_styles = self._find_resources("", ".bst", {})
+ return self._bibtex_styles
+
+ @property
+ def document_classes(self):
+ """
+ Return the available document classes
+ """
+ if not self._classes:
+ self._classes = self._find_resources("", ".cls", _DOCUMENT_CLASSES)
+ return self._classes
+
+ @property
+ def language_definitions(self):
+ if not self._language_definitions:
+ self._language_definitions = self._find_resources("/tex/generic/babel/", ".ldf", _BABEL_PACKAGES)
+ return self._language_definitions
+
+ @property
+ def input_encodings(self):
+ """
+ Return a list of all available input encodings
+ """
+ if not self._input_encodings:
+ self._input_encodings = self._find_resources("/tex/latex/base/", ".def", _INPUT_ENCODINGS)
+ return self._input_encodings
+
+ def _find_resources(self, relative, extension, labels):
+ """
+ Find TeX resources
+
+ @param relative: a path relative to TEXMF... search path, e.g. '/tex/latex/base/'
+ @param extension: the file extension of the resources, e.g. '.bst'
+ @param labels: the dictionary to be searched for labels
+ """
+ resources = []
+ files = []
+
+ for search_path in self._search_paths:
+ files += [File(f) for f in popen("find %s%s -name '*%s'" % (search_path, relative, extension)).readlines()]
+
+ if len(files) > 0:
+ for file in files:
+ name = file.shortbasename
+ try:
+ label = labels[name]
+ except KeyError:
+ label = ""
+ resources.append(TeXResource(file, name, label))
+ else:
+ # no files found
+ self._log.error("No %s-files found in %s%s" % (extension, search_path, relative))
+
+ for name, label in labels.iteritems():
+ found = False
+ for resource in resources:
+ if resource.name == name:
+ found = True
+ if not found:
+ resources.append(TeXResource(None, name, label))
+
+ return resources
+
+ @property
+ def screen_dpi(self):
+ if not self._screen_dpi:
+ screen = Gdk.Screen.get_default()
+ dpi_x = screen.width() / screen.width_mm() * 25.4
+ dpi_y = screen.height() / screen.height_mm() * 25.4
+
+ self._screen_dpi = (dpi_x + dpi_y) / 2.0
+
+ return self._screen_dpi
+
+ @property
+ def username(self):
+ """
+ Return user name derived from pwd entry
+ """
+ record = getpwnam(getuser()) # get pwd entry
+
+ self._log.debug("Found user pw entry: " + str(record))
+
+ if len(record[4]):
+ return record[4].split(",")[0]
+ else:
+ return record[0].title()
+
+ @property
+ def date_format(self):
+ """
+ Return localized date format for use in strftime()
+ """
+ return nl_langinfo(D_FMT)
+
+ @property
+ def language_code(self):
+ """
+ Return language code like 'de'
+ """
+ return getdefaultlocale()[0]
diff --git a/latex/latex/expander.py b/latex/latex/expander.py
index 975aa36..c34abd0 100644
--- a/latex/latex/expander.py
+++ b/latex/latex/expander.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -30,69 +30,69 @@ from parser import Node
class LaTeXReferenceExpander(object):
- """
- This expands '\include' and '\input' commands by parsing the referenced child
- documents. The resulting trees may be attached to the parent tree.
- """
-
- # TODO: embed this into parser so that we don't need to walk the document again
-
- _log = getLogger("ReferenceExpander")
-
- def __init__(self):
- self._document_cache = LaTeXDocumentCache()
-
- def expand(self, documentNode, master_file, issue_handler, charset):
- """
- @param documentNode: the master model
- @param master_file: the File object of the master
- @param issue_handler: an IIssueHandler object
- @param charset: a string naming the character set used by Gedit
- """
- self._master_file = master_file
- self._issue_handler = issue_handler
- self._charset = charset
- self._expand(documentNode)
-
- def _expand(self, parentNode):
- """
- Recursively walk all nodes in the document model and check for \input or \include
- commands. Extract the filename from the command and parse the referenced file.
- Attach its model as a DOCUMENT node to the master model and hold its filename as
- the value of that DOCUMENT node, so that the Validator may differ between issue
- sources.
- """
- for node in parentNode:
- if node.type == Node.COMMAND and (node.value == "input" or node.value == "include"):
- try:
- # build child filename (absolute/relative, with .tex/without .tex)
- target = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
-
- file = None
- if File.is_absolute(target):
- # absolute path
- # look for 'x' and then for 'x.tex'
- file = File("%s.tex" % target)
- if not file.exists:
- file = File(target)
- else:
- # path relative to the master file's directory
- # TODO: include TeX search path!
- # look for 'x' and then for 'x.tex'
- file = File.create_from_relative_path("%s.tex" % target, self._master_file.dirname)
- if not file.exists:
- file = File.create_from_relative_path(target, self._master_file.dirname)
-
- self._log.debug("Expanding %s" % file)
-
- # lookup/parse child document model
- try:
- fragment = self._document_cache.get_document(file, self._charset, self._issue_handler)
-
- node.append(fragment)
- except IOError:
- self._log.error("Referenced file not found: %s" % file.uri)
- except IndexError:
- self._log.error("Malformed reference command at %s" % node.start)
-
- self._expand(node)
+ """
+ This expands '\include' and '\input' commands by parsing the referenced child
+ documents. The resulting trees may be attached to the parent tree.
+ """
+
+ # TODO: embed this into parser so that we don't need to walk the document again
+
+ _log = getLogger("ReferenceExpander")
+
+ def __init__(self):
+ self._document_cache = LaTeXDocumentCache()
+
+ def expand(self, documentNode, master_file, issue_handler, charset):
+ """
+ @param documentNode: the master model
+ @param master_file: the File object of the master
+ @param issue_handler: an IIssueHandler object
+ @param charset: a string naming the character set used by Gedit
+ """
+ self._master_file = master_file
+ self._issue_handler = issue_handler
+ self._charset = charset
+ self._expand(documentNode)
+
+ def _expand(self, parentNode):
+ """
+ Recursively walk all nodes in the document model and check for \input or \include
+ commands. Extract the filename from the command and parse the referenced file.
+ Attach its model as a DOCUMENT node to the master model and hold its filename as
+ the value of that DOCUMENT node, so that the Validator may differ between issue
+ sources.
+ """
+ for node in parentNode:
+ if node.type == Node.COMMAND and (node.value == "input" or node.value == "include"):
+ try:
+ # build child filename (absolute/relative, with .tex/without .tex)
+ target = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+
+ file = None
+ if File.is_absolute(target):
+ # absolute path
+ # look for 'x' and then for 'x.tex'
+ file = File("%s.tex" % target)
+ if not file.exists:
+ file = File(target)
+ else:
+ # path relative to the master file's directory
+ # TODO: include TeX search path!
+ # look for 'x' and then for 'x.tex'
+ file = File.create_from_relative_path("%s.tex" % target, self._master_file.dirname)
+ if not file.exists:
+ file = File.create_from_relative_path(target, self._master_file.dirname)
+
+ self._log.debug("Expanding %s" % file)
+
+ # lookup/parse child document model
+ try:
+ fragment = self._document_cache.get_document(file, self._charset, self._issue_handler)
+
+ node.append(fragment)
+ except IOError:
+ self._log.error("Referenced file not found: %s" % file.uri)
+ except IndexError:
+ self._log.error("Malformed reference command at %s" % node.start)
+
+ self._expand(node)
diff --git a/latex/latex/inversesearch.py b/latex/latex/inversesearch.py
index f80aaa8..10ed416 100644
--- a/latex/latex/inversesearch.py
+++ b/latex/latex/inversesearch.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -42,56 +42,55 @@ BUS_NAME = 'org.gedit.LaTeXPlugin'
OBJECT_PATH = '/org/gedit/LaTeXPlugin/InverseSearchService'
try:
- import dbus
- import dbus.service
- import dbus.glib # attach D-Bus connections to main loop
-
- class InverseSearchService(dbus.service.Object):
- """
- A D-Bus object listening for commands from xdvi. This is a delegate object
- for GeditWindowDecorator.
-
- @deprecated:
- """
-
- def __init__(self, context):
- """
- Construct the service object
-
- @param context: a base.WindowContext instance
- """
- bus_name = dbus.service.BusName(BUS_NAME, bus=dbus.SessionBus())
- dbus.service.Object.__init__(self, bus_name, OBJECT_PATH)
-
- self._context = context
-
- _log.debug("Created service object %s" % OBJECT_PATH)
-
- @dbus.service.method('org.gedit.LaTeXPlugin.InverseSearchService')
- def inverse_search(self, filename, line):
- """
- A service call has been received
-
- @param filename: the filename
- @param line: a line number counting from 1 (!)
- """
- _log.debug("Received inverse DVI search call: %s %s" % (filename, line))
-
- file = File(filename)
-
- try:
- self._context.activate_editor(file)
- editor = self._context.active_editor
-
- assert type(editor) is LaTeXEditor
-
- editor.select_lines(line - 1)
-
- except KeyError:
- _log.error("Could not activate tab for file %s" % filename)
-
+ import dbus
+ import dbus.service
+ import dbus.glib # attach D-Bus connections to main loop
+
+ class InverseSearchService(dbus.service.Object):
+ """
+ A D-Bus object listening for commands from xdvi. This is a delegate object
+ for GeditWindowDecorator.
+
+ @deprecated:
+ """
+
+ def __init__(self, context):
+ """
+ Construct the service object
+
+ @param context: a base.WindowContext instance
+ """
+ bus_name = dbus.service.BusName(BUS_NAME, bus=dbus.SessionBus())
+ dbus.service.Object.__init__(self, bus_name, OBJECT_PATH)
+
+ self._context = context
+
+ _log.debug("Created service object %s" % OBJECT_PATH)
+
+ @dbus.service.method('org.gedit.LaTeXPlugin.InverseSearchService')
+ def inverse_search(self, filename, line):
+ """
+ A service call has been received
+
+ @param filename: the filename
+ @param line: a line number counting from 1 (!)
+ """
+ _log.debug("Received inverse DVI search call: %s %s" % (filename, line))
+
+ file = File(filename)
+
+ try:
+ self._context.activate_editor(file)
+ editor = self._context.active_editor
+
+ assert type(editor) is LaTeXEditor
+
+ editor.select_lines(line - 1)
+
+ except KeyError:
+ _log.error("Could not activate tab for file %s" % filename)
+
except ImportError:
- # TODO: popup a message
- _log.error("Failed to import D-Bus bindings")
-
-
\ No newline at end of file
+ # TODO: popup a message
+ _log.error("Failed to import D-Bus bindings")
+
diff --git a/latex/latex/lexer.py b/latex/latex/lexer.py
index 66c9570..87a76ad 100644
--- a/latex/latex/lexer.py
+++ b/latex/latex/lexer.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -23,299 +23,299 @@ latex.lexer
"""
class StringListener(object):
- """
- Recognizes a string in a stream of characters
- """
- def __init__(self, string, any_position=True):
- """
- @param string: the character sequence to be recognized
- @param any_position: if True the sequence may occur at any position in
- the stream, if False it must occur at the start
- """
- self._string = string
- self._last = len(string)
- self._pos = 0
- self._any_position = any_position
-
- self._active = True
-
- def put(self, char):
- """
- Returns True if the string is recognized
- """
- if not self._active:
- return False
-
- if char == self._string[self._pos]:
- self._pos += 1
-
- if self._pos == self._last:
- return True
- else:
- if self._any_position:
- self._pos = 0
- else:
- self._active = False
-
- return False
+ """
+ Recognizes a string in a stream of characters
+ """
+ def __init__(self, string, any_position=True):
+ """
+ @param string: the character sequence to be recognized
+ @param any_position: if True the sequence may occur at any position in
+ the stream, if False it must occur at the start
+ """
+ self._string = string
+ self._last = len(string)
+ self._pos = 0
+ self._any_position = any_position
+
+ self._active = True
+
+ def put(self, char):
+ """
+ Returns True if the string is recognized
+ """
+ if not self._active:
+ return False
+
+ if char == self._string[self._pos]:
+ self._pos += 1
+
+ if self._pos == self._last:
+ return True
+ else:
+ if self._any_position:
+ self._pos = 0
+ else:
+ self._active = False
+
+ return False
from ..util import StringReader
class Token(object):
- """
- A Token returned by the Lexer
- """
-
- COMMAND, TEXT, COMMENT, VERBATIM, BEGIN_CURLY, END_CURLY, BEGIN_SQUARE, END_SQUARE = range(8)
-
- def __init__(self, type, offset=None, value=None):
- self.type = type
- self.offset = offset
- self.value = value
-
- @property
- def xml(self):
- if self.type == self.COMMAND:
- return "<t:command>%s</t:command>" % self.value
- elif self.type == self.TEXT:
- return "<t:text>%s</t:text>" % self.value
- elif self.type == self.VERBATIM:
- return "<t:verbatim>%s</t:verbatim>" % self.value
- elif self.type == self.COMMENT:
- return "<t:comment>%s</t:comment>" % self.value
- else:
- return "<t:terminal />"
+ """
+ A Token returned by the Lexer
+ """
+
+ COMMAND, TEXT, COMMENT, VERBATIM, BEGIN_CURLY, END_CURLY, BEGIN_SQUARE, END_SQUARE = range(8)
+
+ def __init__(self, type, offset=None, value=None):
+ self.type = type
+ self.offset = offset
+ self.value = value
+
+ @property
+ def xml(self):
+ if self.type == self.COMMAND:
+ return "<t:command>%s</t:command>" % self.value
+ elif self.type == self.TEXT:
+ return "<t:text>%s</t:text>" % self.value
+ elif self.type == self.VERBATIM:
+ return "<t:verbatim>%s</t:verbatim>" % self.value
+ elif self.type == self.COMMENT:
+ return "<t:comment>%s</t:comment>" % self.value
+ else:
+ return "<t:terminal />"
class Lexer(object):
- """
- LaTeX lexer
- """
-
- # TODO: redesign and optimize this from a DFA
-
- # states of the lexer
- _DEFAULT, _BACKSLASH, _COMMAND, _TEXT, _COMMENT, _PRE_VERB, _VERB, _VERBATIM = range(8)
-
- _SPECIAL = set(["&", "$", "{", "}", "[", "]", "%", "#", "_", "\\"])
-
- _TERMINALS = set(["{", "}", "[", "]"])
- _TERMINALS_MAP = {"{" : Token.BEGIN_CURLY, "}" : Token.END_CURLY,
- "[" : Token.BEGIN_SQUARE, "]" : Token.END_SQUARE}
-
- _VERBATIM_ENVIRONS = set(["verbatim", "verbatim*", "lstlisting", "lstlisting*"])
-
-
- # additional states for recognizing "\begin{verbatim}"
- _VERBATIM_DEFAULT, _VERBATIM_BEGIN, _VERBATIM_BEGIN_CURLY, _VERBATIM_BEGIN_CURLY_ENVIRON = range(4)
-
-
- def __init__(self, string, skipWs=True, skipComment=False):
- self._reader = StringReader(string)
-
- self._skipWs = skipWs
- self._skipComment = skipComment
-
- self._state = self._DEFAULT
- self._verbatimState = self._VERBATIM_DEFAULT
-
- self._eof = False
- self._tokenStack = [] # used to return a sequence of tokens after a verbatim ended
-
- def __iter__(self):
- return self
-
- def next(self):
- if self._eof:
- raise StopIteration
-
- # first empty the token stack
- if len(self._tokenStack):
- return self._tokenStack.pop()
-
- while True:
- try:
- char = self._reader.read()
-
- if self._state == self._DEFAULT:
- if char == "\\":
- self._state = self._BACKSLASH
- self._verbatimState = self._VERBATIM_DEFAULT
- self._startOffset = self._reader.offset - 1
-
- elif char == "%":
- self._state = self._COMMENT
- self._verbatimState = self._VERBATIM_DEFAULT
- self._startOffset = self._reader.offset - 1
- if not self._skipComment:
- self._text = []
-
- elif char in self._TERMINALS:
- if self._verbatimState == self._VERBATIM_BEGIN and char == "{":
- self._verbatimState = self._VERBATIM_BEGIN_CURLY
-
- elif self._verbatimState == self._VERBATIM_BEGIN_CURLY_ENVIRON and char == "}":
- # we have "\begin{verbatim}"
- self._verbatimState = self._VERBATIM_DEFAULT
- self._state = self._VERBATIM
- self._text = []
- self._startOffset = self._reader.offset
- self._verbatimSequenceListener = StringListener("\\end{%s}" % self._verbatimEnviron)
-
- else:
- self._verbatimState = self._VERBATIM_DEFAULT
-
- return Token(self._TERMINALS_MAP[char], self._reader.offset - 1)
-
- else:
- self._state = self._TEXT
- self._startOffset = self._reader.offset - 1
- self._text = [char]
-
- elif self._state == self._BACKSLASH:
- if char in self._SPECIAL or char.isspace():
- # this is a one-character-command, also whitespace is allowed
- self._state = self._DEFAULT
- return Token(Token.COMMAND, self._startOffset, char)
-
- else:
- self._state = self._COMMAND
-
- self._verbListener = StringListener("verb", any_position=False)
- self._verbListener.put(char)
-
- self._text = [char]
-
- elif self._state == self._COMMENT:
- if char == "\n":
- self._state = self._DEFAULT
- if not self._skipComment:
- return Token(Token.COMMENT, self._startOffset, "".join(self._text))
-
- else:
- if not self._skipComment:
- self._text.append(char)
-
- elif self._state == self._COMMAND:
- if char in self._SPECIAL or char.isspace():
-
- name = "".join(self._text)
-
- # this is mostly false because \verb is mostly followed by something like |
- if name == "verb":
- self._state = self._VERB
- self._verbDelimiter = char
- self._startOffset = self._reader.offset - 1
- self._text = [char]
-
- elif name == "url": # we handle "\url" just like "\verb"
- self._state = self._VERB
- self._verbDelimiter = "}"
- self._startOffset = self._reader.offset - 1
- self._text = []
-
- else:
- self._state = self._DEFAULT
- self._reader.unread(char)
-
- if name == "begin":
- self._verbatimState = self._VERBATIM_BEGIN
-
- return Token(Token.COMMAND, self._startOffset, name)
-
- else:
- if self._verbListener.put(char):
- # we have "\verb"
- self._state = self._PRE_VERB
- else:
- self._text.append(char)
-
- elif self._state == self._PRE_VERB:
- self._state = self._VERB
- self._verbDelimiter = char
- self._startOffset = self._reader.offset - 1
- self._text = []
-
- elif self._state == self._TEXT:
- if char in self._SPECIAL:
- self._state = self._DEFAULT
- self._reader.unread(char)
-
- text = "".join(self._text)
-
- if self._skipWs and text.isspace():
- continue
- else:
-
- if self._verbatimState == self._VERBATIM_BEGIN_CURLY:
- # we have "\begin{" until now, handle verbatim environment
-
- if text in self._VERBATIM_ENVIRONS:
- self._verbatimEnviron = text
- self._verbatimState = self._VERBATIM_BEGIN_CURLY_ENVIRON
-
- else:
- self._verbatimState = self._VERBATIM_DEFAULT
-
- return Token(Token.TEXT, self._startOffset, text)
-
- else:
- self._text.append(char)
-
- elif self._state == self._VERB:
- if char == self._verbDelimiter: # FIXME: \overbrace
- self._state = self._DEFAULT
-
- return Token(Token.VERBATIM, self._startOffset, "".join(self._text) + char)
-
- else:
- self._text.append(char)
-
- elif self._state == self._VERBATIM:
- if self._verbatimSequenceListener.put(char):
- self._state = self._DEFAULT
-
- # TODO: calculate offsets
- self._tokenStack = [ Token(Token.END_CURLY, 0),
- Token(Token.TEXT, 0, self._verbatimEnviron),
- Token(Token.BEGIN_CURLY, 0),
- Token(Token.COMMAND, 0, "end") ]
-
- text = "".join(self._text)
- text = text[5:] # cut off "\end{"
- return Token(Token.VERBATIM, self._startOffset, text)
- else:
- self._text.append(char)
-
- elif self._state == self._VERB:
- # this char is the verb delimiter
- # TODO: implement verbatim detection
- pass
-
- except StopIteration:
- self._eof = True
-
- # evaluate final state
- if self._state == self._BACKSLASH:
- return Token(Token.COMMAND, self._startOffset, "")
-
- elif self._state == self._COMMAND:
- return Token(Token.COMMAND, self._startOffset, "".join(self._text))
-
- elif self._state == self._TEXT:
- text = "".join(self._text)
- if not (self._skipWs and text.isspace()):
- return Token(Token.TEXT, self._startOffset, text)
-
- elif self._state == self._VERB:
-
- # TODO: the document is malformed in this case, so the lexer should be
- # able to produce issues, too
- #
- # TODO: return a VERBATIM token
-
- return Token(Token.TEXT, self._startOffset, "".join(self._text))
-
- raise StopIteration
\ No newline at end of file
+ """
+ LaTeX lexer
+ """
+
+ # TODO: redesign and optimize this from a DFA
+
+ # states of the lexer
+ _DEFAULT, _BACKSLASH, _COMMAND, _TEXT, _COMMENT, _PRE_VERB, _VERB, _VERBATIM = range(8)
+
+ _SPECIAL = set(["&", "$", "{", "}", "[", "]", "%", "#", "_", "\\"])
+
+ _TERMINALS = set(["{", "}", "[", "]"])
+ _TERMINALS_MAP = {"{" : Token.BEGIN_CURLY, "}" : Token.END_CURLY,
+ "[" : Token.BEGIN_SQUARE, "]" : Token.END_SQUARE}
+
+ _VERBATIM_ENVIRONS = set(["verbatim", "verbatim*", "lstlisting", "lstlisting*"])
+
+
+ # additional states for recognizing "\begin{verbatim}"
+ _VERBATIM_DEFAULT, _VERBATIM_BEGIN, _VERBATIM_BEGIN_CURLY, _VERBATIM_BEGIN_CURLY_ENVIRON = range(4)
+
+
+ def __init__(self, string, skipWs=True, skipComment=False):
+ self._reader = StringReader(string)
+
+ self._skipWs = skipWs
+ self._skipComment = skipComment
+
+ self._state = self._DEFAULT
+ self._verbatimState = self._VERBATIM_DEFAULT
+
+ self._eof = False
+ self._tokenStack = [] # used to return a sequence of tokens after a verbatim ended
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ if self._eof:
+ raise StopIteration
+
+ # first empty the token stack
+ if len(self._tokenStack):
+ return self._tokenStack.pop()
+
+ while True:
+ try:
+ char = self._reader.read()
+
+ if self._state == self._DEFAULT:
+ if char == "\\":
+ self._state = self._BACKSLASH
+ self._verbatimState = self._VERBATIM_DEFAULT
+ self._startOffset = self._reader.offset - 1
+
+ elif char == "%":
+ self._state = self._COMMENT
+ self._verbatimState = self._VERBATIM_DEFAULT
+ self._startOffset = self._reader.offset - 1
+ if not self._skipComment:
+ self._text = []
+
+ elif char in self._TERMINALS:
+ if self._verbatimState == self._VERBATIM_BEGIN and char == "{":
+ self._verbatimState = self._VERBATIM_BEGIN_CURLY
+
+ elif self._verbatimState == self._VERBATIM_BEGIN_CURLY_ENVIRON and char == "}":
+ # we have "\begin{verbatim}"
+ self._verbatimState = self._VERBATIM_DEFAULT
+ self._state = self._VERBATIM
+ self._text = []
+ self._startOffset = self._reader.offset
+ self._verbatimSequenceListener = StringListener("\\end{%s}" % self._verbatimEnviron)
+
+ else:
+ self._verbatimState = self._VERBATIM_DEFAULT
+
+ return Token(self._TERMINALS_MAP[char], self._reader.offset - 1)
+
+ else:
+ self._state = self._TEXT
+ self._startOffset = self._reader.offset - 1
+ self._text = [char]
+
+ elif self._state == self._BACKSLASH:
+ if char in self._SPECIAL or char.isspace():
+ # this is a one-character-command, also whitespace is allowed
+ self._state = self._DEFAULT
+ return Token(Token.COMMAND, self._startOffset, char)
+
+ else:
+ self._state = self._COMMAND
+
+ self._verbListener = StringListener("verb", any_position=False)
+ self._verbListener.put(char)
+
+ self._text = [char]
+
+ elif self._state == self._COMMENT:
+ if char == "\n":
+ self._state = self._DEFAULT
+ if not self._skipComment:
+ return Token(Token.COMMENT, self._startOffset, "".join(self._text))
+
+ else:
+ if not self._skipComment:
+ self._text.append(char)
+
+ elif self._state == self._COMMAND:
+ if char in self._SPECIAL or char.isspace():
+
+ name = "".join(self._text)
+
+ # this is mostly false because \verb is mostly followed by something like |
+ if name == "verb":
+ self._state = self._VERB
+ self._verbDelimiter = char
+ self._startOffset = self._reader.offset - 1
+ self._text = [char]
+
+ elif name == "url": # we handle "\url" just like "\verb"
+ self._state = self._VERB
+ self._verbDelimiter = "}"
+ self._startOffset = self._reader.offset - 1
+ self._text = []
+
+ else:
+ self._state = self._DEFAULT
+ self._reader.unread(char)
+
+ if name == "begin":
+ self._verbatimState = self._VERBATIM_BEGIN
+
+ return Token(Token.COMMAND, self._startOffset, name)
+
+ else:
+ if self._verbListener.put(char):
+ # we have "\verb"
+ self._state = self._PRE_VERB
+ else:
+ self._text.append(char)
+
+ elif self._state == self._PRE_VERB:
+ self._state = self._VERB
+ self._verbDelimiter = char
+ self._startOffset = self._reader.offset - 1
+ self._text = []
+
+ elif self._state == self._TEXT:
+ if char in self._SPECIAL:
+ self._state = self._DEFAULT
+ self._reader.unread(char)
+
+ text = "".join(self._text)
+
+ if self._skipWs and text.isspace():
+ continue
+ else:
+
+ if self._verbatimState == self._VERBATIM_BEGIN_CURLY:
+ # we have "\begin{" until now, handle verbatim environment
+
+ if text in self._VERBATIM_ENVIRONS:
+ self._verbatimEnviron = text
+ self._verbatimState = self._VERBATIM_BEGIN_CURLY_ENVIRON
+
+ else:
+ self._verbatimState = self._VERBATIM_DEFAULT
+
+ return Token(Token.TEXT, self._startOffset, text)
+
+ else:
+ self._text.append(char)
+
+ elif self._state == self._VERB:
+ if char == self._verbDelimiter: # FIXME: \overbrace
+ self._state = self._DEFAULT
+
+ return Token(Token.VERBATIM, self._startOffset, "".join(self._text) + char)
+
+ else:
+ self._text.append(char)
+
+ elif self._state == self._VERBATIM:
+ if self._verbatimSequenceListener.put(char):
+ self._state = self._DEFAULT
+
+ # TODO: calculate offsets
+ self._tokenStack = [ Token(Token.END_CURLY, 0),
+ Token(Token.TEXT, 0, self._verbatimEnviron),
+ Token(Token.BEGIN_CURLY, 0),
+ Token(Token.COMMAND, 0, "end") ]
+
+ text = "".join(self._text)
+ text = text[5:] # cut off "\end{"
+ return Token(Token.VERBATIM, self._startOffset, text)
+ else:
+ self._text.append(char)
+
+ elif self._state == self._VERB:
+ # this char is the verb delimiter
+ # TODO: implement verbatim detection
+ pass
+
+ except StopIteration:
+ self._eof = True
+
+ # evaluate final state
+ if self._state == self._BACKSLASH:
+ return Token(Token.COMMAND, self._startOffset, "")
+
+ elif self._state == self._COMMAND:
+ return Token(Token.COMMAND, self._startOffset, "".join(self._text))
+
+ elif self._state == self._TEXT:
+ text = "".join(self._text)
+ if not (self._skipWs and text.isspace()):
+ return Token(Token.TEXT, self._startOffset, text)
+
+ elif self._state == self._VERB:
+
+ # TODO: the document is malformed in this case, so the lexer should be
+ # able to produce issues, too
+ #
+ # TODO: return a VERBATIM token
+
+ return Token(Token.TEXT, self._startOffset, "".join(self._text))
+
+ raise StopIteration
\ No newline at end of file
diff --git a/latex/latex/listing.py b/latex/latex/listing.py
index 1746fb5..a9627b8 100644
--- a/latex/latex/listing.py
+++ b/latex/latex/listing.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -26,49 +26,48 @@ from xml.sax import ContentHandler, parse
class Language(object):
- def __init__(self, name):
- self.name = name
- self.dialects = []
-
+ def __init__(self, name):
+ self.name = name
+ self.dialects = []
+
class Dialect(object):
- def __init__(self, name, default):
- self.name = name
- self.default = default
-
+ def __init__(self, name, default):
+ self.name = name
+ self.default = default
+
class LanguagesParser(ContentHandler):
- """
- This parses the listing.xml file containing the available languages for listings
- """
- def __init__(self):
- self._languages = None
- self._language = None
-
- def startElement(self, name, attributes):
- if name == "language":
- l = Language(attributes["name"])
- self._languages.append(l)
- self._language = l
- elif name == "dialect":
- default = False
- try:
- if attributes["default"] == "true":
- default = True
- except KeyError:
- pass
-
- self._language.dialects.append(Dialect(attributes["name"], default))
- elif name == "no-dialect":
- self._language.dialects.append(Dialect(None, True))
-
- def parse(self, languages, filename):
- """
- Parse XML
-
- "languages" must be a list
- """
- self._languages = languages
- parse(filename, self)
-
-
\ No newline at end of file
+ """
+ This parses the listing.xml file containing the available languages for listings
+ """
+ def __init__(self):
+ self._languages = None
+ self._language = None
+
+ def startElement(self, name, attributes):
+ if name == "language":
+ l = Language(attributes["name"])
+ self._languages.append(l)
+ self._language = l
+ elif name == "dialect":
+ default = False
+ try:
+ if attributes["default"] == "true":
+ default = True
+ except KeyError:
+ pass
+
+ self._language.dialects.append(Dialect(attributes["name"], default))
+ elif name == "no-dialect":
+ self._language.dialects.append(Dialect(None, True))
+
+ def parse(self, languages, filename):
+ """
+ Parse XML
+
+ "languages" must be a list
+ """
+ self._languages = languages
+ parse(filename, self)
+
diff --git a/latex/latex/model.py b/latex/latex/model.py
index 885d6ff..9d1f28d 100644
--- a/latex/latex/model.py
+++ b/latex/latex/model.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -28,214 +28,214 @@ from logging import getLogger
class Element(object):
- """
- """
- TYPE_COMMAND, TYPE_MANDATORY_ARGUMENT, TYPE_OPTIONAL_ARGUMENT, TYPE_CHOICE, TYPE_PLACEHOLDER = 1, 2, 3, 4, 5
-
- def __init__(self, package, type):
- """
- @param package: the name of the LaTeX package providing this Element
- @param type: one of TYPE_*
- """
- self.package = package
- self.type = type
- self._children = []
-
- @property
- def children(self):
- return self._children
-
- def append_child(self, child):
- self._children.append(child)
-
+ """
+ """
+ TYPE_COMMAND, TYPE_MANDATORY_ARGUMENT, TYPE_OPTIONAL_ARGUMENT, TYPE_CHOICE, TYPE_PLACEHOLDER = 1, 2, 3, 4, 5
+
+ def __init__(self, package, type):
+ """
+ @param package: the name of the LaTeX package providing this Element
+ @param type: one of TYPE_*
+ """
+ self.package = package
+ self.type = type
+ self._children = []
+
+ @property
+ def children(self):
+ return self._children
+
+ def append_child(self, child):
+ self._children.append(child)
+
class Command(Element):
- def __init__(self, package, name):
- Element.__init__(self, package, Element.TYPE_COMMAND)
- self.name = name
-
- @property
- def first_mandatory_argument(self):
- for node in self.children:
- if node.type == Element.TYPE_MANDATORY_ARGUMENT:
- return node
- raise IndexError
-
- @property
- def first_optional_argument(self):
- for node in self.children:
- if node.type == Element.TYPE_OPTIONAL_ARGUMENT:
- return node
- raise IndexError
+ def __init__(self, package, name):
+ Element.__init__(self, package, Element.TYPE_COMMAND)
+ self.name = name
+
+ @property
+ def first_mandatory_argument(self):
+ for node in self.children:
+ if node.type == Element.TYPE_MANDATORY_ARGUMENT:
+ return node
+ raise IndexError
+
+ @property
+ def first_optional_argument(self):
+ for node in self.children:
+ if node.type == Element.TYPE_OPTIONAL_ARGUMENT:
+ return node
+ raise IndexError
class Argument(Element):
- def get_children(self):
- """
- Override the children property of Element to be able to evaluate Placeholders
- allowed as children of Arguments
- """
- children = []
-
- for node in self._children:
- if node.type == Element.TYPE_PLACEHOLDER:
- children.extend(node.children)
- else:
- children.append(node)
- return children
-
- def set_children(self, children):
- self._children = children
-
- children = property(get_children, set_children)
+ def get_children(self):
+ """
+ Override the children property of Element to be able to evaluate Placeholders
+ allowed as children of Arguments
+ """
+ children = []
+
+ for node in self._children:
+ if node.type == Element.TYPE_PLACEHOLDER:
+ children.extend(node.children)
+ else:
+ children.append(node)
+ return children
+
+ def set_children(self, children):
+ self._children = children
+
+ children = property(get_children, set_children)
class MandatoryArgument(Argument):
- def __init__(self, package, label):
- Argument.__init__(self, package, Element.TYPE_MANDATORY_ARGUMENT)
- self.label = label
+ def __init__(self, package, label):
+ Argument.__init__(self, package, Element.TYPE_MANDATORY_ARGUMENT)
+ self.label = label
class OptionalArgument(Argument):
- def __init__(self, package, label):
- Argument.__init__(self, package, Element.TYPE_OPTIONAL_ARGUMENT)
- self.label = label
-
-
+ def __init__(self, package, label):
+ Argument.__init__(self, package, Element.TYPE_OPTIONAL_ARGUMENT)
+ self.label = label
+
+
class Choice(Element):
- def __init__(self, package, value, details=None):
- Element.__init__(self, package, Element.TYPE_CHOICE)
- self.value = value
- self.details = details
+ def __init__(self, package, value, details=None):
+ Element.__init__(self, package, Element.TYPE_CHOICE)
+ self.value = value
+ self.details = details
class Placeholder(Element):
- def __init__(self, name):
- Element.__init__(self, None, Element.TYPE_PLACEHOLDER)
- self.name = name
-
- def get_children(self):
- return self._children
-
- def set_children(self, children):
- self._children = children
-
- children = property(get_children, set_children)
+ def __init__(self, name):
+ Element.__init__(self, None, Element.TYPE_PLACEHOLDER)
+ self.name = name
+
+ def get_children(self):
+ return self._children
+
+ def set_children(self, children):
+ self._children = children
+
+ children = property(get_children, set_children)
class LanguageModel(object):
- """
- """
-
- __log = getLogger("LanguageModel")
-
- def __init__(self):
- self.commands = {} # maps command names to Command elements
-
- self.__placeholders = {}
- self.__newcommands = []
-
- self.__log.debug("init")
-
- def find_command(self, prefix):
- """
- Find a command by a prefix. A prefix like 'be' would return the command '\begin'
- """
- return [command for name, command in self.commands.iteritems() if name.startswith(prefix)]
-
- def register_placeholder(self, placeholder):
- """
- Register a placeholder under its name. There may be multiple
- placeholder nodes for one name.
- """
- try:
- nodes = self.__placeholders[placeholder.name]
- nodes.append(placeholder)
- except KeyError:
- self.__placeholders[placeholder.name] = [placeholder]
-
- def fill_placeholder(self, name, child_elements):
- """
- Attach child elements to a placeholder
- """
- try:
- #self.__log.debug("fill_placeholder: name=%s, child_elements=%s" % (name, child_elements))
-
- for placeholder in self.__placeholders[name]:
- placeholder.children = child_elements
- except KeyError:
- self.__log.error("fill_placeholder: placeholder '%s' not registered" % name)
-
- def set_newcommands(self, newcommands):
-
- # TODO: use sets
-
- self.__log.debug("set_newcommands: " + ",".join([c.name for c in newcommands]))
-
- for name in self.__newcommands:
- self.commands.__delitem__(name)
-
- for command in newcommands:
- self.commands[command.name] = command
+ """
+ """
+
+ __log = getLogger("LanguageModel")
+
+ def __init__(self):
+ self.commands = {} # maps command names to Command elements
+
+ self.__placeholders = {}
+ self.__newcommands = []
+
+ self.__log.debug("init")
+
+ def find_command(self, prefix):
+ """
+ Find a command by a prefix. A prefix like 'be' would return the command '\begin'
+ """
+ return [command for name, command in self.commands.iteritems() if name.startswith(prefix)]
+
+ def register_placeholder(self, placeholder):
+ """
+ Register a placeholder under its name. There may be multiple
+ placeholder nodes for one name.
+ """
+ try:
+ nodes = self.__placeholders[placeholder.name]
+ nodes.append(placeholder)
+ except KeyError:
+ self.__placeholders[placeholder.name] = [placeholder]
+
+ def fill_placeholder(self, name, child_elements):
+ """
+ Attach child elements to a placeholder
+ """
+ try:
+ #self.__log.debug("fill_placeholder: name=%s, child_elements=%s" % (name, child_elements))
+
+ for placeholder in self.__placeholders[name]:
+ placeholder.children = child_elements
+ except KeyError:
+ self.__log.error("fill_placeholder: placeholder '%s' not registered" % name)
+
+ def set_newcommands(self, newcommands):
+
+ # TODO: use sets
+
+ self.__log.debug("set_newcommands: " + ",".join([c.name for c in newcommands]))
+
+ for name in self.__newcommands:
+ self.commands.__delitem__(name)
+
+ for command in newcommands:
+ self.commands[command.name] = command
from xml import sax
class LanguageModelParser(sax.ContentHandler):
- """
- SAX parser for the language model in latex.xml
- """
-
- # TODO: this should be a simple state machine
-
- __log = getLogger("LanguageModelParser")
-
- def parse(self, filename, language_model):
- self.__language_model = language_model
-
- self.__command = None
- self.__argument = None
-
- self.__log.debug("Parsing %s" % filename)
-
- sax.parse(filename, self)
-
- def startElement(self, name, attrs):
- try:
- package = attrs["package"]
- except KeyError:
- package = None
-
- if name == "command":
- name = attrs["name"]
- self.__command = Command(package, name)
- self.__language_model.commands[name] = self.__command
-
- elif name == "argument":
- try:
- label = attrs["label"]
- except KeyError:
- label = ""
-
- try:
- if attrs["type"] == "optional":
- self.__argument = OptionalArgument(package, label)
- else:
- self.__argument = MandatoryArgument(package, label)
- except KeyError:
- self.__argument = MandatoryArgument(package, label)
-
- self.__command.children.append(self.__argument)
-
- elif name == "choice":
- choice = Choice(package, attrs["name"])
- self.__argument.append_child(choice)
-
- elif name == "placeholder":
- placeholder = Placeholder(attrs["key"])
- self.__argument.append_child(placeholder)
- self.__language_model.register_placeholder(placeholder)
+ """
+ SAX parser for the language model in latex.xml
+ """
+
+ # TODO: this should be a simple state machine
+
+ __log = getLogger("LanguageModelParser")
+
+ def parse(self, filename, language_model):
+ self.__language_model = language_model
+
+ self.__command = None
+ self.__argument = None
+
+ self.__log.debug("Parsing %s" % filename)
+
+ sax.parse(filename, self)
+
+ def startElement(self, name, attrs):
+ try:
+ package = attrs["package"]
+ except KeyError:
+ package = None
+
+ if name == "command":
+ name = attrs["name"]
+ self.__command = Command(package, name)
+ self.__language_model.commands[name] = self.__command
+
+ elif name == "argument":
+ try:
+ label = attrs["label"]
+ except KeyError:
+ label = ""
+
+ try:
+ if attrs["type"] == "optional":
+ self.__argument = OptionalArgument(package, label)
+ else:
+ self.__argument = MandatoryArgument(package, label)
+ except KeyError:
+ self.__argument = MandatoryArgument(package, label)
+
+ self.__command.children.append(self.__argument)
+
+ elif name == "choice":
+ choice = Choice(package, attrs["name"])
+ self.__argument.append_child(choice)
+
+ elif name == "placeholder":
+ placeholder = Placeholder(attrs["key"])
+ self.__argument.append_child(placeholder)
+ self.__language_model.register_placeholder(placeholder)
from copy import deepcopy
@@ -246,62 +246,62 @@ from ..base import File
class LanguageModelFactory(object):
- """
- This singleton creates LanguageModel instances.
-
- If a serialized ('pickled') LanguageModel object exists, then a copy
- of this object is returned. Otherwise the XML file must be parsed.
- """
-
- __log = getLogger("LanguageModelFactory")
-
- def __new__(cls):
- if not '_instance' in cls.__dict__:
- cls._instance = object.__new__(cls)
- return cls._instance
-
- def __init__(self):
- if not '_ready' in dir(self):
-
- pickled_object = self.__find_pickled_object()
-
- if pickled_object:
- self.__language_model = pickled_object
- else:
- pkl_filename = find_resource("latex.pkl", MODE_READWRITE)
- xml_filename = find_resource("latex.xml")
-
- self.__language_model = LanguageModel()
- parser = LanguageModelParser()
- parser.parse(xml_filename, self.__language_model)
-
- pickle.dump(self.__language_model, open(pkl_filename, 'w'))
-
- self._ready = True
-
- def __find_pickled_object(self):
- pkl_file = File(find_resource("latex.pkl", MODE_READWRITE))
- xml_file = File(find_resource("latex.xml"))
-
- if pkl_file.exists:
- if xml_file.mtime > pkl_file.mtime:
- self.__log.debug("Pickled object and XML file have different modification times")
- else:
- try:
- self.__log.debug("Pickled object found: %s" % pkl_file.path)
- return pickle.load(open(pkl_file.path))
- except:
- return None
- else:
- self.__log.debug("No pickled object found")
- return None
-
- def create_language_model(self):
- """
- Return a new LanguageModel
- """
- return deepcopy(self.__language_model)
-
-
-
-
+ """
+ This singleton creates LanguageModel instances.
+
+ If a serialized ('pickled') LanguageModel object exists, then a copy
+ of this object is returned. Otherwise the XML file must be parsed.
+ """
+
+ __log = getLogger("LanguageModelFactory")
+
+ def __new__(cls):
+ if not '_instance' in cls.__dict__:
+ cls._instance = object.__new__(cls)
+ return cls._instance
+
+ def __init__(self):
+ if not '_ready' in dir(self):
+
+ pickled_object = self.__find_pickled_object()
+
+ if pickled_object:
+ self.__language_model = pickled_object
+ else:
+ pkl_filename = find_resource("latex.pkl", MODE_READWRITE)
+ xml_filename = find_resource("latex.xml")
+
+ self.__language_model = LanguageModel()
+ parser = LanguageModelParser()
+ parser.parse(xml_filename, self.__language_model)
+
+ pickle.dump(self.__language_model, open(pkl_filename, 'w'))
+
+ self._ready = True
+
+ def __find_pickled_object(self):
+ pkl_file = File(find_resource("latex.pkl", MODE_READWRITE))
+ xml_file = File(find_resource("latex.xml"))
+
+ if pkl_file.exists:
+ if xml_file.mtime > pkl_file.mtime:
+ self.__log.debug("Pickled object and XML file have different modification times")
+ else:
+ try:
+ self.__log.debug("Pickled object found: %s" % pkl_file.path)
+ return pickle.load(open(pkl_file.path))
+ except:
+ return None
+ else:
+ self.__log.debug("No pickled object found")
+ return None
+
+ def create_language_model(self):
+ """
+ Return a new LanguageModel
+ """
+ return deepcopy(self.__language_model)
+
+
+
+
diff --git a/latex/latex/outline.py b/latex/latex/outline.py
index d407885..9a2b0b0 100644
--- a/latex/latex/outline.py
+++ b/latex/latex/outline.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -33,40 +33,40 @@ from ..issues import Issue
class OutlineNode(list):
-
- ROOT, STRUCTURE, LABEL, NEWCOMMAND, REFERENCE, GRAPHICS, PACKAGE, TABLE, NEWENVIRONMENT = range(9)
-
- def __init__(self, type, start=None, end=None, value=None, level=None, foreign=False, numOfArgs=None, file=None):
- """
- numOfArgs only used for NEWCOMMAND type
- """
- self.type = type
- self.start = start
- self.end = end
- self.value = value
- self.level = level
- self.foreign = foreign
- self.numOfArgs = numOfArgs
- self.file = file
-
- @property
- def xml(self):
- if self.type == self.ROOT:
- return "<root>%s</root>" % "".join([child.xml for child in self])
- elif self.type == self.STRUCTURE:
- return "<structure level=\"%s\" headline=\"%s\">%s</structure>" % (self.level, self.value,
- "".join([child.xml for child in self]))
-
+
+ ROOT, STRUCTURE, LABEL, NEWCOMMAND, REFERENCE, GRAPHICS, PACKAGE, TABLE, NEWENVIRONMENT = range(9)
+
+ def __init__(self, type, start=None, end=None, value=None, level=None, foreign=False, numOfArgs=None, file=None):
+ """
+ numOfArgs only used for NEWCOMMAND type
+ """
+ self.type = type
+ self.start = start
+ self.end = end
+ self.value = value
+ self.level = level
+ self.foreign = foreign
+ self.numOfArgs = numOfArgs
+ self.file = file
+
+ @property
+ def xml(self):
+ if self.type == self.ROOT:
+ return "<root>%s</root>" % "".join([child.xml for child in self])
+ elif self.type == self.STRUCTURE:
+ return "<structure level=\"%s\" headline=\"%s\">%s</structure>" % (self.level, self.value,
+ "".join([child.xml for child in self]))
+
class Outline(object):
- def __init__(self):
- self.rootNode = OutlineNode(OutlineNode.ROOT, level=0)
- self.labels = [] # OutlineNode objects
- self.bibliographies = [] # File objects
- self.colors = []
- self.packages = [] # OutlineNode objects
- self.newcommands = [] # OutlineNode objects
- self.newenvironments = [] # OutlineNode objects
+ def __init__(self):
+ self.rootNode = OutlineNode(OutlineNode.ROOT, level=0)
+ self.labels = [] # OutlineNode objects
+ self.bibliographies = [] # File objects
+ self.colors = []
+ self.packages = [] # OutlineNode objects
+ self.newcommands = [] # OutlineNode objects
+ self.newenvironments = [] # OutlineNode objects
from ..base import File
@@ -74,190 +74,189 @@ from ..preferences import Preferences
class LaTeXOutlineGenerator(object):
-
- _log = getLogger("LaTeXOutlineGenerator")
-
- # TODO: foreign flag is not necessary
-
- _STRUCTURE_LEVELS = { "part" : 1, "part*" : 1,
- "chapter" : 2, "chapter*" : 2,
- "section" : 3, "section*" : 3,
- "subsection" : 4, "subsection*" : 4,
- "subsubsection" : 5, "subsubsection*" : 5,
- "paragraph" : 6,
- "subparagraph" : 7 }
-
-# def __init__(self):
-# # TODO: read config
-# self.cfgLabelsInTree = True
-# self.cfgTablesInTree = True
-# self.cfgGraphicsInTree = True
-
- def generate(self, documentNode, issue_handler):
- """
- Generates an outline model from a document model and returns a list
- of list of issues if some occured.
- """
-
- # setup
- self.cfgLabelsInTree = Preferences().get_bool("outline-show-labels")
- self.cfgTablesInTree = Preferences().get_bool("outline-show-tables")
- self.cfgGraphicsInTree = Preferences().get_bool("outline-show-graphics")
-
- self._outline = Outline()
- self._stack = [self._outline.rootNode]
-
- self._labelCache = {}
-
-# self._file = documentNode.value # this is updated when a DOCUMENT occurs
-
- self._walk(documentNode, issue_handler)
-
- return self._outline
-
- def _walk(self, parentNode, issue_handler, foreign=False):
- """
- Recursively walk a node in the document model
-
- foreign if True this node is a child of a reference node, so it's coming
- from an expanded reference
- """
-
- childForeign = foreign
-
- for node in parentNode:
-# if node.type == Node.DOCUMENT:
-# self._file = node.value
- if node.type == Node.COMMAND:
- if node.value in self._STRUCTURE_LEVELS.keys():
- try:
- headline = node.firstOfType(Node.MANDATORY_ARGUMENT).innerMarkup
- level = self._STRUCTURE_LEVELS[node.value]
- outlineNode = OutlineNode(OutlineNode.STRUCTURE, node.start, node.lastEnd, headline, level, foreign, file=node.file)
-
- while self._stack[-1].level >= level:
- self._stack.pop()
-
- self._stack[-1].append(outlineNode)
- self._stack.append(outlineNode)
- except IndexError:
- issue_handler.issue(Issue("Malformed structure command", node.start, node.end, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "label":
- try:
- value = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
-
- if value in self._labelCache.keys():
- start, end = self._labelCache[value]
- issue_handler.issue(Issue("Label <b>%s</b> has already been defined" % value, start, end, node.file, Issue.SEVERITY_ERROR))
- else:
- self._labelCache[value] = (node.start, node.lastEnd)
-
- labelNode = OutlineNode(OutlineNode.LABEL, node.start, node.lastEnd, value, foreign=foreign, file=node.file)
-
- self._outline.labels.append(labelNode)
- if self.cfgLabelsInTree:
- self._stack[-1].append(labelNode)
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
-# elif node.value == "begin":
-# environment = str(node.filter(Node.MANDATORY_ARGUMENT)[0][0])
-# if environment == "lstlisting":
-# # look for label in listing environment
-# try:
-# # TODO: Node should have a method like toDict() or something
-# optionNode = node.filter(Node.OPTIONAL_ARGUMENT)[0]
-# option = "".join([str(child) for child in optionNode])
-# for pair in option.split(","):
-# key, value = pair.split("=")
-# if key.strip() == "label":
-# labelNode = OutlineNode(OutlineNode.LABEL, node.start, node.end, value.strip())
-# outline.labels.append(labelNode)
-# if self.cfgLabelsInTree:
-# stack[-1].append(labelNode)
-# except IndexError:
-# pass
-
- elif node.value == "usepackage":
- try:
- package = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- packageNode = OutlineNode(OutlineNode.PACKAGE, node.start, node.lastEnd, package, file=node.file)
- self._outline.packages.append(packageNode)
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.end, node.file, Issue.SEVERITY_ERROR))
-
- elif self.cfgTablesInTree and node.value == "begin":
- try:
- environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- if environ == "tabular":
- tableNode = OutlineNode(OutlineNode.TABLE, node.start, node.lastEnd, "", foreign=foreign, file=node.file)
- self._stack[-1].append(tableNode)
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif self.cfgGraphicsInTree and node.value == "includegraphics":
- try:
- target = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- graphicsNode = OutlineNode(OutlineNode.GRAPHICS, node.start, node.lastEnd, target, foreign=foreign, file=node.file)
- self._stack[-1].append(graphicsNode)
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "bibliography":
- try:
- value = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- for bib in value.split(","):
- self._outline.bibliographies.append(File("%s/%s.bib" % (node.file.dirname, bib)))
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "definecolor" or node.value == "xdefinecolor":
- try:
- name = str(node.firstOfType(Node.MANDATORY_ARGUMENT)[0])
- self._outline.colors.append(name)
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "newcommand":
- try:
- name = str(node.firstOfType(Node.MANDATORY_ARGUMENT)[0])[1:] # remove "\"
- try:
- nArgs = int(node.filter(Node.OPTIONAL_ARGUMENT)[0].innerText)
- except IndexError:
- nArgs = 0
- except Exception:
- issue_handler.issue(Issue("Malformed newcommand", node.start, node.end, node.file, Issue.SEVERITY_ERROR))
- nArgs = 0
- ncNode = OutlineNode(OutlineNode.NEWCOMMAND, node.start, node.lastEnd, name, numOfArgs=nArgs, file=node.file)
- self._outline.newcommands.append(ncNode)
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- # don't walk through \newcommand
- continue
-
- elif node.value in ["newenvironment", "newtheorem"]:
- try:
- name = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- #try:
- # n_args = int(node.firstOfType(Node.OPTIONAL_ARGUMENT).innerText)
- #except IndexError:
- # n_args = 0
- ne_node = OutlineNode(OutlineNode.NEWENVIRONMENT, node.start, node.lastEnd, name, numOfArgs=0, file=node.file)
- self._outline.newenvironments.append(ne_node)
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "include" or node.value == "input":
- childForeign = True
-
- self._walk(node, issue_handler, childForeign)
-
- #~ def __del__(self):
- #~ print "Properly destroyed %s" % self
-
-
-
-
-
\ No newline at end of file
+
+ _log = getLogger("LaTeXOutlineGenerator")
+
+ # TODO: foreign flag is not necessary
+
+ _STRUCTURE_LEVELS = { "part" : 1, "part*" : 1,
+ "chapter" : 2, "chapter*" : 2,
+ "section" : 3, "section*" : 3,
+ "subsection" : 4, "subsection*" : 4,
+ "subsubsection" : 5, "subsubsection*" : 5,
+ "paragraph" : 6,
+ "subparagraph" : 7 }
+
+# def __init__(self):
+# # TODO: read config
+# self.cfgLabelsInTree = True
+# self.cfgTablesInTree = True
+# self.cfgGraphicsInTree = True
+
+ def generate(self, documentNode, issue_handler):
+ """
+ Generates an outline model from a document model and returns a list
+ of list of issues if some occured.
+ """
+
+ # setup
+ self.cfgLabelsInTree = Preferences().get_bool("outline-show-labels")
+ self.cfgTablesInTree = Preferences().get_bool("outline-show-tables")
+ self.cfgGraphicsInTree = Preferences().get_bool("outline-show-graphics")
+
+ self._outline = Outline()
+ self._stack = [self._outline.rootNode]
+
+ self._labelCache = {}
+
+# self._file = documentNode.value # this is updated when a DOCUMENT occurs
+
+ self._walk(documentNode, issue_handler)
+
+ return self._outline
+
+ def _walk(self, parentNode, issue_handler, foreign=False):
+ """
+ Recursively walk a node in the document model
+
+ foreign if True this node is a child of a reference node, so it's coming
+ from an expanded reference
+ """
+
+ childForeign = foreign
+
+ for node in parentNode:
+# if node.type == Node.DOCUMENT:
+# self._file = node.value
+ if node.type == Node.COMMAND:
+ if node.value in self._STRUCTURE_LEVELS.keys():
+ try:
+ headline = node.firstOfType(Node.MANDATORY_ARGUMENT).innerMarkup
+ level = self._STRUCTURE_LEVELS[node.value]
+ outlineNode = OutlineNode(OutlineNode.STRUCTURE, node.start, node.lastEnd, headline, level, foreign, file=node.file)
+
+ while self._stack[-1].level >= level:
+ self._stack.pop()
+
+ self._stack[-1].append(outlineNode)
+ self._stack.append(outlineNode)
+ except IndexError:
+ issue_handler.issue(Issue("Malformed structure command", node.start, node.end, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "label":
+ try:
+ value = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+
+ if value in self._labelCache.keys():
+ start, end = self._labelCache[value]
+ issue_handler.issue(Issue("Label <b>%s</b> has already been defined" % value, start, end, node.file, Issue.SEVERITY_ERROR))
+ else:
+ self._labelCache[value] = (node.start, node.lastEnd)
+
+ labelNode = OutlineNode(OutlineNode.LABEL, node.start, node.lastEnd, value, foreign=foreign, file=node.file)
+
+ self._outline.labels.append(labelNode)
+ if self.cfgLabelsInTree:
+ self._stack[-1].append(labelNode)
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+# elif node.value == "begin":
+# environment = str(node.filter(Node.MANDATORY_ARGUMENT)[0][0])
+# if environment == "lstlisting":
+# # look for label in listing environment
+# try:
+# # TODO: Node should have a method like toDict() or something
+# optionNode = node.filter(Node.OPTIONAL_ARGUMENT)[0]
+# option = "".join([str(child) for child in optionNode])
+# for pair in option.split(","):
+# key, value = pair.split("=")
+# if key.strip() == "label":
+# labelNode = OutlineNode(OutlineNode.LABEL, node.start, node.end, value.strip())
+# outline.labels.append(labelNode)
+# if self.cfgLabelsInTree:
+# stack[-1].append(labelNode)
+# except IndexError:
+# pass
+
+ elif node.value == "usepackage":
+ try:
+ package = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ packageNode = OutlineNode(OutlineNode.PACKAGE, node.start, node.lastEnd, package, file=node.file)
+ self._outline.packages.append(packageNode)
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.end, node.file, Issue.SEVERITY_ERROR))
+
+ elif self.cfgTablesInTree and node.value == "begin":
+ try:
+ environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ if environ == "tabular":
+ tableNode = OutlineNode(OutlineNode.TABLE, node.start, node.lastEnd, "", foreign=foreign, file=node.file)
+ self._stack[-1].append(tableNode)
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif self.cfgGraphicsInTree and node.value == "includegraphics":
+ try:
+ target = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ graphicsNode = OutlineNode(OutlineNode.GRAPHICS, node.start, node.lastEnd, target, foreign=foreign, file=node.file)
+ self._stack[-1].append(graphicsNode)
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "bibliography":
+ try:
+ value = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ for bib in value.split(","):
+ self._outline.bibliographies.append(File("%s/%s.bib" % (node.file.dirname, bib)))
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "definecolor" or node.value == "xdefinecolor":
+ try:
+ name = str(node.firstOfType(Node.MANDATORY_ARGUMENT)[0])
+ self._outline.colors.append(name)
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "newcommand":
+ try:
+ name = str(node.firstOfType(Node.MANDATORY_ARGUMENT)[0])[1:] # remove "\"
+ try:
+ nArgs = int(node.filter(Node.OPTIONAL_ARGUMENT)[0].innerText)
+ except IndexError:
+ nArgs = 0
+ except Exception:
+ issue_handler.issue(Issue("Malformed newcommand", node.start, node.end, node.file, Issue.SEVERITY_ERROR))
+ nArgs = 0
+ ncNode = OutlineNode(OutlineNode.NEWCOMMAND, node.start, node.lastEnd, name, numOfArgs=nArgs, file=node.file)
+ self._outline.newcommands.append(ncNode)
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ # don't walk through \newcommand
+ continue
+
+ elif node.value in ["newenvironment", "newtheorem"]:
+ try:
+ name = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ #try:
+ # n_args = int(node.firstOfType(Node.OPTIONAL_ARGUMENT).innerText)
+ #except IndexError:
+ # n_args = 0
+ ne_node = OutlineNode(OutlineNode.NEWENVIRONMENT, node.start, node.lastEnd, name, numOfArgs=0, file=node.file)
+ self._outline.newenvironments.append(ne_node)
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "include" or node.value == "input":
+ childForeign = True
+
+ self._walk(node, issue_handler, childForeign)
+
+ #~ def __del__(self):
+ #~ print "Properly destroyed %s" % self
+
+
+
+
diff --git a/latex/latex/parser.py b/latex/latex/parser.py
index b93e7c7..fe04374 100644
--- a/latex/latex/parser.py
+++ b/latex/latex/parser.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -34,678 +34,677 @@ from ..issues import Issue
class Node(list):
- """
- This is the base class of the LaTeX object model
- """
-
- DOCUMENT, COMMAND, MANDATORY_ARGUMENT, OPTIONAL_ARGUMENT, TEXT, EMBRACED = range(6)
-
- def __init__(self, type, value=None):
- self.type = type
- self.value = value
- self.parent = None
-
- # this indicates if an argument is closed or not
- # (only used by the PrefixParser)
- self.closed = False
-
- def firstOfType(self, type):
- """
- Return the first child node of a given type
- """
- for node in self:
- if node.type == type:
- return node
- raise IndexError
-
- def filter(self, type):
- """
- Return all child nodes of this node having a certain type
- """
- return [node for node in self if node.type == type]
-
- @property
- def xml(self):
- """
- Return an XML representation of this node (for debugging)
- """
- if self.type == self.COMMAND:
- content = "".join([node.xml for node in self])
- if len(content):
- return "<command name=\"%s\">%s</command>" % (escape(self.value), content)
- else:
- return "<command name=\"%s\" />" % escape(self.value)
- elif self.type == self.MANDATORY_ARGUMENT:
- return "<mandatory>%s</mandatory>" % "".join([node.xml for node in self])
- elif self.type == self.OPTIONAL_ARGUMENT:
- return "<optional>%s</optional>" % "".join([node.xml for node in self])
- elif self.type == self.TEXT:
- return escape(self.value)
- elif self.type == self.DOCUMENT:
- return "<document>%s</document>" % "".join([node.xml for node in self])
- elif self.type == self.EMBRACED:
- return "<embraced>%s</embraced>" % "".join([node.xml for node in self])
-
- @property
- def xmlPrefix(self):
- """
- Return an XML representation of this node (for debugging)
-
- This is for the prefix mode, so we print if the arguments are closed or not
- """
- if self.type == self.COMMAND:
- content = "".join([node.xmlPrefix for node in self])
- if len(content):
- return "<command name=\"%s\">%s</command>" % (self.value, content)
- else:
- return "<command name=\"%s\" />" % self.value
- elif self.type == self.MANDATORY_ARGUMENT:
- return "<mandatory closed=%s>%s</mandatory>" % (self.closed, "".join([node.xmlPrefix for node in self]))
- elif self.type == self.OPTIONAL_ARGUMENT:
- return "<optional closed=%s>%s</optional>" % (self.closed, "".join([node.xmlPrefix for node in self]))
- elif self.type == self.TEXT:
- return escape(self.value)
- elif self.type == self.DOCUMENT:
- return "<document>%s</document>" % "".join([node.xmlPrefix for node in self])
- elif self.type == self.EMBRACED:
- return "<embraced>%s</embraced>" % "".join([node.xmlPrefix for node in self])
-
- def __str__(self):
- """
- Return the original LaTeX representation of this node
- """
- if self.type == self.COMMAND:
- return "\\%s%s" % (self.value, "".join([str(node) for node in self]))
- elif self.type == self.MANDATORY_ARGUMENT or self.type == self.EMBRACED:
- return "{%s}" % "".join([str(node) for node in self])
- elif self.type == self.OPTIONAL_ARGUMENT:
- return "[%s]" % "".join([str(node) for node in self])
- elif self.type == self.TEXT:
- return self.value
- elif self.type == self.DOCUMENT:
- return "".join([str(node) for node in self])
-
- @property
- def innerText(self):
- """
- Return the concatenated values of all TEXT child nodes
- """
- return "".join([child.value for child in self if child.type == Node.TEXT])
-
- @property
- def markup(self):
- """
- Return the concatenated markup values of this node and all child nodes
- """
- if self.type == self.COMMAND:
- return "<span color=\"grey\">\\%s</span>%s" % (escape(self.value), "".join([node.markup for node in self]))
- elif self.type == self.MANDATORY_ARGUMENT or self.type == self.EMBRACED:
- return "<span color=\"grey\">{</span>%s<span color=\"grey\">}</span>" % "".join([node.markup for node in self])
- elif self.type == self.OPTIONAL_ARGUMENT:
- return "<span color=\"grey\">[</span>%s<span color=\"grey\">]</span>" % "".join([node.markup for node in self])
- elif self.type == self.TEXT:
- return escape(self.value)
- elif self.type == self.DOCUMENT:
- return "".join([node.markup for node in self])
-
- @property
- def innerMarkup(self):
- """
- Return the concatenated markup values of only the child nodes
- """
- return "".join([node.markup for node in self])
-
- def append(self, node):
- """
- Append a child node and store a back-reference
- """
- node.parent = self
- list.append(self, node)
-
- def find(self, value):
- """
- Find child node with given value (recursive, so grand-children are found, too)
- """
- # TODO
-
- def destroy(self):
- # This is important, python cannot automatically free this
- # object because of cyclic references (it is doubly linked)
- self.parent = None
- for child in self:
- child.destroy()
- del self[:]
-
- #~ def __del__(self):
- #~ print "Properly destroyed %s" % self
-
-
+ """
+ This is the base class of the LaTeX object model
+ """
+
+ DOCUMENT, COMMAND, MANDATORY_ARGUMENT, OPTIONAL_ARGUMENT, TEXT, EMBRACED = range(6)
+
+ def __init__(self, type, value=None):
+ self.type = type
+ self.value = value
+ self.parent = None
+
+ # this indicates if an argument is closed or not
+ # (only used by the PrefixParser)
+ self.closed = False
+
+ def firstOfType(self, type):
+ """
+ Return the first child node of a given type
+ """
+ for node in self:
+ if node.type == type:
+ return node
+ raise IndexError
+
+ def filter(self, type):
+ """
+ Return all child nodes of this node having a certain type
+ """
+ return [node for node in self if node.type == type]
+
+ @property
+ def xml(self):
+ """
+ Return an XML representation of this node (for debugging)
+ """
+ if self.type == self.COMMAND:
+ content = "".join([node.xml for node in self])
+ if len(content):
+ return "<command name=\"%s\">%s</command>" % (escape(self.value), content)
+ else:
+ return "<command name=\"%s\" />" % escape(self.value)
+ elif self.type == self.MANDATORY_ARGUMENT:
+ return "<mandatory>%s</mandatory>" % "".join([node.xml for node in self])
+ elif self.type == self.OPTIONAL_ARGUMENT:
+ return "<optional>%s</optional>" % "".join([node.xml for node in self])
+ elif self.type == self.TEXT:
+ return escape(self.value)
+ elif self.type == self.DOCUMENT:
+ return "<document>%s</document>" % "".join([node.xml for node in self])
+ elif self.type == self.EMBRACED:
+ return "<embraced>%s</embraced>" % "".join([node.xml for node in self])
+
+ @property
+ def xmlPrefix(self):
+ """
+ Return an XML representation of this node (for debugging)
+
+ This is for the prefix mode, so we print if the arguments are closed or not
+ """
+ if self.type == self.COMMAND:
+ content = "".join([node.xmlPrefix for node in self])
+ if len(content):
+ return "<command name=\"%s\">%s</command>" % (self.value, content)
+ else:
+ return "<command name=\"%s\" />" % self.value
+ elif self.type == self.MANDATORY_ARGUMENT:
+ return "<mandatory closed=%s>%s</mandatory>" % (self.closed, "".join([node.xmlPrefix for node in self]))
+ elif self.type == self.OPTIONAL_ARGUMENT:
+ return "<optional closed=%s>%s</optional>" % (self.closed, "".join([node.xmlPrefix for node in self]))
+ elif self.type == self.TEXT:
+ return escape(self.value)
+ elif self.type == self.DOCUMENT:
+ return "<document>%s</document>" % "".join([node.xmlPrefix for node in self])
+ elif self.type == self.EMBRACED:
+ return "<embraced>%s</embraced>" % "".join([node.xmlPrefix for node in self])
+
+ def __str__(self):
+ """
+ Return the original LaTeX representation of this node
+ """
+ if self.type == self.COMMAND:
+ return "\\%s%s" % (self.value, "".join([str(node) for node in self]))
+ elif self.type == self.MANDATORY_ARGUMENT or self.type == self.EMBRACED:
+ return "{%s}" % "".join([str(node) for node in self])
+ elif self.type == self.OPTIONAL_ARGUMENT:
+ return "[%s]" % "".join([str(node) for node in self])
+ elif self.type == self.TEXT:
+ return self.value
+ elif self.type == self.DOCUMENT:
+ return "".join([str(node) for node in self])
+
+ @property
+ def innerText(self):
+ """
+ Return the concatenated values of all TEXT child nodes
+ """
+ return "".join([child.value for child in self if child.type == Node.TEXT])
+
+ @property
+ def markup(self):
+ """
+ Return the concatenated markup values of this node and all child nodes
+ """
+ if self.type == self.COMMAND:
+ return "<span color=\"grey\">\\%s</span>%s" % (escape(self.value), "".join([node.markup for node in self]))
+ elif self.type == self.MANDATORY_ARGUMENT or self.type == self.EMBRACED:
+ return "<span color=\"grey\">{</span>%s<span color=\"grey\">}</span>" % "".join([node.markup for node in self])
+ elif self.type == self.OPTIONAL_ARGUMENT:
+ return "<span color=\"grey\">[</span>%s<span color=\"grey\">]</span>" % "".join([node.markup for node in self])
+ elif self.type == self.TEXT:
+ return escape(self.value)
+ elif self.type == self.DOCUMENT:
+ return "".join([node.markup for node in self])
+
+ @property
+ def innerMarkup(self):
+ """
+ Return the concatenated markup values of only the child nodes
+ """
+ return "".join([node.markup for node in self])
+
+ def append(self, node):
+ """
+ Append a child node and store a back-reference
+ """
+ node.parent = self
+ list.append(self, node)
+
+ def find(self, value):
+ """
+ Find child node with given value (recursive, so grand-children are found, too)
+ """
+ # TODO
+
+ def destroy(self):
+ # This is important, python cannot automatically free this
+ # object because of cyclic references (it is doubly linked)
+ self.parent = None
+ for child in self:
+ child.destroy()
+ del self[:]
+
+ #~ def __del__(self):
+ #~ print "Properly destroyed %s" % self
+
+
class Document(Node):
- """
- An extended Node with special methods for a LaTeX document
- """
-
- # FIXME: doesn't extend LocalizedNode which leads to
- # https://sourceforge.net/tracker/index.php?func=detail&aid=2899795&group_id=204144&atid=988428
-
- # TODO: implement a generic interface for searching the document model
-
- def __init__(self, file):
- Node.__init__(self, Node.DOCUMENT, file)
-
- self._is_master_called = False
- self._is_master = False
-
- self._end_of_document = None
- self._end_of_packages = None
-
- def _do_is_master(self):
- # TODO: this should be recursive
-
- for node in self:
- if node.type == Node.COMMAND and node.value == "begin":
- if node.firstOfType(Node.MANDATORY_ARGUMENT).innerText == "document":
- return True
- return False
-
- @property
- def is_master(self):
- """
- @return: True if this document is a master document
- """
-
- # TODO: determine this while parsing
-
- if not self._is_master_called:
- self._is_master = self._do_is_master()
- self._is_master_called = True
- return self._is_master
-
- def _find_end_of_document(self):
- # TODO: this should be recursive
-
- for node in self:
- if node.type == Node.COMMAND and node.value == "end":
- if node.firstOfType(Node.MANDATORY_ARGUMENT).innerText == "document":
- return node.start
- return 0
-
- @property
- def end_of_document(self):
- """
- Return the offset right before \end{document}
-
- used by LaTeXEditor.insert_at_position
- """
- if self._end_of_document is None:
- self._end_of_document = self._find_end_of_document()
- return self._end_of_document
-
- def _find_end_of_packages(self):
- """
- @raise IndexError: if not found
- """
- # TODO: this should be recursive
-
- offset = None
- for node in self:
- if node.type == Node.COMMAND and node.value == "usepackage":
- offset = node.lastEnd
- if offset is None:
- raise IndexError
- return offset
-
- def _find_begin_of_preamble(self):
- # TODO: this should be recursive
-
- offset = 0
- for node in self:
- if node.type == Node.COMMAND and node.value == "documentclass":
- offset = node.lastEnd
- return offset
-
- @property
- def end_of_packages(self):
- """
- Return the offset right after the last \usepackage
-
- used by LaTeXEditor.insert_at_position
- """
- if self._end_of_packages is None:
- try:
- self._end_of_packages = self._find_end_of_packages()
- except IndexError:
- self._end_of_packages = self._find_begin_of_preamble()
-
- return self._end_of_packages
-
+ """
+ An extended Node with special methods for a LaTeX document
+ """
+
+ # FIXME: doesn't extend LocalizedNode which leads to
+ # https://sourceforge.net/tracker/index.php?func=detail&aid=2899795&group_id=204144&atid=988428
+
+ # TODO: implement a generic interface for searching the document model
+
+ def __init__(self, file):
+ Node.__init__(self, Node.DOCUMENT, file)
+
+ self._is_master_called = False
+ self._is_master = False
+
+ self._end_of_document = None
+ self._end_of_packages = None
+
+ def _do_is_master(self):
+ # TODO: this should be recursive
+
+ for node in self:
+ if node.type == Node.COMMAND and node.value == "begin":
+ if node.firstOfType(Node.MANDATORY_ARGUMENT).innerText == "document":
+ return True
+ return False
+
+ @property
+ def is_master(self):
+ """
+ @return: True if this document is a master document
+ """
+
+ # TODO: determine this while parsing
+
+ if not self._is_master_called:
+ self._is_master = self._do_is_master()
+ self._is_master_called = True
+ return self._is_master
+
+ def _find_end_of_document(self):
+ # TODO: this should be recursive
+
+ for node in self:
+ if node.type == Node.COMMAND and node.value == "end":
+ if node.firstOfType(Node.MANDATORY_ARGUMENT).innerText == "document":
+ return node.start
+ return 0
+
+ @property
+ def end_of_document(self):
+ """
+ Return the offset right before \end{document}
+
+ used by LaTeXEditor.insert_at_position
+ """
+ if self._end_of_document is None:
+ self._end_of_document = self._find_end_of_document()
+ return self._end_of_document
+
+ def _find_end_of_packages(self):
+ """
+ @raise IndexError: if not found
+ """
+ # TODO: this should be recursive
+
+ offset = None
+ for node in self:
+ if node.type == Node.COMMAND and node.value == "usepackage":
+ offset = node.lastEnd
+ if offset is None:
+ raise IndexError
+ return offset
+
+ def _find_begin_of_preamble(self):
+ # TODO: this should be recursive
+
+ offset = 0
+ for node in self:
+ if node.type == Node.COMMAND and node.value == "documentclass":
+ offset = node.lastEnd
+ return offset
+
+ @property
+ def end_of_packages(self):
+ """
+ Return the offset right after the last \usepackage
+
+ used by LaTeXEditor.insert_at_position
+ """
+ if self._end_of_packages is None:
+ try:
+ self._end_of_packages = self._find_end_of_packages()
+ except IndexError:
+ self._end_of_packages = self._find_begin_of_preamble()
+
+ return self._end_of_packages
+
class LocalizedNode(Node):
- """
- This Node type holds the start and end offsets of the substring it belongs to
- in the source
- """
- def __init__(self, type, start, end, value=None, file=None):
- Node.__init__(self, type, value)
- self.start = start
- self.end = end
- self.file = file
-
- @property
- def lastEnd(self):
- """
- Return the end of the last child node or of this node if
- it doesn't have children.
- """
- try:
- return self[-1].end
- except IndexError:
- return self.end
- except AttributeError:
- # AttributeError: 'Document' object has no attribute 'end'
- # FIXME: why can this happen?
- return self.end
-
+ """
+ This Node type holds the start and end offsets of the substring it belongs to
+ in the source
+ """
+ def __init__(self, type, start, end, value=None, file=None):
+ Node.__init__(self, type, value)
+ self.start = start
+ self.end = end
+ self.file = file
+
+ @property
+ def lastEnd(self):
+ """
+ Return the end of the last child node or of this node if
+ it doesn't have children.
+ """
+ try:
+ return self[-1].end
+ except IndexError:
+ return self.end
+ except AttributeError:
+ # AttributeError: 'Document' object has no attribute 'end'
+ # FIXME: why can this happen?
+ return self.end
+
class FatalParseException(Exception):
- """
- This raised of the Parser faces a fatal error and cannot continue
- """
+ """
+ This raised of the Parser faces a fatal error and cannot continue
+ """
from lexer import Lexer, Token
class LaTeXParser(object):
- """
- A tree parser building an object model of nodes
- """
-
- # TODO: remove second parse method
-
- @verbose
- def _parse(self, string, documentNode, file, issue_handler):
- """
- @deprecated: use parse_string() and issues() instead
- """
-
- self._stack = [documentNode]
-
- callables = {
- Token.COMMAND : self.command,
- Token.TEXT : self.text,
- Token.BEGIN_CURLY : self.beginCurly,
- Token.END_CURLY : self.endCurly,
- Token.BEGIN_SQUARE : self.beginSquare,
- Token.END_SQUARE : self.endSquare,
- Token.COMMENT : self.comment,
- Token.VERBATIM : self.verbatim }
-
- try:
- for token in Lexer(string):
- callables[token.type].__call__(token.value, token.offset, file, issue_handler)
- except FatalParseException:
- return
-
- # check stack remainder
- for node in self._stack:
- if node.type == Node.MANDATORY_ARGUMENT or node.type == Node.EMBRACED:
- issue_handler.issue(Issue("Unclosed {", node.start, node.start + 1, file, Issue.SEVERITY_ERROR))
- elif node.type == Node.OPTIONAL_ARGUMENT:
- issue_handler.issue(Issue("Unclosed [", node.start, node.start + 1, file, Issue.SEVERITY_ERROR))
-
- def parse(self, string, file, issue_handler):
- """
- @param string: LaTeX source
- @param from_filename: filename from where the source is read (this is used to tag
- parts of the model)
-
- @rtype: Document
- """
- document_node = Document(file)
- self._parse(string, document_node, file, issue_handler)
-
- return document_node
-
- # TODO: rename methods from "command()" to "_on_command()"
-
- def command(self, value, offset, file, issue_handler):
- top = self._stack[-1]
-
- if top.type == Node.DOCUMENT \
- or top.type == Node.MANDATORY_ARGUMENT \
- or top.type == Node.OPTIONAL_ARGUMENT \
- or top.type == Node.EMBRACED:
- node = LocalizedNode(Node.COMMAND, offset, offset + len(value) + 1, value, file)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.COMMAND \
- or top.type == Node.TEXT:
- try:
- self._stack.pop()
- self.command(value, offset, file, issue_handler)
- except IndexError:
- issue_handler.issue(Issue("Undefined Parse Error", offset, offset + 1, file, Issue.SEVERITY_ERROR))
-
- def text(self, value, offset, file, issue_handler):
- top = self._stack[-1]
-
- if top.type == Node.DOCUMENT \
- or top.type == Node.MANDATORY_ARGUMENT \
- or top.type == Node.OPTIONAL_ARGUMENT \
- or top.type == Node.EMBRACED:
- node = LocalizedNode(Node.TEXT, offset, offset + len(value), value, file)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.COMMAND:
- self._stack.pop()
- self.text(value, offset, file, issue_handler)
-
- elif top.type == Node.TEXT:
- try:
- self._stack.pop()
- self.text(value, offset, file, issue_handler)
- except IndexError:
- issue_handler.issue(Issue("Undefined Parse Error", offset, offset + 1, file, Issue.SEVERITY_ERROR))
- else:
- # TODO: possible?
- issue_handler.issue(Issue("Unexpected TEXT token with %s on stack" % top.type, offset, offset + 1, file, Issue.SEVERITY_ERROR))
-
- def beginCurly(self, value, offset, file, issue_handler):
- top = self._stack[-1]
-
- if top.type == Node.COMMAND:
- node = LocalizedNode(Node.MANDATORY_ARGUMENT, offset, offset + 1, file=file)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.DOCUMENT \
- or top.type == Node.MANDATORY_ARGUMENT \
- or top.type == Node.OPTIONAL_ARGUMENT \
- or top.type == Node.EMBRACED:
- node = LocalizedNode(Node.EMBRACED, offset, offset + 1, file=file)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.TEXT:
- try:
- self._stack.pop()
- self.beginCurly(value, offset, file, issue_handler)
- except IndexError:
- issue_handler.issue(Issue("Undefined Parse Error", offset, offset + 1, file, Issue.SEVERITY_ERROR))
- else:
- # TODO: possible?
- issue_handler.issue(Issue("Unexpected BEGIN_CURLY token with %s on stack" % top.type, offset, offset + 1, file, Issue.SEVERITY_ERROR))
-
- def endCurly(self, value, offset, file, issue_handler):
- try:
- # pop from stack until MANDATORY_ARGUMENT or EMBRACED
- while True:
- top = self._stack[-1]
- if top.type == Node.MANDATORY_ARGUMENT or top.type == Node.EMBRACED:
- node = self._stack.pop()
- break
- self._stack.pop()
-
- # set end offset of MANDATORY_ARGUMENT or EMBRACED
- node.end = offset + 1
- except IndexError:
- issue_handler.issue(Issue("Encountered <b>}</b> without <b>{</b>", offset, offset + 1, file, Issue.SEVERITY_ERROR))
- # we cannot continue after that
- raise FatalParseException
-
- def beginSquare(self, value, offset, file, issue_handler):
- top = self._stack[-1]
- if top.type == Node.COMMAND:
- node = LocalizedNode(Node.OPTIONAL_ARGUMENT, offset, offset + 1, file=file)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.TEXT:
- top.value += "["
-
- elif top.type == Node.MANDATORY_ARGUMENT:
- node = LocalizedNode(Node.TEXT, offset, offset + 1, "[", file)
- top.append(node)
- self._stack.append(node)
-
- else:
- issue_handler.issue(Issue("Unexpected BEGIN_SQUARE token with %s on stack" % top.type, offset, offset + 1, file, Issue.SEVERITY_ERROR))
-
- def endSquare(self, value, offset, file, issue_handler):
- try:
- node = [node for node in self._stack if node.type == Node.OPTIONAL_ARGUMENT][-1]
-
- # open optional argument found
- # this square closes it, so pop stack until there
-
- while self._stack[-1].type != Node.OPTIONAL_ARGUMENT:
- self._stack.pop()
- node = self._stack.pop()
- node.end = offset + 1
-
- except IndexError:
- # no open optional argument, so this "]" is TEXT
-
- top = self._stack[-1]
- if top.type == Node.TEXT:
- top.value += "]"
-
- elif top.type == Node.COMMAND:
- try:
- self._stack.pop()
- self.endSquare(value, offset, file, issue_handler)
- except IndexError:
- issue_handler.issue(Issue("Undefined Parse Error", offset, offset + 1, file, Issue.SEVERITY_ERROR))
-
- elif top.type == Node.MANDATORY_ARGUMENT or top.type == Node.DOCUMENT or top.type == Node.OPTIONAL_ARGUMENT:
- node = LocalizedNode(Node.TEXT, offset, offset + 1, "]", file)
- top.append(node)
- self._stack.append(node)
-
- else:
- issue_handler.issue(Issue("Unexpected END_SQUARE token with %s on stack and no optional argument" % top.type, offset, offset + 1, file, Issue.SEVERITY_ERROR))
-
- _PATTERN = compile("(TODO|FIXME)\w?\:?(?P<text>.*)")
-
- def comment(self, value, offset, file, issue_handler):
- """
- Extract TODOs and FIXMEs
- """
- match = self._PATTERN.search(value)
- if match:
- text = match.group("text").strip()
- # TODO: escape
- issue_handler.issue(Issue(text, offset + match.start(), offset + match.end() + 1, file, Issue.SEVERITY_TASK))
-
- def verbatim(self, value, offset, file, issue_handler):
- pass
-
- #~ def __del__(self):
- #~ print "Properly destroyed %s" % self
+ """
+ A tree parser building an object model of nodes
+ """
+
+ # TODO: remove second parse method
+
+ @verbose
+ def _parse(self, string, documentNode, file, issue_handler):
+ """
+ @deprecated: use parse_string() and issues() instead
+ """
+
+ self._stack = [documentNode]
+
+ callables = {
+ Token.COMMAND : self.command,
+ Token.TEXT : self.text,
+ Token.BEGIN_CURLY : self.beginCurly,
+ Token.END_CURLY : self.endCurly,
+ Token.BEGIN_SQUARE : self.beginSquare,
+ Token.END_SQUARE : self.endSquare,
+ Token.COMMENT : self.comment,
+ Token.VERBATIM : self.verbatim }
+
+ try:
+ for token in Lexer(string):
+ callables[token.type].__call__(token.value, token.offset, file, issue_handler)
+ except FatalParseException:
+ return
+
+ # check stack remainder
+ for node in self._stack:
+ if node.type == Node.MANDATORY_ARGUMENT or node.type == Node.EMBRACED:
+ issue_handler.issue(Issue("Unclosed {", node.start, node.start + 1, file, Issue.SEVERITY_ERROR))
+ elif node.type == Node.OPTIONAL_ARGUMENT:
+ issue_handler.issue(Issue("Unclosed [", node.start, node.start + 1, file, Issue.SEVERITY_ERROR))
+
+ def parse(self, string, file, issue_handler):
+ """
+ @param string: LaTeX source
+ @param from_filename: filename from where the source is read (this is used to tag
+ parts of the model)
+
+ @rtype: Document
+ """
+ document_node = Document(file)
+ self._parse(string, document_node, file, issue_handler)
+
+ return document_node
+
+ # TODO: rename methods from "command()" to "_on_command()"
+
+ def command(self, value, offset, file, issue_handler):
+ top = self._stack[-1]
+
+ if top.type == Node.DOCUMENT \
+ or top.type == Node.MANDATORY_ARGUMENT \
+ or top.type == Node.OPTIONAL_ARGUMENT \
+ or top.type == Node.EMBRACED:
+ node = LocalizedNode(Node.COMMAND, offset, offset + len(value) + 1, value, file)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.COMMAND \
+ or top.type == Node.TEXT:
+ try:
+ self._stack.pop()
+ self.command(value, offset, file, issue_handler)
+ except IndexError:
+ issue_handler.issue(Issue("Undefined Parse Error", offset, offset + 1, file, Issue.SEVERITY_ERROR))
+
+ def text(self, value, offset, file, issue_handler):
+ top = self._stack[-1]
+
+ if top.type == Node.DOCUMENT \
+ or top.type == Node.MANDATORY_ARGUMENT \
+ or top.type == Node.OPTIONAL_ARGUMENT \
+ or top.type == Node.EMBRACED:
+ node = LocalizedNode(Node.TEXT, offset, offset + len(value), value, file)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.COMMAND:
+ self._stack.pop()
+ self.text(value, offset, file, issue_handler)
+
+ elif top.type == Node.TEXT:
+ try:
+ self._stack.pop()
+ self.text(value, offset, file, issue_handler)
+ except IndexError:
+ issue_handler.issue(Issue("Undefined Parse Error", offset, offset + 1, file, Issue.SEVERITY_ERROR))
+ else:
+ # TODO: possible?
+ issue_handler.issue(Issue("Unexpected TEXT token with %s on stack" % top.type, offset, offset + 1, file, Issue.SEVERITY_ERROR))
+
+ def beginCurly(self, value, offset, file, issue_handler):
+ top = self._stack[-1]
+
+ if top.type == Node.COMMAND:
+ node = LocalizedNode(Node.MANDATORY_ARGUMENT, offset, offset + 1, file=file)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.DOCUMENT \
+ or top.type == Node.MANDATORY_ARGUMENT \
+ or top.type == Node.OPTIONAL_ARGUMENT \
+ or top.type == Node.EMBRACED:
+ node = LocalizedNode(Node.EMBRACED, offset, offset + 1, file=file)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.TEXT:
+ try:
+ self._stack.pop()
+ self.beginCurly(value, offset, file, issue_handler)
+ except IndexError:
+ issue_handler.issue(Issue("Undefined Parse Error", offset, offset + 1, file, Issue.SEVERITY_ERROR))
+ else:
+ # TODO: possible?
+ issue_handler.issue(Issue("Unexpected BEGIN_CURLY token with %s on stack" % top.type, offset, offset + 1, file, Issue.SEVERITY_ERROR))
+
+ def endCurly(self, value, offset, file, issue_handler):
+ try:
+ # pop from stack until MANDATORY_ARGUMENT or EMBRACED
+ while True:
+ top = self._stack[-1]
+ if top.type == Node.MANDATORY_ARGUMENT or top.type == Node.EMBRACED:
+ node = self._stack.pop()
+ break
+ self._stack.pop()
+
+ # set end offset of MANDATORY_ARGUMENT or EMBRACED
+ node.end = offset + 1
+ except IndexError:
+ issue_handler.issue(Issue("Encountered <b>}</b> without <b>{</b>", offset, offset + 1, file, Issue.SEVERITY_ERROR))
+ # we cannot continue after that
+ raise FatalParseException
+
+ def beginSquare(self, value, offset, file, issue_handler):
+ top = self._stack[-1]
+ if top.type == Node.COMMAND:
+ node = LocalizedNode(Node.OPTIONAL_ARGUMENT, offset, offset + 1, file=file)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.TEXT:
+ top.value += "["
+
+ elif top.type == Node.MANDATORY_ARGUMENT:
+ node = LocalizedNode(Node.TEXT, offset, offset + 1, "[", file)
+ top.append(node)
+ self._stack.append(node)
+
+ else:
+ issue_handler.issue(Issue("Unexpected BEGIN_SQUARE token with %s on stack" % top.type, offset, offset + 1, file, Issue.SEVERITY_ERROR))
+
+ def endSquare(self, value, offset, file, issue_handler):
+ try:
+ node = [node for node in self._stack if node.type == Node.OPTIONAL_ARGUMENT][-1]
+
+ # open optional argument found
+ # this square closes it, so pop stack until there
+
+ while self._stack[-1].type != Node.OPTIONAL_ARGUMENT:
+ self._stack.pop()
+ node = self._stack.pop()
+ node.end = offset + 1
+
+ except IndexError:
+ # no open optional argument, so this "]" is TEXT
+
+ top = self._stack[-1]
+ if top.type == Node.TEXT:
+ top.value += "]"
+
+ elif top.type == Node.COMMAND:
+ try:
+ self._stack.pop()
+ self.endSquare(value, offset, file, issue_handler)
+ except IndexError:
+ issue_handler.issue(Issue("Undefined Parse Error", offset, offset + 1, file, Issue.SEVERITY_ERROR))
+
+ elif top.type == Node.MANDATORY_ARGUMENT or top.type == Node.DOCUMENT or top.type == Node.OPTIONAL_ARGUMENT:
+ node = LocalizedNode(Node.TEXT, offset, offset + 1, "]", file)
+ top.append(node)
+ self._stack.append(node)
+
+ else:
+ issue_handler.issue(Issue("Unexpected END_SQUARE token with %s on stack and no optional argument" % top.type, offset, offset + 1, file, Issue.SEVERITY_ERROR))
+
+ _PATTERN = compile("(TODO|FIXME)\w?\:?(?P<text>.*)")
+
+ def comment(self, value, offset, file, issue_handler):
+ """
+ Extract TODOs and FIXMEs
+ """
+ match = self._PATTERN.search(value)
+ if match:
+ text = match.group("text").strip()
+ # TODO: escape
+ issue_handler.issue(Issue(text, offset + match.start(), offset + match.end() + 1, file, Issue.SEVERITY_TASK))
+
+ def verbatim(self, value, offset, file, issue_handler):
+ pass
+
+ #~ def __del__(self):
+ #~ print "Properly destroyed %s" % self
class PrefixParser(object):
- """
- A light-weight LaTeX parser used for parsing just a prefix in
- the code completion.
-
- The differences between the full parser and this one include:
- * we don't collect issues (we just raise an exception)
- * we don't store node offsets
- * we indicate whether the last argument is closed or not
- """
-
- # TODO: use another Lexer here that doesn't count offsets (faster)
-
- def parse(self, string, documentNode):
-
- # TODO: change semantic
-
- self._stack = [documentNode]
-
- callables = {Token.COMMAND : self.command,
- Token.TEXT : self.text,
- Token.BEGIN_CURLY : self.beginCurly,
- Token.END_CURLY : self.endCurly,
- Token.BEGIN_SQUARE : self.beginSquare,
- Token.END_SQUARE : self.endSquare,
- Token.COMMENT : self.comment,
- Token.VERBATIM : self.verbatim}
-
- try:
- for token in Lexer(string, skipWs=False, skipComment=False):
- callables[token.type].__call__(token.value)
- except FatalParseException:
- return
-
- def command(self, value):
- top = self._stack[-1]
-
- if top.type == Node.DOCUMENT \
- or top.type == Node.MANDATORY_ARGUMENT \
- or top.type == Node.OPTIONAL_ARGUMENT \
- or top.type == Node.EMBRACED:
- node = Node(Node.COMMAND, value)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.COMMAND \
- or top.type == Node.TEXT:
- try:
- self._stack.pop()
- self.command(value)
- except IndexError:
- raise FatalParseException
-
- def text(self, value):
- top = self._stack[-1]
-
- if top.type == Node.DOCUMENT \
- or top.type == Node.MANDATORY_ARGUMENT \
- or top.type == Node.OPTIONAL_ARGUMENT \
- or top.type == Node.EMBRACED:
- node = Node(Node.TEXT, value)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.COMMAND:
- self._stack.pop()
- self.text(value)
-
- elif top.type == Node.TEXT:
- try:
- self._stack.pop()
- self.text(value)
- except IndexError:
- raise FatalParseException
- else:
- # TODO: possible?
- raise FatalParseException
-
- def beginCurly(self, value):
- top = self._stack[-1]
-
- if top.type == Node.COMMAND:
- node = Node(Node.MANDATORY_ARGUMENT)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.DOCUMENT \
- or top.type == Node.MANDATORY_ARGUMENT \
- or top.type == Node.OPTIONAL_ARGUMENT \
- or top.type == Node.EMBRACED:
- node = Node(Node.EMBRACED)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.TEXT:
- try:
- self._stack.pop()
- self.beginCurly(value)
- except IndexError:
- raise FatalParseException
- else:
- # TODO: possible?
- raise FatalParseException
-
- def endCurly(self, value):
- try:
- # pop from stack until MANDATORY_ARGUMENT or EMBRACED
- while True:
- top = self._stack[-1]
- if top.type == Node.MANDATORY_ARGUMENT or top.type == Node.EMBRACED:
- node = self._stack.pop()
- break
- self._stack.pop()
-
- node.closed = True
- except IndexError:
- raise FatalParseException
-
- def beginSquare(self, value):
- top = self._stack[-1]
- if top.type == Node.COMMAND:
- node = Node(Node.OPTIONAL_ARGUMENT)
- top.append(node)
- self._stack.append(node)
-
- elif top.type == Node.TEXT:
- top.value += "["
-
- elif top.type == Node.MANDATORY_ARGUMENT:
- node = Node(Node.TEXT, "[")
- top.append(node)
- self._stack.append(node)
-
- else:
- raise FatalParseException
-
- def endSquare(self, value):
- try:
- # check whether an optional argument is open at all
- #
- # for this we address the top OPTIONAL_ARGUMENT node on the stack, if it doesn't
- # exist an IndexError is thrown
- node = [node for node in self._stack if node.type == Node.OPTIONAL_ARGUMENT][-1]
-
- # open optional argument found
- # this square closes it, so pop stack until there
-
- while self._stack[-1].type != Node.OPTIONAL_ARGUMENT:
- self._stack.pop()
- node = self._stack.pop()
- node.closed = True
-
- except IndexError:
- # no open optional argument, so this "]" is TEXT
-
- top = self._stack[-1]
- if top.type == Node.TEXT:
- top.value += "]"
-
- elif top.type == Node.COMMAND:
- try:
- self._stack.pop()
- self.endSquare(value)
- except IndexError:
- raise FatalParseException
-
- elif top.type == Node.MANDATORY_ARGUMENT or top.type == Node.DOCUMENT or top.type == Node.OPTIONAL_ARGUMENT:
- node = Node(Node.TEXT, "]")
- top.append(node)
- self._stack.append(node)
-
- else:
- raise FatalParseException
-
- def comment(self, value):
- pass
-
- def verbatim(self, value):
- pass
-
-
\ No newline at end of file
+ """
+ A light-weight LaTeX parser used for parsing just a prefix in
+ the code completion.
+
+ The differences between the full parser and this one include:
+ * we don't collect issues (we just raise an exception)
+ * we don't store node offsets
+ * we indicate whether the last argument is closed or not
+ """
+
+ # TODO: use another Lexer here that doesn't count offsets (faster)
+
+ def parse(self, string, documentNode):
+
+ # TODO: change semantic
+
+ self._stack = [documentNode]
+
+ callables = {Token.COMMAND : self.command,
+ Token.TEXT : self.text,
+ Token.BEGIN_CURLY : self.beginCurly,
+ Token.END_CURLY : self.endCurly,
+ Token.BEGIN_SQUARE : self.beginSquare,
+ Token.END_SQUARE : self.endSquare,
+ Token.COMMENT : self.comment,
+ Token.VERBATIM : self.verbatim}
+
+ try:
+ for token in Lexer(string, skipWs=False, skipComment=False):
+ callables[token.type].__call__(token.value)
+ except FatalParseException:
+ return
+
+ def command(self, value):
+ top = self._stack[-1]
+
+ if top.type == Node.DOCUMENT \
+ or top.type == Node.MANDATORY_ARGUMENT \
+ or top.type == Node.OPTIONAL_ARGUMENT \
+ or top.type == Node.EMBRACED:
+ node = Node(Node.COMMAND, value)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.COMMAND \
+ or top.type == Node.TEXT:
+ try:
+ self._stack.pop()
+ self.command(value)
+ except IndexError:
+ raise FatalParseException
+
+ def text(self, value):
+ top = self._stack[-1]
+
+ if top.type == Node.DOCUMENT \
+ or top.type == Node.MANDATORY_ARGUMENT \
+ or top.type == Node.OPTIONAL_ARGUMENT \
+ or top.type == Node.EMBRACED:
+ node = Node(Node.TEXT, value)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.COMMAND:
+ self._stack.pop()
+ self.text(value)
+
+ elif top.type == Node.TEXT:
+ try:
+ self._stack.pop()
+ self.text(value)
+ except IndexError:
+ raise FatalParseException
+ else:
+ # TODO: possible?
+ raise FatalParseException
+
+ def beginCurly(self, value):
+ top = self._stack[-1]
+
+ if top.type == Node.COMMAND:
+ node = Node(Node.MANDATORY_ARGUMENT)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.DOCUMENT \
+ or top.type == Node.MANDATORY_ARGUMENT \
+ or top.type == Node.OPTIONAL_ARGUMENT \
+ or top.type == Node.EMBRACED:
+ node = Node(Node.EMBRACED)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.TEXT:
+ try:
+ self._stack.pop()
+ self.beginCurly(value)
+ except IndexError:
+ raise FatalParseException
+ else:
+ # TODO: possible?
+ raise FatalParseException
+
+ def endCurly(self, value):
+ try:
+ # pop from stack until MANDATORY_ARGUMENT or EMBRACED
+ while True:
+ top = self._stack[-1]
+ if top.type == Node.MANDATORY_ARGUMENT or top.type == Node.EMBRACED:
+ node = self._stack.pop()
+ break
+ self._stack.pop()
+
+ node.closed = True
+ except IndexError:
+ raise FatalParseException
+
+ def beginSquare(self, value):
+ top = self._stack[-1]
+ if top.type == Node.COMMAND:
+ node = Node(Node.OPTIONAL_ARGUMENT)
+ top.append(node)
+ self._stack.append(node)
+
+ elif top.type == Node.TEXT:
+ top.value += "["
+
+ elif top.type == Node.MANDATORY_ARGUMENT:
+ node = Node(Node.TEXT, "[")
+ top.append(node)
+ self._stack.append(node)
+
+ else:
+ raise FatalParseException
+
+ def endSquare(self, value):
+ try:
+ # check whether an optional argument is open at all
+ #
+ # for this we address the top OPTIONAL_ARGUMENT node on the stack, if it doesn't
+ # exist an IndexError is thrown
+ node = [node for node in self._stack if node.type == Node.OPTIONAL_ARGUMENT][-1]
+
+ # open optional argument found
+ # this square closes it, so pop stack until there
+
+ while self._stack[-1].type != Node.OPTIONAL_ARGUMENT:
+ self._stack.pop()
+ node = self._stack.pop()
+ node.closed = True
+
+ except IndexError:
+ # no open optional argument, so this "]" is TEXT
+
+ top = self._stack[-1]
+ if top.type == Node.TEXT:
+ top.value += "]"
+
+ elif top.type == Node.COMMAND:
+ try:
+ self._stack.pop()
+ self.endSquare(value)
+ except IndexError:
+ raise FatalParseException
+
+ elif top.type == Node.MANDATORY_ARGUMENT or top.type == Node.DOCUMENT or top.type == Node.OPTIONAL_ARGUMENT:
+ node = Node(Node.TEXT, "]")
+ top.append(node)
+ self._stack.append(node)
+
+ else:
+ raise FatalParseException
+
+ def comment(self, value):
+ pass
+
+ def verbatim(self, value):
+ pass
+
diff --git a/latex/latex/preview.py b/latex/latex/preview.py
index 2f89813..30624cf 100644
--- a/latex/latex/preview.py
+++ b/latex/latex/preview.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -31,135 +31,135 @@ from environment import Environment
from gi.repository import Gdk, GdkPixbuf
class ImageToolGenerator(object):
- """
- This generates Tools for rendering images from LaTeX source
- """
-
- FORMAT_PNG, FORMAT_JPEG, FORMAT_GIF = 1, 2, 3
-
- PNG_MODE_MONOCHROME, PNG_MODE_GRAYSCALE, PNG_MODE_RGB, PNG_MODE_RGBA = 1, 2, 3, 4
-
- def __init__(self):
- self._names = {self.FORMAT_PNG : "PNG Image", self.FORMAT_JPEG : "JPEG Image", self.FORMAT_GIF : "GIF Image"}
- self._png_modes = {self.PNG_MODE_MONOCHROME : "mono", self.PNG_MODE_GRAYSCALE : "gray", self.PNG_MODE_RGB : "16m",
- self.PNG_MODE_RGBA : "alpha"}
-
- # default settings
- self.format = self.FORMAT_PNG
- self.png_mode = self.PNG_MODE_RGBA
- self.render_box = True
- self.resolution = int(round(Environment().screen_dpi))
- self.antialias_factor = 4
- self.open = False
-
- def generate(self):
- """
- @return: a Tool object
- """
- tool = Tool(label=self._names[self.format], jobs=[], description="", accelerator="", extensions=[])
-
- # use rubber to render a DVI
- tool.jobs.append(Job("rubber --force --short --inplace \"$filename\"", True, RubberPostProcessor))
-
- if self.render_box:
- # DVI -> PS
-
- # -D num resolution in DPI
- # -q quiet mode
- # -E generate an EPSF file with a tight bounding box
- tool.jobs.append(Job("dvips -D %s -q -E -o \"$shortname.eps\" \"$shortname.dvi\"" % self.resolution, True, GenericPostProcessor))
-
- # EPS -> PNG|JPG|GIF
- if self.format == self.FORMAT_PNG:
- command = "$plugin_path/util/eps2png.pl -png%s -resolution=%s -antialias=%s \"$shortname.eps\"" % (self._png_modes[self.png_mode],
- self.resolution, self.antialias_factor)
- elif self.format == self.FORMAT_JPEG:
- command = "$plugin_path/util/eps2png.pl -jpeg -resolution=%s -antialias=%s \"$shortname.eps\"" % (self.resolution, self.antialias_factor)
- elif self.format == self.FORMAT_GIF:
- command = "$plugin_path/util/eps2png.pl -gif -resolution=%s -antialias=%s \"$shortname.eps\"" % (self.resolution, self.antialias_factor)
-
- tool.jobs.append(Job(command, True, GenericPostProcessor))
- else:
- # dvips
- tool.jobs.append(Job("dvips -D %s -q -o \"$shortname.ps\" \"$shortname.dvi\"" % self.resolution, True, GenericPostProcessor))
-
- if self.format == self.FORMAT_PNG:
- tool.jobs.append(Job("gs -q -dNOPAUSE -r%s -dTextAlphaBits=%s -dGraphicsAlphaBits=%s -sDEVICE=png%s -sOutputFile=$shortname.png $shortname.ps quit.ps"
- % (self.resolution, self.antialias_factor, self.antialias_factor, self._png_modes[self.png_mode]), True, GenericPostProcessor))
- elif self.format == self.FORMAT_JPEG:
- tool.jobs.append(Job("gs -q -dNOPAUSE -r%s -dTextAlphaBits=%s -dGraphicsAlphaBits=%s -sDEVICE=jpeg -sOutputFile=$shortname.jpg $shortname.ps quit.ps"
- % (self.resolution, self.antialias_factor, self.antialias_factor), True, GenericPostProcessor))
- elif self.format == self.FORMAT_GIF:
- tool.jobs.append(Job("gs -q -dNOPAUSE -r%s -dTextAlphaBits=%s -dGraphicsAlphaBits=%s -sDEVICE=ppm -sOutputFile=$shortname.ppm $shortname.ps quit.ps"
- % (self.resolution, self.antialias_factor, self.antialias_factor), True, GenericPostProcessor))
- # ppmtogif
- tool.jobs.append(Job("ppmtogif $shortname.ppm > $shortname.gif", True, GenericPostProcessor))
-
- if self.open:
- extension = {self.FORMAT_PNG : "png", self.FORMAT_JPEG: "jpg", self.FORMAT_GIF : "gif"}[self.format]
- tool.jobs.append(Job("gnome-open \"$shortname.%s\"" % extension, True, GenericPostProcessor))
-
- return tool
+ """
+ This generates Tools for rendering images from LaTeX source
+ """
+
+ FORMAT_PNG, FORMAT_JPEG, FORMAT_GIF = 1, 2, 3
+
+ PNG_MODE_MONOCHROME, PNG_MODE_GRAYSCALE, PNG_MODE_RGB, PNG_MODE_RGBA = 1, 2, 3, 4
+
+ def __init__(self):
+ self._names = {self.FORMAT_PNG : "PNG Image", self.FORMAT_JPEG : "JPEG Image", self.FORMAT_GIF : "GIF Image"}
+ self._png_modes = {self.PNG_MODE_MONOCHROME : "mono", self.PNG_MODE_GRAYSCALE : "gray", self.PNG_MODE_RGB : "16m",
+ self.PNG_MODE_RGBA : "alpha"}
+
+ # default settings
+ self.format = self.FORMAT_PNG
+ self.png_mode = self.PNG_MODE_RGBA
+ self.render_box = True
+ self.resolution = int(round(Environment().screen_dpi))
+ self.antialias_factor = 4
+ self.open = False
+
+ def generate(self):
+ """
+ @return: a Tool object
+ """
+ tool = Tool(label=self._names[self.format], jobs=[], description="", accelerator="", extensions=[])
+
+ # use rubber to render a DVI
+ tool.jobs.append(Job("rubber --force --short --inplace \"$filename\"", True, RubberPostProcessor))
+
+ if self.render_box:
+ # DVI -> PS
+
+ # -D num resolution in DPI
+ # -q quiet mode
+ # -E generate an EPSF file with a tight bounding box
+ tool.jobs.append(Job("dvips -D %s -q -E -o \"$shortname.eps\" \"$shortname.dvi\"" % self.resolution, True, GenericPostProcessor))
+
+ # EPS -> PNG|JPG|GIF
+ if self.format == self.FORMAT_PNG:
+ command = "$plugin_path/util/eps2png.pl -png%s -resolution=%s -antialias=%s \"$shortname.eps\"" % (self._png_modes[self.png_mode],
+ self.resolution, self.antialias_factor)
+ elif self.format == self.FORMAT_JPEG:
+ command = "$plugin_path/util/eps2png.pl -jpeg -resolution=%s -antialias=%s \"$shortname.eps\"" % (self.resolution, self.antialias_factor)
+ elif self.format == self.FORMAT_GIF:
+ command = "$plugin_path/util/eps2png.pl -gif -resolution=%s -antialias=%s \"$shortname.eps\"" % (self.resolution, self.antialias_factor)
+
+ tool.jobs.append(Job(command, True, GenericPostProcessor))
+ else:
+ # dvips
+ tool.jobs.append(Job("dvips -D %s -q -o \"$shortname.ps\" \"$shortname.dvi\"" % self.resolution, True, GenericPostProcessor))
+
+ if self.format == self.FORMAT_PNG:
+ tool.jobs.append(Job("gs -q -dNOPAUSE -r%s -dTextAlphaBits=%s -dGraphicsAlphaBits=%s -sDEVICE=png%s -sOutputFile=$shortname.png $shortname.ps quit.ps"
+ % (self.resolution, self.antialias_factor, self.antialias_factor, self._png_modes[self.png_mode]), True, GenericPostProcessor))
+ elif self.format == self.FORMAT_JPEG:
+ tool.jobs.append(Job("gs -q -dNOPAUSE -r%s -dTextAlphaBits=%s -dGraphicsAlphaBits=%s -sDEVICE=jpeg -sOutputFile=$shortname.jpg $shortname.ps quit.ps"
+ % (self.resolution, self.antialias_factor, self.antialias_factor), True, GenericPostProcessor))
+ elif self.format == self.FORMAT_GIF:
+ tool.jobs.append(Job("gs -q -dNOPAUSE -r%s -dTextAlphaBits=%s -dGraphicsAlphaBits=%s -sDEVICE=ppm -sOutputFile=$shortname.ppm $shortname.ps quit.ps"
+ % (self.resolution, self.antialias_factor, self.antialias_factor), True, GenericPostProcessor))
+ # ppmtogif
+ tool.jobs.append(Job("ppmtogif $shortname.ppm > $shortname.gif", True, GenericPostProcessor))
+
+ if self.open:
+ extension = {self.FORMAT_PNG : "png", self.FORMAT_JPEG: "jpg", self.FORMAT_GIF : "gif"}[self.format]
+ tool.jobs.append(Job("gnome-open \"$shortname.%s\"" % extension, True, GenericPostProcessor))
+
+ return tool
from tempfile import NamedTemporaryFile
class PreviewRenderer(ToolRunner):
- def render(self, source):
- """
- Render a preview image from LaTeX source
-
- @param source: some LaTeX source without \begin{document}
- """
- # create temp file with source
- self._temp_file = NamedTemporaryFile(mode="w", suffix=".tex")
- self._temp_file.write("\\documentclass{article}\\pagestyle{empty}\\begin{document}%s\\end{document}" % source)
- self._temp_file.flush()
-
- # generate Tool
- tool = ImageToolGenerator().generate()
- self._file = File(self._temp_file.name)
- issue_handler = MockStructuredIssueHandler()
-
- # run the Tool
- self.run(self._file, tool, issue_handler)
-
- def _on_tool_succeeded(self):
- # see ToolRunner._on_tool_succeeded
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(self._file.shortname + ".png")
- self.__cleanup()
- self._on_render_succeeded(pixbuf)
-
- def _on_tool_failed(self):
- # see ToolRunner._on_tool_failed
- self.__cleanup()
- self._on_render_failed()
-
- def __cleanup(self):
- """
- Remove the files created during the render process
- """
- # delete the temp file
- self._temp_file.close()
-
- # delete all files created during the build process
- for file in self._file.siblings:
- try:
- file.delete()
- self._log.debug("Removed %s" % file)
- except OSError:
- self._log.error("Failed to remove '%s'" % file)
-
- def _on_render_succeeded(self, pixbuf):
- """
- The rendering process has finished successfully
-
- @param pixbuf: a GdkPixbuf.Pixbuf containing the result image
- """
-
- def _on_render_failed(self):
- """
- The rendering process has failed
- """
-
-
+ def render(self, source):
+ """
+ Render a preview image from LaTeX source
+
+ @param source: some LaTeX source without \begin{document}
+ """
+ # create temp file with source
+ self._temp_file = NamedTemporaryFile(mode="w", suffix=".tex")
+ self._temp_file.write("\\documentclass{article}\\pagestyle{empty}\\begin{document}%s\\end{document}" % source)
+ self._temp_file.flush()
+
+ # generate Tool
+ tool = ImageToolGenerator().generate()
+ self._file = File(self._temp_file.name)
+ issue_handler = MockStructuredIssueHandler()
+
+ # run the Tool
+ self.run(self._file, tool, issue_handler)
+
+ def _on_tool_succeeded(self):
+ # see ToolRunner._on_tool_succeeded
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(self._file.shortname + ".png")
+ self.__cleanup()
+ self._on_render_succeeded(pixbuf)
+
+ def _on_tool_failed(self):
+ # see ToolRunner._on_tool_failed
+ self.__cleanup()
+ self._on_render_failed()
+
+ def __cleanup(self):
+ """
+ Remove the files created during the render process
+ """
+ # delete the temp file
+ self._temp_file.close()
+
+ # delete all files created during the build process
+ for file in self._file.siblings:
+ try:
+ file.delete()
+ self._log.debug("Removed %s" % file)
+ except OSError:
+ self._log.error("Failed to remove '%s'" % file)
+
+ def _on_render_succeeded(self, pixbuf):
+ """
+ The rendering process has finished successfully
+
+ @param pixbuf: a GdkPixbuf.Pixbuf containing the result image
+ """
+
+ def _on_render_failed(self):
+ """
+ The rendering process has failed
+ """
+
+
diff --git a/latex/latex/validator.py b/latex/latex/validator.py
index 8c6aa50..76df3cf 100644
--- a/latex/latex/validator.py
+++ b/latex/latex/validator.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -33,208 +33,208 @@ from environment import Environment
class LaTeXValidator(object):
- """
- This validates the following aspects by walking the document model
- and using the outline:
-
- * unused labels
- * unclosed environments, also "\[" and "\]"
- """
-
- _log = getLogger("LaTeXValidator")
-
- def __init__(self):
- self._environment = Environment()
-
- def validate(self, document_node, outline, issue_handler):
- """
- Validate a LaTeX document
-
- @param document_node: the root node of the document tree
- @param outline: a LaTeX outline object
- @param issue_handler: an object implementing IIssueHandler
- """
-
- self._log.debug("validate")
-
- #~ # TODO: this is dangerous, the outline object could be outdated
- #~ self._outline = outline
-
- # prepare a map for checking labels
- self._labels = {}
- for label in outline.labels:
- self._labels[label.value] = [label, False]
-
- self._environStack = []
-
- self._checkRefs = True
-
- self._run(document_node, issue_handler)
-
- # evaluate label map
- for label, used in self._labels.values():
- if not used:
- # FIXME: we need to know in which File the label was defined!
- issue_handler.issue(Issue("Label <b>%s</b> is never used" % escape(label.value), label.start, label.end, label.file, Issue.SEVERITY_WARNING))
-
- def _run(self, parentNode, issue_handler):
- """
- Recursive method validation
- """
- for node in parentNode:
- recurse = True
-# if node.type == Node.DOCUMENT:
-#
-# self._log.debug("DOCUMENT: %s" % node.value)
-#
-# # the document node contains the File object as value
-# self._file = node.value
-
- if node.type == Node.COMMAND:
- if node.value == "begin":
- try:
- # push environment on stack
- environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- self._environStack.append((environ, node.start, node.lastEnd))
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "end":
- try:
- # check environment
- environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- try:
- tEnviron, tStart, tEnd = self._environStack.pop()
- if tEnviron != environ:
- issue_handler.issue(Issue("Environment <b>%s</b> has to be ended before <b>%s</b>" % (escape(tEnviron), escape(environ)), node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
- except IndexError:
- issue_handler.issue(Issue("Environment <b>%s</b> has no beginning" % escape(environ), node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "[":
- # push eqn env on stack
- self._environStack.append(("[", node.start, node.end))
-
- elif node.value == "]":
- try:
- tEnviron, tStart, tEnd = self._environStack.pop()
- if tEnviron != "[":
- issue_handler.issue(Issue("Environment <b>%s</b> has to be ended before <b>]</b>" % escape(tEnviron), node.start, node.end, node.file, Issue.SEVERITY_ERROR))
- except IndexError:
- issue_handler.issue(Issue("Environment <b>%s</b> has no beginning" % escape(environ), node.start, node.end, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "ref" or node.value == "eqref" or node.value == "pageref":
- # mark label as used
- try:
- label = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- try:
- self._labels[label][1] = True
- except KeyError:
- issue_handler.issue(Issue("Label <b>%s</b> has not been defined" % escape(label), node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif self._checkRefs and (node.value == "includegraphics"):
- try:
- # check referenced image file
- target = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- if len(target) > 0:
- if File.is_absolute(target):
- filename = target
- else:
- file = File.create_from_relative_path(target, node.file.dirname)
- filename = file.path
-
- # an image may be specified without the extension
- potential_extensions = ["", ".eps", ".pdf", ".jpg", ".jpeg", ".gif", ".png"]
-
- found = False
- for ext in potential_extensions:
- if exists(filename + ext):
- found = True
- break
-
- if not found:
- issue_handler.issue(Issue("Image <b>%s</b> could not be found" % escape(target), node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
- else:
- issue_handler.issue(Issue("No image file specified", node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif self._checkRefs and (node.value == "include" or node.value == "input"):
- # check referenced tex file
- try:
- target = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- if len(target) > 0:
- if File.is_absolute(target):
- filename = target
- else:
- file = File.create_from_relative_path(target, node.file.dirname)
- filename = file.path
-
- # an input may be specified without the extension
- potential_extensions = ["", ".tex"]
-
- found = False
- for ext in potential_extensions:
- if exists(filename + ext):
- found = True
- break
-
- if not found:
- issue_handler.issue(Issue("Document <b>%s</b> could not be found" % escape(filename), node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
- except IndexError: # firstOfType failed
- # this happens on old-style syntax like "\input myfile"
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif self._checkRefs and node.value == "bibliography":
- try:
- # check referenced BibTeX file(s)
- value = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
- for target in value.split(","):
- if File.is_absolute(target):
- filename = target
- else:
- file = File.create_from_relative_path(target, node.file.dirname)
- filename = file.path
-
- # a bib file may be specified without the extension
- potential_extensions = ["", ".bib"]
-
- found = False
- for ext in potential_extensions:
- if exists(filename + ext):
- found = True
- break
-
- if not found:
- issue_handler.issue(Issue("Bibliography <b>%s</b> could not be found" % escape(filename), node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
- except IndexError: # firstOfType failed
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- elif node.value == "newcommand":
- # don't validate in newcommand definitions
- recurse = False
-
- elif node.value == "bibliographystyle":
- try:
- # check if style exists
- value = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
-
- # search the TeX environment
- if not self._environment.file_exists("%s.bst" % value):
- # search the working directory
- bst_file = File.create_from_relative_path("%s.bst" % value, node.file.dirname)
- if not bst_file.exists:
- issue_handler.issue(Issue("Bibliography style <b>%s</b> could not be found" % escape(value), node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
- except IndexError:
- issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
-
- if recurse:
- self._run(node, issue_handler)
-
- #~ def __del__(self):
- #~ print "Properly destroyed %s" % self
-
-
+ """
+ This validates the following aspects by walking the document model
+ and using the outline:
+
+ * unused labels
+ * unclosed environments, also "\[" and "\]"
+ """
+
+ _log = getLogger("LaTeXValidator")
+
+ def __init__(self):
+ self._environment = Environment()
+
+ def validate(self, document_node, outline, issue_handler):
+ """
+ Validate a LaTeX document
+
+ @param document_node: the root node of the document tree
+ @param outline: a LaTeX outline object
+ @param issue_handler: an object implementing IIssueHandler
+ """
+
+ self._log.debug("validate")
+
+ #~ # TODO: this is dangerous, the outline object could be outdated
+ #~ self._outline = outline
+
+ # prepare a map for checking labels
+ self._labels = {}
+ for label in outline.labels:
+ self._labels[label.value] = [label, False]
+
+ self._environStack = []
+
+ self._checkRefs = True
+
+ self._run(document_node, issue_handler)
+
+ # evaluate label map
+ for label, used in self._labels.values():
+ if not used:
+ # FIXME: we need to know in which File the label was defined!
+ issue_handler.issue(Issue("Label <b>%s</b> is never used" % escape(label.value), label.start, label.end, label.file, Issue.SEVERITY_WARNING))
+
+ def _run(self, parentNode, issue_handler):
+ """
+ Recursive method validation
+ """
+ for node in parentNode:
+ recurse = True
+# if node.type == Node.DOCUMENT:
+#
+# self._log.debug("DOCUMENT: %s" % node.value)
+#
+# # the document node contains the File object as value
+# self._file = node.value
+
+ if node.type == Node.COMMAND:
+ if node.value == "begin":
+ try:
+ # push environment on stack
+ environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ self._environStack.append((environ, node.start, node.lastEnd))
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "end":
+ try:
+ # check environment
+ environ = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ try:
+ tEnviron, tStart, tEnd = self._environStack.pop()
+ if tEnviron != environ:
+ issue_handler.issue(Issue("Environment <b>%s</b> has to be ended before <b>%s</b>" % (escape(tEnviron), escape(environ)), node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+ except IndexError:
+ issue_handler.issue(Issue("Environment <b>%s</b> has no beginning" % escape(environ), node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "[":
+ # push eqn env on stack
+ self._environStack.append(("[", node.start, node.end))
+
+ elif node.value == "]":
+ try:
+ tEnviron, tStart, tEnd = self._environStack.pop()
+ if tEnviron != "[":
+ issue_handler.issue(Issue("Environment <b>%s</b> has to be ended before <b>]</b>" % escape(tEnviron), node.start, node.end, node.file, Issue.SEVERITY_ERROR))
+ except IndexError:
+ issue_handler.issue(Issue("Environment <b>%s</b> has no beginning" % escape(environ), node.start, node.end, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "ref" or node.value == "eqref" or node.value == "pageref":
+ # mark label as used
+ try:
+ label = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ try:
+ self._labels[label][1] = True
+ except KeyError:
+ issue_handler.issue(Issue("Label <b>%s</b> has not been defined" % escape(label), node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif self._checkRefs and (node.value == "includegraphics"):
+ try:
+ # check referenced image file
+ target = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ if len(target) > 0:
+ if File.is_absolute(target):
+ filename = target
+ else:
+ file = File.create_from_relative_path(target, node.file.dirname)
+ filename = file.path
+
+ # an image may be specified without the extension
+ potential_extensions = ["", ".eps", ".pdf", ".jpg", ".jpeg", ".gif", ".png"]
+
+ found = False
+ for ext in potential_extensions:
+ if exists(filename + ext):
+ found = True
+ break
+
+ if not found:
+ issue_handler.issue(Issue("Image <b>%s</b> could not be found" % escape(target), node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
+ else:
+ issue_handler.issue(Issue("No image file specified", node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif self._checkRefs and (node.value == "include" or node.value == "input"):
+ # check referenced tex file
+ try:
+ target = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ if len(target) > 0:
+ if File.is_absolute(target):
+ filename = target
+ else:
+ file = File.create_from_relative_path(target, node.file.dirname)
+ filename = file.path
+
+ # an input may be specified without the extension
+ potential_extensions = ["", ".tex"]
+
+ found = False
+ for ext in potential_extensions:
+ if exists(filename + ext):
+ found = True
+ break
+
+ if not found:
+ issue_handler.issue(Issue("Document <b>%s</b> could not be found" % escape(filename), node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
+ except IndexError: # firstOfType failed
+ # this happens on old-style syntax like "\input myfile"
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif self._checkRefs and node.value == "bibliography":
+ try:
+ # check referenced BibTeX file(s)
+ value = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+ for target in value.split(","):
+ if File.is_absolute(target):
+ filename = target
+ else:
+ file = File.create_from_relative_path(target, node.file.dirname)
+ filename = file.path
+
+ # a bib file may be specified without the extension
+ potential_extensions = ["", ".bib"]
+
+ found = False
+ for ext in potential_extensions:
+ if exists(filename + ext):
+ found = True
+ break
+
+ if not found:
+ issue_handler.issue(Issue("Bibliography <b>%s</b> could not be found" % escape(filename), node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
+ except IndexError: # firstOfType failed
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ elif node.value == "newcommand":
+ # don't validate in newcommand definitions
+ recurse = False
+
+ elif node.value == "bibliographystyle":
+ try:
+ # check if style exists
+ value = node.firstOfType(Node.MANDATORY_ARGUMENT).innerText
+
+ # search the TeX environment
+ if not self._environment.file_exists("%s.bst" % value):
+ # search the working directory
+ bst_file = File.create_from_relative_path("%s.bst" % value, node.file.dirname)
+ if not bst_file.exists:
+ issue_handler.issue(Issue("Bibliography style <b>%s</b> could not be found" % escape(value), node.start, node.lastEnd, node.file, Issue.SEVERITY_WARNING))
+ except IndexError:
+ issue_handler.issue(Issue("Malformed command", node.start, node.lastEnd, node.file, Issue.SEVERITY_ERROR))
+
+ if recurse:
+ self._run(node, issue_handler)
+
+ #~ def __del__(self):
+ #~ print "Properly destroyed %s" % self
+
+
diff --git a/latex/latex/views.py b/latex/latex/views.py
index 9a44726..3c55cec 100755
--- a/latex/latex/views.py
+++ b/latex/latex/views.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -38,140 +38,140 @@ from ..issues import Issue
class SymbolCollection(object):
- """
- A collection of symbols read from an XML file
- """
-
- _log = getLogger("SymbolCollection")
-
-
- class Group(object):
- def __init__(self, label):
- """
- @param label: a label for this Group
- """
- self.label = label
- self.symbols = []
-
-
- class Symbol(object):
- def __init__(self, template, icon):
- """
- @param template: a Template instance
- @param icon: an icon filename
- """
- self.template = template
- self.icon = icon
-
-
- def __init__(self):
- filename = find_resource("symbols.xml")
-
- self.groups = []
-
- symbols_el = ElementTree.parse(filename).getroot()
- for group_el in symbols_el.findall("group"):
- group = self.Group(group_el.get("label"))
- for symbol_el in group_el.findall("symbol"):
- symbol = self.Symbol(Template(symbol_el.text.strip()), find_resource("icons/%s" % symbol_el.get("icon")))
- group.symbols.append(symbol)
- self.groups.append(group)
-
-
+ """
+ A collection of symbols read from an XML file
+ """
+
+ _log = getLogger("SymbolCollection")
+
+
+ class Group(object):
+ def __init__(self, label):
+ """
+ @param label: a label for this Group
+ """
+ self.label = label
+ self.symbols = []
+
+
+ class Symbol(object):
+ def __init__(self, template, icon):
+ """
+ @param template: a Template instance
+ @param icon: an icon filename
+ """
+ self.template = template
+ self.icon = icon
+
+
+ def __init__(self):
+ filename = find_resource("symbols.xml")
+
+ self.groups = []
+
+ symbols_el = ElementTree.parse(filename).getroot()
+ for group_el in symbols_el.findall("group"):
+ group = self.Group(group_el.get("label"))
+ for symbol_el in group_el.findall("symbol"):
+ symbol = self.Symbol(Template(symbol_el.text.strip()), find_resource("icons/%s" % symbol_el.get("icon")))
+ group.symbols.append(symbol)
+ self.groups.append(group)
+
+
class LaTeXSymbolMapView(SideView):
- """
- """
- __log = getLogger("LaTeXSymbolMapView")
-
- label = "Symbols"
- icon = Gtk.Image.new_from_stock(Gtk.STOCK_INDEX,Gtk.IconSize.MENU)
- scope = View.SCOPE_WINDOW
-
- def init(self, context):
- self.__log.debug("init")
-
- self.__context = context
- self.__preferences = Preferences()
-
- scrolled = Gtk.ScrolledWindow()
- scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
- scrolled.set_shadow_type(Gtk.ShadowType.NONE)
-
- self.__box = Gtk.VBox()
- scrolled.add_with_viewport(self.__box)
-
- self.add(scrolled)
- self.show_all()
-
- self.__load_collection(SymbolCollection())
-
- def __load_collection(self, collection):
- self.__expanded_groups = set(self.__preferences.get("expanded-symbol-groups", "").split(","))
-
- for group in collection.groups:
- self.__add_group(group)
-
- def __add_group(self, group):
- model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, object) # icon, tooltip, Template
-
- for symbol in group.symbols:
- try:
- model.append([GdkPixbuf.Pixbuf.new_from_file(symbol.icon), str(symbol.template), symbol.template])
- except GError, s:
- print s
-
- view = Gtk.IconView(model=model)
- view.set_pixbuf_column(0)
- view.set_selection_mode(Gtk.SelectionMode.SINGLE)
- view.connect("selection-changed", self.__on_symbol_selected)
- view.set_item_width(-1)
- view.set_spacing(0)
- view.set_column_spacing(0)
- view.set_row_spacing(0)
- view.set_columns(-1)
- view.set_text_column(-1)
-
- view.set_tooltip_column(1) # this requires PyGTK 2.12
-
- view.show()
-
- expander = Gtk.Expander(label=group.label)
- expander.add(view)
- expander.show_all()
-
- if group.label in self.__expanded_groups:
- expander.set_expanded(True)
-
- expander.connect("notify::expanded", self.__on_group_expanded, group.label)
-
- self.__box.pack_start(expander, False, False, 0)
-
- def __on_group_expanded(self, expander, paramSpec, group_label):
- """
- The Expander for a symbol group has been expanded
- """
- if expander.get_expanded():
- self.__expanded_groups.add(group_label)
- else:
- self.__expanded_groups.remove(group_label)
-
- self.__preferences.set("expanded-symbol-groups", ",".join(self.__expanded_groups))
-
- def __on_symbol_selected(self, icon_view):
- """
- A symbol has been selected
-
- @param icon_view: the Gtk.IconView
- """
- try:
- path = icon_view.get_selected_items()[0]
- template = icon_view.get_model()[path][2]
-
- self.__context.active_editor.insert(template)
-
- icon_view.unselect_all()
- except IndexError:
- pass # must be caught after unselect_all()
+ """
+ """
+ __log = getLogger("LaTeXSymbolMapView")
+
+ label = "Symbols"
+ icon = Gtk.Image.new_from_stock(Gtk.STOCK_INDEX,Gtk.IconSize.MENU)
+ scope = View.SCOPE_WINDOW
+
+ def init(self, context):
+ self.__log.debug("init")
+
+ self.__context = context
+ self.__preferences = Preferences()
+
+ scrolled = Gtk.ScrolledWindow()
+ scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ scrolled.set_shadow_type(Gtk.ShadowType.NONE)
+
+ self.__box = Gtk.VBox()
+ scrolled.add_with_viewport(self.__box)
+
+ self.add(scrolled)
+ self.show_all()
+
+ self.__load_collection(SymbolCollection())
+
+ def __load_collection(self, collection):
+ self.__expanded_groups = set(self.__preferences.get("expanded-symbol-groups", "").split(","))
+
+ for group in collection.groups:
+ self.__add_group(group)
+
+ def __add_group(self, group):
+ model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, object) # icon, tooltip, Template
+
+ for symbol in group.symbols:
+ try:
+ model.append([GdkPixbuf.Pixbuf.new_from_file(symbol.icon), str(symbol.template), symbol.template])
+ except GError, s:
+ print s
+
+ view = Gtk.IconView(model=model)
+ view.set_pixbuf_column(0)
+ view.set_selection_mode(Gtk.SelectionMode.SINGLE)
+ view.connect("selection-changed", self.__on_symbol_selected)
+ view.set_item_width(-1)
+ view.set_spacing(0)
+ view.set_column_spacing(0)
+ view.set_row_spacing(0)
+ view.set_columns(-1)
+ view.set_text_column(-1)
+
+ view.set_tooltip_column(1) # this requires PyGTK 2.12
+
+ view.show()
+
+ expander = Gtk.Expander(label=group.label)
+ expander.add(view)
+ expander.show_all()
+
+ if group.label in self.__expanded_groups:
+ expander.set_expanded(True)
+
+ expander.connect("notify::expanded", self.__on_group_expanded, group.label)
+
+ self.__box.pack_start(expander, False, False, 0)
+
+ def __on_group_expanded(self, expander, paramSpec, group_label):
+ """
+ The Expander for a symbol group has been expanded
+ """
+ if expander.get_expanded():
+ self.__expanded_groups.add(group_label)
+ else:
+ self.__expanded_groups.remove(group_label)
+
+ self.__preferences.set("expanded-symbol-groups", ",".join(self.__expanded_groups))
+
+ def __on_symbol_selected(self, icon_view):
+ """
+ A symbol has been selected
+
+ @param icon_view: the Gtk.IconView
+ """
+ try:
+ path = icon_view.get_selected_items()[0]
+ template = icon_view.get_model()[path][2]
+
+ self.__context.active_editor.insert(template)
+
+ icon_view.unselect_all()
+ except IndexError:
+ pass # must be caught after unselect_all()
from os import system
@@ -182,206 +182,206 @@ from outline import OutlineNode
class LaTeXOutlineView(BaseOutlineView):
- """
- A View showing an outline of the edited LaTeX document
- """
-
- _log = getLogger("LaTeXOutlineView")
-
- label = "Outline"
- scope = View.SCOPE_EDITOR
-
- def __init__(self, context, editor):
- BaseOutlineView.__init__(self, context, editor)
- self._handlers = {}
-
- @property
- def icon(self):
- image = Gtk.Image()
- image.set_from_file(find_resource("icons/outline.png"))
- return image
-
- def init(self, context):
- BaseOutlineView.init(self, context)
-
- self._offset_map = OutlineOffsetMap()
-
- # additional toolbar buttons
- btn_graphics = Gtk.ToggleToolButton()
- btn_graphics.set_icon_widget(Gtk.Image.new_from_file(find_resource("icons/tree_includegraphics.png")))
- btn_graphics.set_tooltip_text("Show graphics")
- self._toolbar.insert(btn_graphics, -1)
-
- btn_tables = Gtk.ToggleToolButton()
- btn_tables.set_icon_widget(Gtk.Image.new_from_file(find_resource("icons/tree_table.png")))
- btn_tables.set_tooltip_text("Show tables")
- self._toolbar.insert(btn_tables, -1)
-
- btn_graphics.set_active(Preferences().get_bool("outline-show-graphics"))
- btn_tables.set_active(Preferences().get_bool("outline-show-tables"))
-
- self._handlers[btn_graphics] = btn_graphics.connect("toggled", self._on_graphics_toggled)
- self._handlers[btn_tables] = btn_tables.connect("toggled", self._on_tables_toggled)
-
- def set_outline(self, outline):
- """
- Load a new outline model
- """
- self._log.debug("set_outline")
-
- self.assure_init()
-
- self._save_state()
-
- self._offset_map = OutlineOffsetMap()
- OutlineConverter().convert(self._store, outline, self._offset_map, self._editor.edited_file)
-
- self._restore_state()
-
- def _on_node_selected(self, node):
- """
- An outline node has been selected
- """
- if Preferences().get_bool("outline-connect-to-editor"):
- if node.file == self._editor.edited_file:
- self._editor.select(node.start, node.end)
-
- def _on_node_activated(self, node):
- """
- An outline node has been double-clicked on
-
- @param node: an instance of latex.outline.OutlineNode
- """
- if node.type == OutlineNode.GRAPHICS:
- # use 'gnome-open' to open the graphics file
-
- target = node.value
-
- if not target:
- return
-
- # the way we use a mixture of File objects and filenames is not optimal...
- if File.is_absolute(target):
- filename = target
- else:
- filename = File.create_from_relative_path(target, node.file.dirname).path
-
- # an image may be specified without the extension
- potential_extensions = ["", ".eps", ".pdf", ".jpg", ".jpeg", ".gif", ".png"]
-
- found = False
- for ext in potential_extensions:
- f = File(filename + ext)
- if f.exists:
- found = True
- break
-
- if not found:
- self._log.error("File not found: %s" % filename)
- return
-
- system("gnome-open %s" % f.uri)
-
- else:
- # open/activate the referenced file, if the node is 'foreign'
- if node.file != self._editor.edited_file:
- self._context.activate_editor(node.file)
-
- def _on_tables_toggled(self, toggle_button):
- value = toggle_button.get_active()
-# Settings().set("LatexOutlineTables", value)
-# self.trigger("tablesToggled", value)
- Preferences().set("outline-show-tables", value)
-
- def _on_graphics_toggled(self, toggle_button):
- value = toggle_button.get_active()
-# Settings().set("LatexOutlineGraphics", value)
-# self.trigger("graphicsToggled", value)
- Preferences().set("outline-show-graphics", value)
-
- def destroy(self):
- for obj in self._handlers:
- obj.disconnect(self._handlers[obj])
- BaseOutlineView.destroy(self)
-
+ """
+ A View showing an outline of the edited LaTeX document
+ """
+
+ _log = getLogger("LaTeXOutlineView")
+
+ label = "Outline"
+ scope = View.SCOPE_EDITOR
+
+ def __init__(self, context, editor):
+ BaseOutlineView.__init__(self, context, editor)
+ self._handlers = {}
+
+ @property
+ def icon(self):
+ image = Gtk.Image()
+ image.set_from_file(find_resource("icons/outline.png"))
+ return image
+
+ def init(self, context):
+ BaseOutlineView.init(self, context)
+
+ self._offset_map = OutlineOffsetMap()
+
+ # additional toolbar buttons
+ btn_graphics = Gtk.ToggleToolButton()
+ btn_graphics.set_icon_widget(Gtk.Image.new_from_file(find_resource("icons/tree_includegraphics.png")))
+ btn_graphics.set_tooltip_text("Show graphics")
+ self._toolbar.insert(btn_graphics, -1)
+
+ btn_tables = Gtk.ToggleToolButton()
+ btn_tables.set_icon_widget(Gtk.Image.new_from_file(find_resource("icons/tree_table.png")))
+ btn_tables.set_tooltip_text("Show tables")
+ self._toolbar.insert(btn_tables, -1)
+
+ btn_graphics.set_active(Preferences().get_bool("outline-show-graphics"))
+ btn_tables.set_active(Preferences().get_bool("outline-show-tables"))
+
+ self._handlers[btn_graphics] = btn_graphics.connect("toggled", self._on_graphics_toggled)
+ self._handlers[btn_tables] = btn_tables.connect("toggled", self._on_tables_toggled)
+
+ def set_outline(self, outline):
+ """
+ Load a new outline model
+ """
+ self._log.debug("set_outline")
+
+ self.assure_init()
+
+ self._save_state()
+
+ self._offset_map = OutlineOffsetMap()
+ OutlineConverter().convert(self._store, outline, self._offset_map, self._editor.edited_file)
+
+ self._restore_state()
+
+ def _on_node_selected(self, node):
+ """
+ An outline node has been selected
+ """
+ if Preferences().get_bool("outline-connect-to-editor"):
+ if node.file == self._editor.edited_file:
+ self._editor.select(node.start, node.end)
+
+ def _on_node_activated(self, node):
+ """
+ An outline node has been double-clicked on
+
+ @param node: an instance of latex.outline.OutlineNode
+ """
+ if node.type == OutlineNode.GRAPHICS:
+ # use 'gnome-open' to open the graphics file
+
+ target = node.value
+
+ if not target:
+ return
+
+ # the way we use a mixture of File objects and filenames is not optimal...
+ if File.is_absolute(target):
+ filename = target
+ else:
+ filename = File.create_from_relative_path(target, node.file.dirname).path
+
+ # an image may be specified without the extension
+ potential_extensions = ["", ".eps", ".pdf", ".jpg", ".jpeg", ".gif", ".png"]
+
+ found = False
+ for ext in potential_extensions:
+ f = File(filename + ext)
+ if f.exists:
+ found = True
+ break
+
+ if not found:
+ self._log.error("File not found: %s" % filename)
+ return
+
+ system("gnome-open %s" % f.uri)
+
+ else:
+ # open/activate the referenced file, if the node is 'foreign'
+ if node.file != self._editor.edited_file:
+ self._context.activate_editor(node.file)
+
+ def _on_tables_toggled(self, toggle_button):
+ value = toggle_button.get_active()
+# Settings().set("LatexOutlineTables", value)
+# self.trigger("tablesToggled", value)
+ Preferences().set("outline-show-tables", value)
+
+ def _on_graphics_toggled(self, toggle_button):
+ value = toggle_button.get_active()
+# Settings().set("LatexOutlineGraphics", value)
+# self.trigger("graphicsToggled", value)
+ Preferences().set("outline-show-graphics", value)
+
+ def destroy(self):
+ for obj in self._handlers:
+ obj.disconnect(self._handlers[obj])
+ BaseOutlineView.destroy(self)
+
from os.path import basename
class OutlineConverter(object):
- """
- This creates a Gtk.TreeStore object from a LaTeX outline model
- """
-
- _ICON_LABEL = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/label.png"))
- _ICON_TABLE = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_table.png"))
- _ICON_GRAPHICS = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_includegraphics.png"))
-
- _LEVEL_ICONS = { 1 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_part.png")),
- 2 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_chapter.png")),
- 3 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_section.png")),
- 4 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_subsection.png")),
- 5 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_subsubsection.png")),
- 6 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_paragraph.png")),
- 7 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_paragraph.png")) }
-
- def __init__(self):
- self._preferences = Preferences()
-
- def convert(self, tree_store, outline, offset_map, file):
- """
- Convert an Outline object to a Gtk.TreeStore and update an OutlineOffsetMap
-
- @param tree_store: Gtk.TreeStore
- @param outline: latex.outline.Outline
- @param offset_map: outline.OutlineOffsetMap
- @param file: the edited File (to identify foreign outline nodes)
- """
- self._offsetMap = offset_map
- self._treeStore = tree_store
- self._treeStore.clear()
- self._file = file
-
- self._append(None, outline.rootNode)
-
- def _append(self, parent, node):
- """
- Recursively append an outline node to the TreeStore
-
- @param parent: a Gtk.TreeIter or None
- @param node: an OutlineNode
- """
- value = node.value
-
- color = self._preferences.get("light-foreground-color")
-
- if node.file and node.file != self._file:
- value = "%s <span color='%s'>%s</span>" % (value, color, node.file.shortbasename)
-
- if node.type == OutlineNode.STRUCTURE:
- icon = self._LEVEL_ICONS[node.level]
- parent = self._treeStore.append(parent, [value, icon, node])
- elif node.type == OutlineNode.LABEL:
- parent = self._treeStore.append(parent, [value, self._ICON_LABEL, node])
- elif node.type == OutlineNode.TABLE:
- parent = self._treeStore.append(parent, [value, self._ICON_TABLE, node])
- elif node.type == OutlineNode.GRAPHICS:
- label = basename(node.value)
-
- if node.file and node.file != self._file:
- label = "%s <span color='%s'>%s</span>" % (label, color, node.file.shortbasename)
-
- parent = self._treeStore.append(parent, [label, self._ICON_GRAPHICS, node])
-
- # store path in offset map for all non-foreign nodes
- # check for parent to ignore root node
- if parent and not node.foreign:
- path = self._treeStore.get_path(parent)
- self._offsetMap.put(node.start, path)
-
- for child in node:
- self._append(parent, child)
-
-
-
-
+ """
+ This creates a Gtk.TreeStore object from a LaTeX outline model
+ """
+
+ _ICON_LABEL = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/label.png"))
+ _ICON_TABLE = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_table.png"))
+ _ICON_GRAPHICS = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_includegraphics.png"))
+
+ _LEVEL_ICONS = { 1 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_part.png")),
+ 2 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_chapter.png")),
+ 3 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_section.png")),
+ 4 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_subsection.png")),
+ 5 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_subsubsection.png")),
+ 6 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_paragraph.png")),
+ 7 : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/tree_paragraph.png")) }
+
+ def __init__(self):
+ self._preferences = Preferences()
+
+ def convert(self, tree_store, outline, offset_map, file):
+ """
+ Convert an Outline object to a Gtk.TreeStore and update an OutlineOffsetMap
+
+ @param tree_store: Gtk.TreeStore
+ @param outline: latex.outline.Outline
+ @param offset_map: outline.OutlineOffsetMap
+ @param file: the edited File (to identify foreign outline nodes)
+ """
+ self._offsetMap = offset_map
+ self._treeStore = tree_store
+ self._treeStore.clear()
+ self._file = file
+
+ self._append(None, outline.rootNode)
+
+ def _append(self, parent, node):
+ """
+ Recursively append an outline node to the TreeStore
+
+ @param parent: a Gtk.TreeIter or None
+ @param node: an OutlineNode
+ """
+ value = node.value
+
+ color = self._preferences.get("light-foreground-color")
+
+ if node.file and node.file != self._file:
+ value = "%s <span color='%s'>%s</span>" % (value, color, node.file.shortbasename)
+
+ if node.type == OutlineNode.STRUCTURE:
+ icon = self._LEVEL_ICONS[node.level]
+ parent = self._treeStore.append(parent, [value, icon, node])
+ elif node.type == OutlineNode.LABEL:
+ parent = self._treeStore.append(parent, [value, self._ICON_LABEL, node])
+ elif node.type == OutlineNode.TABLE:
+ parent = self._treeStore.append(parent, [value, self._ICON_TABLE, node])
+ elif node.type == OutlineNode.GRAPHICS:
+ label = basename(node.value)
+
+ if node.file and node.file != self._file:
+ label = "%s <span color='%s'>%s</span>" % (label, color, node.file.shortbasename)
+
+ parent = self._treeStore.append(parent, [label, self._ICON_GRAPHICS, node])
+
+ # store path in offset map for all non-foreign nodes
+ # check for parent to ignore root node
+ if parent and not node.foreign:
+ path = self._treeStore.get_path(parent)
+ self._offsetMap.put(node.start, path)
+
+ for child in node:
+ self._append(parent, child)
+
+
+
+
diff --git a/latex/outline.py b/latex/outline.py
index 087746b..45e2178 100644
--- a/latex/outline.py
+++ b/latex/outline.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -33,304 +33,304 @@ from base.resources import find_resource
class BaseOutlineView(SideView):
- """
- Base class for the BibTeX and LaTeX outline views
- """
-
- __log = getLogger("BaseOutlineView")
-
- label = "Outline"
- scope = View.SCOPE_EDITOR
-
- def __init__(self, context, editor):
- SideView.__init__(self, context)
- self._editor = editor
- self._base_handlers = {}
-
- @property
- def icon(self):
- image = Gtk.Image()
- image.set_from_file(find_resource("icons/outline.png"))
- return image
-
- def init(self, context):
- self._log.debug("init")
-
- self._context = context
-
- self._preferences = Preferences()
-
- # toolbar
-
- btn_follow = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_CONNECT)
- btn_follow.set_tooltip_text("Follow Editor")
- btn_follow.set_active(self._preferences.get_bool("outline-connect-to-editor"))
- self._base_handlers[btn_follow] = btn_follow.connect("toggled", self._on_follow_toggled)
-
- btn_expand = Gtk.ToolButton.new_from_stock(Gtk.STOCK_ZOOM_IN)
- btn_expand.set_tooltip_text("Expand All")
- self._base_handlers[btn_expand] = btn_expand.connect("clicked", self._on_expand_clicked)
-
- btn_collapse = Gtk.ToolButton.new_from_stock(Gtk.STOCK_ZOOM_OUT)
- btn_collapse.set_tooltip_text("Collapse All")
- self._base_handlers[btn_collapse] = btn_collapse.connect("clicked", self._on_collapse_clicked)
-
- self._toolbar = Gtk.Toolbar()
- self._toolbar.set_style(Gtk.ToolbarStyle.ICONS)
- self._toolbar.set_icon_size(Gtk.IconSize.MENU)
- self._toolbar.insert(btn_follow, -1)
- self._toolbar.insert(Gtk.SeparatorToolItem(), -1)
- self._toolbar.insert(btn_expand, -1)
- self._toolbar.insert(btn_collapse, -1)
- self._toolbar.insert(Gtk.SeparatorToolItem(), -1)
-
- self.pack_start(self._toolbar, False, True, 0)
-
- # tree view
-
- column = Gtk.TreeViewColumn()
-
- pixbuf_renderer = Gtk.CellRendererPixbuf()
- column.pack_start(pixbuf_renderer, False)
- column.add_attribute(pixbuf_renderer, "pixbuf", 1)
-
- text_renderer = Gtk.CellRendererText()
- column.pack_start(text_renderer, True)
- column.add_attribute(text_renderer, "markup", 0)
-
- self._offset_map = OutlineOffsetMap()
-
- self._store = Gtk.TreeStore(str, GdkPixbuf.Pixbuf, object) # label, icon, node object
-
- self._view = Gtk.TreeView(model=self._store)
- self._view.append_column(column)
- self._view.set_headers_visible(False)
- self._cursor_changed_id = self._view.connect("cursor-changed", self._on_cursor_changed)
- self._base_handlers[self._view] = self._view.connect("row-activated", self._on_row_activated)
-
- scrolled = Gtk.ScrolledWindow()
- scrolled.add(self._view)
- scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
-
- self.pack_start(scrolled, True, True, 0)
-
- # theme like gtk3
- ctx = scrolled.get_style_context()
- ctx.set_junction_sides(Gtk.JunctionSides.TOP)
-
- ctx = self._toolbar.get_style_context()
- ctx.set_junction_sides(Gtk.JunctionSides.TOP | Gtk.JunctionSides.BOTTOM)
- ctx.add_class("inline-toolbar")
-
- # this holds a list of the currently expanded paths
- self._expandedPaths = None
-
- self.show_all()
-
- def _on_follow_toggled(self, toggle_button):
- value = toggle_button.get_active()
- self._preferences.set("outline-connect-to-editor", value)
-
- def _on_expand_clicked(self, button):
- self._view.expand_all()
-
- def _on_collapse_clicked(self, button):
- self._view.collapse_all()
-
- def select_path_by_offset(self, offset):
- """
- Select the path corresponding to a given offset in the source
-
- Called by the Editor
- """
- self.assure_init()
-
- try:
- path = self._offset_map.lookup(offset)
- self._select_path(path)
- except KeyError:
- pass
-
- def _save_state(self):
- """
- Save the current expand state
- """
- self._expanded_paths = []
- self._view.map_expanded_rows(self._save_state_map_function,None)
-
- def _save_state_map_function(self, view, path):
- """
- Mapping function for saving the current expand state
- """
- self._expanded_paths.append(path)
-
- def _restore_state(self):
- """
- Restore the last expand state
- """
- self._view.collapse_all()
-
- if self._expanded_paths:
- for path in self._expanded_paths:
- self._view.expand_to_path(path)
-# else:
-# self._view.expand_to_path((0,))
-
- def _on_cursor_changed(self, view):
- store, it = view.get_selection().get_selected()
- if not it:
- return
-
- outline_node = store.get_value(it, 2)
-
- self._on_node_selected(outline_node)
-
- def _on_row_activated(self, view, path, column):
- it = self._store.get_iter(path)
- node = self._store.get(it, 2)[0]
-
- self._on_node_activated(node)
-
- def _select_path(self, path):
- """
- Expand a path and select the last node
- """
- # disconnect from 'cursor-changed'
- self._view.disconnect(self._cursor_changed_id)
-
- # select path
- self._view.expand_to_path(path)
- self._view.set_cursor(path, None, False)
-
- # connect to 'cursor-changed' again
- self._cursor_changed_id = self._view.connect("cursor-changed", self._on_cursor_changed)
-
- #
- # methods to be overridden by the subclass
- #
-
- def _on_node_selected(self, node):
- """
- To be overridden
- """
-
- def _on_node_activated(self, node):
- """
- To be overridden
- """
-
- def set_outline(self, outline):
- """
- Load a new outline model
-
- To be overridden
- """
-
- def destroy(self):
- self._view.disconnect(self._cursor_changed_id)
- for obj in self._base_handlers:
- obj.disconnect(self._base_handlers[obj])
- del self._editor
- SideView.destroy(self)
-
+ """
+ Base class for the BibTeX and LaTeX outline views
+ """
+
+ __log = getLogger("BaseOutlineView")
+
+ label = "Outline"
+ scope = View.SCOPE_EDITOR
+
+ def __init__(self, context, editor):
+ SideView.__init__(self, context)
+ self._editor = editor
+ self._base_handlers = {}
+
+ @property
+ def icon(self):
+ image = Gtk.Image()
+ image.set_from_file(find_resource("icons/outline.png"))
+ return image
+
+ def init(self, context):
+ self._log.debug("init")
+
+ self._context = context
+
+ self._preferences = Preferences()
+
+ # toolbar
+
+ btn_follow = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_CONNECT)
+ btn_follow.set_tooltip_text("Follow Editor")
+ btn_follow.set_active(self._preferences.get_bool("outline-connect-to-editor"))
+ self._base_handlers[btn_follow] = btn_follow.connect("toggled", self._on_follow_toggled)
+
+ btn_expand = Gtk.ToolButton.new_from_stock(Gtk.STOCK_ZOOM_IN)
+ btn_expand.set_tooltip_text("Expand All")
+ self._base_handlers[btn_expand] = btn_expand.connect("clicked", self._on_expand_clicked)
+
+ btn_collapse = Gtk.ToolButton.new_from_stock(Gtk.STOCK_ZOOM_OUT)
+ btn_collapse.set_tooltip_text("Collapse All")
+ self._base_handlers[btn_collapse] = btn_collapse.connect("clicked", self._on_collapse_clicked)
+
+ self._toolbar = Gtk.Toolbar()
+ self._toolbar.set_style(Gtk.ToolbarStyle.ICONS)
+ self._toolbar.set_icon_size(Gtk.IconSize.MENU)
+ self._toolbar.insert(btn_follow, -1)
+ self._toolbar.insert(Gtk.SeparatorToolItem(), -1)
+ self._toolbar.insert(btn_expand, -1)
+ self._toolbar.insert(btn_collapse, -1)
+ self._toolbar.insert(Gtk.SeparatorToolItem(), -1)
+
+ self.pack_start(self._toolbar, False, True, 0)
+
+ # tree view
+
+ column = Gtk.TreeViewColumn()
+
+ pixbuf_renderer = Gtk.CellRendererPixbuf()
+ column.pack_start(pixbuf_renderer, False)
+ column.add_attribute(pixbuf_renderer, "pixbuf", 1)
+
+ text_renderer = Gtk.CellRendererText()
+ column.pack_start(text_renderer, True)
+ column.add_attribute(text_renderer, "markup", 0)
+
+ self._offset_map = OutlineOffsetMap()
+
+ self._store = Gtk.TreeStore(str, GdkPixbuf.Pixbuf, object) # label, icon, node object
+
+ self._view = Gtk.TreeView(model=self._store)
+ self._view.append_column(column)
+ self._view.set_headers_visible(False)
+ self._cursor_changed_id = self._view.connect("cursor-changed", self._on_cursor_changed)
+ self._base_handlers[self._view] = self._view.connect("row-activated", self._on_row_activated)
+
+ scrolled = Gtk.ScrolledWindow()
+ scrolled.add(self._view)
+ scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+
+ self.pack_start(scrolled, True, True, 0)
+
+ # theme like gtk3
+ ctx = scrolled.get_style_context()
+ ctx.set_junction_sides(Gtk.JunctionSides.TOP)
+
+ ctx = self._toolbar.get_style_context()
+ ctx.set_junction_sides(Gtk.JunctionSides.TOP | Gtk.JunctionSides.BOTTOM)
+ ctx.add_class("inline-toolbar")
+
+ # this holds a list of the currently expanded paths
+ self._expandedPaths = None
+
+ self.show_all()
+
+ def _on_follow_toggled(self, toggle_button):
+ value = toggle_button.get_active()
+ self._preferences.set("outline-connect-to-editor", value)
+
+ def _on_expand_clicked(self, button):
+ self._view.expand_all()
+
+ def _on_collapse_clicked(self, button):
+ self._view.collapse_all()
+
+ def select_path_by_offset(self, offset):
+ """
+ Select the path corresponding to a given offset in the source
+
+ Called by the Editor
+ """
+ self.assure_init()
+
+ try:
+ path = self._offset_map.lookup(offset)
+ self._select_path(path)
+ except KeyError:
+ pass
+
+ def _save_state(self):
+ """
+ Save the current expand state
+ """
+ self._expanded_paths = []
+ self._view.map_expanded_rows(self._save_state_map_function,None)
+
+ def _save_state_map_function(self, view, path):
+ """
+ Mapping function for saving the current expand state
+ """
+ self._expanded_paths.append(path)
+
+ def _restore_state(self):
+ """
+ Restore the last expand state
+ """
+ self._view.collapse_all()
+
+ if self._expanded_paths:
+ for path in self._expanded_paths:
+ self._view.expand_to_path(path)
+# else:
+# self._view.expand_to_path((0,))
+
+ def _on_cursor_changed(self, view):
+ store, it = view.get_selection().get_selected()
+ if not it:
+ return
+
+ outline_node = store.get_value(it, 2)
+
+ self._on_node_selected(outline_node)
+
+ def _on_row_activated(self, view, path, column):
+ it = self._store.get_iter(path)
+ node = self._store.get(it, 2)[0]
+
+ self._on_node_activated(node)
+
+ def _select_path(self, path):
+ """
+ Expand a path and select the last node
+ """
+ # disconnect from 'cursor-changed'
+ self._view.disconnect(self._cursor_changed_id)
+
+ # select path
+ self._view.expand_to_path(path)
+ self._view.set_cursor(path, None, False)
+
+ # connect to 'cursor-changed' again
+ self._cursor_changed_id = self._view.connect("cursor-changed", self._on_cursor_changed)
+
+ #
+ # methods to be overridden by the subclass
+ #
+
+ def _on_node_selected(self, node):
+ """
+ To be overridden
+ """
+
+ def _on_node_activated(self, node):
+ """
+ To be overridden
+ """
+
+ def set_outline(self, outline):
+ """
+ Load a new outline model
+
+ To be overridden
+ """
+
+ def destroy(self):
+ self._view.disconnect(self._cursor_changed_id)
+ for obj in self._base_handlers:
+ obj.disconnect(self._base_handlers[obj])
+ del self._editor
+ SideView.destroy(self)
+
class Item(object):
- def __init__(self, key, value):
- self.key = key
- self.value = value
-
- def __cmp__(self, item):
- return self.key.__cmp__(item.key)
+ def __init__(self, key, value):
+ self.key = key
+ self.value = value
+
+ def __cmp__(self, item):
+ return self.key.__cmp__(item.key)
class OffsetLookupTree(object):
- def __init__(self):
- self.items = []
-
- def insert(self, key, value):
- """
- Insert a value
- """
- self.items.append(Item(key, value))
-
- def prepare(self):
- """
- Prepare the structure for being searched
- """
- self.items.sort()
-
- def find(self, key):
- """
- Find a value by its key
- """
- return self._find(key, 0, len(self.items) - 1)
-
- def _find(self, key, lo, hi):
- if hi - lo == 0:
- raise KeyError
-
- i = (hi - lo)/2
- item = self.items[i]
- if item.key == key:
- return item.value
- elif item.key > key:
- return self._find(key, lo, i - 1)
- elif item.key < key:
- return self._find(key, i + 1, hi)
+ def __init__(self):
+ self.items = []
+
+ def insert(self, key, value):
+ """
+ Insert a value
+ """
+ self.items.append(Item(key, value))
+
+ def prepare(self):
+ """
+ Prepare the structure for being searched
+ """
+ self.items.sort()
+
+ def find(self, key):
+ """
+ Find a value by its key
+ """
+ return self._find(key, 0, len(self.items) - 1)
+
+ def _find(self, key, lo, hi):
+ if hi - lo == 0:
+ raise KeyError
+
+ i = (hi - lo)/2
+ item = self.items[i]
+ if item.key == key:
+ return item.value
+ elif item.key > key:
+ return self._find(key, lo, i - 1)
+ elif item.key < key:
+ return self._find(key, i + 1, hi)
class OutlineOffsetMap(object):
- """
- This stores a mapping from the start offsets of outline elements
- to paths in the outline tree.
-
- We need this for the 'connect-outline-to-editor' feature.
-
- We may not use a simple dictionary for that because we need some
- kind of neighborhood lookup as we never get the exact offsets.
- """
-
- # TODO: find a faster structure for this - this is a tree with a special
- # find method
-
- def __init__(self):
- self._map = {}
-
- def clear(self):
- self._map = {}
-
- def put(self, offset, path):
- self._map[offset] = path
-
- def lookup(self, offset):
- """
- Returns the outline element containing a certain offset
- """
-
- # sort offsets
- offsets = self._map.keys()
- offsets.sort()
-
- # find nearest offset
- nearestOffset = None
- for o in offsets:
- if o > offset:
- break
- nearestOffset = o
-
- if not nearestOffset:
- raise KeyError
-
- return self._map[nearestOffset]
-
- def __str__(self):
- s = "<OutlineOffsetMap>"
-
- ofs = self._map.keys()
- ofs.sort()
-
- for o in ofs:
- s += "\n\t%s : %s" % (o, self._map[o])
- s += "\n</OutlineOffsetMap>"
- return s
+ """
+ This stores a mapping from the start offsets of outline elements
+ to paths in the outline tree.
+
+ We need this for the 'connect-outline-to-editor' feature.
+
+ We may not use a simple dictionary for that because we need some
+ kind of neighborhood lookup as we never get the exact offsets.
+ """
+
+ # TODO: find a faster structure for this - this is a tree with a special
+ # find method
+
+ def __init__(self):
+ self._map = {}
+
+ def clear(self):
+ self._map = {}
+
+ def put(self, offset, path):
+ self._map[offset] = path
+
+ def lookup(self, offset):
+ """
+ Returns the outline element containing a certain offset
+ """
+
+ # sort offsets
+ offsets = self._map.keys()
+ offsets.sort()
+
+ # find nearest offset
+ nearestOffset = None
+ for o in offsets:
+ if o > offset:
+ break
+ nearestOffset = o
+
+ if not nearestOffset:
+ raise KeyError
+
+ return self._map[nearestOffset]
+
+ def __str__(self):
+ s = "<OutlineOffsetMap>"
+
+ ofs = self._map.keys()
+ ofs.sort()
+
+ for o in ofs:
+ s += "\n\t%s : %s" % (o, self._map[o])
+ s += "\n</OutlineOffsetMap>"
+ return s
diff --git a/latex/preferences/__init__.py b/latex/preferences/__init__.py
index ac0531c..bb4bf7c 100644
--- a/latex/preferences/__init__.py
+++ b/latex/preferences/__init__.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -33,35 +33,35 @@ from ..util import singleton
@singleton
class Preferences(GObject.GObject):
- __gsignals__ = {
- "preferences-changed": (
- GObject.SignalFlags.RUN_LAST, None, [str, str]),
- }
+ __gsignals__ = {
+ "preferences-changed": (
+ GObject.SignalFlags.RUN_LAST, None, [str, str]),
+ }
- """
- A simple map storing preferences as key-value-pairs
- """
-
- _log = logging.getLogger("Preferences")
-
- TEMPLATE_DIR = os.path.join(GLib.get_user_data_dir(), "gedit", "latex", "templates")
-
- def __init__(self):
- GObject.GObject.__init__(self)
- self._settings = Gio.Settings("org.gnome.gedit.plugins.latex")
- self._log.debug("Constructed")
-
- def get(self, key, default=None):
- if default:
- return default
- return self._settings[key]
-
- def get_bool(self, key):
- return self._settings[key]
-
- def set(self, key, value):
- self._settings[key] = value
- self.emit("preferences-changed", str(key), str(value))
-
- def save(self):
- pass
+ """
+ A simple map storing preferences as key-value-pairs
+ """
+
+ _log = logging.getLogger("Preferences")
+
+ TEMPLATE_DIR = os.path.join(GLib.get_user_data_dir(), "gedit", "latex", "templates")
+
+ def __init__(self):
+ GObject.GObject.__init__(self)
+ self._settings = Gio.Settings("org.gnome.gedit.plugins.latex")
+ self._log.debug("Constructed")
+
+ def get(self, key, default=None):
+ if default:
+ return default
+ return self._settings[key]
+
+ def get_bool(self, key):
+ return self._settings[key]
+
+ def set(self, key, value):
+ self._settings[key] = value
+ self.emit("preferences-changed", str(key), str(value))
+
+ def save(self):
+ pass
diff --git a/latex/preferences/dialog.py b/latex/preferences/dialog.py
index 101e5bb..80ba7c0 100644
--- a/latex/preferences/dialog.py
+++ b/latex/preferences/dialog.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -34,487 +34,487 @@ from . import Preferences
from .tools import ToolPreferences
def _insert_column_with_attributes(view, pos, title, rend, **kwargs):
- print kwargs
- tv = Gtk.TreeViewColumn(title)
- tv.pack_start(rend, True)
- for k in kwargs:
- tv.add_attribute(rend, k, kwargs[k])
- view.insert_column(tv, pos)
+ print kwargs
+ tv = Gtk.TreeViewColumn(title)
+ tv.pack_start(rend, True)
+ for k in kwargs:
+ tv.add_attribute(rend, k, kwargs[k])
+ view.insert_column(tv, pos)
class PreferencesSpinButtonProxy(object):
- def __init__(self, widget, key):
- """
- @param widget: a SpinButton widget
- @param key:
- @param default_value:
- """
- self._widget = widget
- self._key = key
- self._preferences = Preferences()
-
- self._widget.set_value(int(self._preferences.get(key)))
-
- self._widget.connect("value-changed", self._on_value_changed)
-
- def _on_value_changed(self, spin_button):
- self._preferences.set(self._key, spin_button.get_value_as_int())
-
+ def __init__(self, widget, key):
+ """
+ @param widget: a SpinButton widget
+ @param key:
+ @param default_value:
+ """
+ self._widget = widget
+ self._key = key
+ self._preferences = Preferences()
+
+ self._widget.set_value(int(self._preferences.get(key)))
+
+ self._widget.connect("value-changed", self._on_value_changed)
+
+ def _on_value_changed(self, spin_button):
+ self._preferences.set(self._key, spin_button.get_value_as_int())
+
class ConfigureToolDialog(GladeInterface):
- """
- Wraps the dialog for setting up a Tool
- """
-
- filename = find_resource("ui/configure_tool.ui")
-
- _dialog = None
-
- def run(self, tool):
- """
- Runs the dialog and returns the updated Tool or None on abort
- """
- dialog = self._get_dialog()
-
- self._tool = tool
-
- # load Tool
- self._entry_label.set_text(tool.label)
- self._entry_description.set_text(tool.description)
-
- self._store_job.clear()
- for job in tool.jobs:
- self._store_job.append([job.command_template, job.must_succeed, job.post_processor.name])
-
- self._store_extension.clear()
- for ext in tool.extensions:
- self._store_extension.append([ext])
-
- if tool.accelerator is None:
- self._radio_user_accel.set_active(False)
- self._entry_accel.set_text("")
- else:
- self._radio_user_accel.set_active(True)
- self._entry_accel.set_text(tool.accelerator)
-
- if dialog.run() == 1:
- #
- # okay clicked - update the Tool object
- #
- tool.label = self._entry_label.get_text()
- tool.description = self._entry_description.get_text()
-
- tool.jobs = []
- for row in self._store_job:
- pp_class = self._tool_preferences.POST_PROCESSORS[row[2]]
- tool.jobs.append(Job(row[0], row[1], pp_class))
-
- tool.extensions = []
- for row in self._store_extension:
- tool.extensions.append(row[0])
-
- # TODO: validate accelerator!
- if self._radio_user_accel.get_active():
- tool.accelerator = self._entry_accel.get_text()
- else:
- tool.accelerator = None
-
- return tool
- else:
- return None
-
- def _get_dialog(self):
- if not self._dialog:
- #
- # build the dialog
- #
- self._preferences = Preferences()
-
- self._dialog = self.find_widget("dialogConfigureTool")
- self._button_okay = self.find_widget("buttonOkay")
- self._labelProfileValidate = self.find_widget("labelHint")
-
- #
- # label
- #
- self._entry_label = self.find_widget("entryLabel")
-
- #
- # jobs
- #
- self._entry_new_job = self.find_widget("entryNewJob")
- self._button_add_job = self.find_widget("buttonAddJob")
- self._button_remove_job = self.find_widget("buttonRemoveJob")
- self._button_job_up = self.find_widget("buttonMoveUpJob")
- self._view_job = self.find_widget("treeviewJob")
-
- self._store_job = Gtk.ListStore(str, bool, str) # command, mustSucceed, postProcessor
-
- self._view_job.set_model(self._store_job)
-
- mustSucceedRenderer = Gtk.CellRendererToggle()
- mustSucceedRenderer.connect("toggled", self._on_must_succeed_toggled)
-
- commandRenderer = Gtk.CellRendererText()
- commandRenderer.connect("edited", self._on_job_command_edited)
-
- self._store_pp = Gtk.ListStore(str)
- for p in self._tool_preferences.POST_PROCESSORS.iterkeys():
- self._store_pp.append([p])
-
- ppRenderer = Gtk.CellRendererCombo()
- ppRenderer.set_property("editable", True)
- ppRenderer.set_property("model", self._store_pp)
- ppRenderer.set_property("text_column", 0)
- ppRenderer.set_property("has_entry", False)
-
- ppRenderer.connect("edited", self._on_job_pp_edited)
-
- _insert_column_with_attributes(self._view_job, -1, "Command", commandRenderer, text=0, editable=True)
- _insert_column_with_attributes(self._view_job, -1, "Must Succeed", mustSucceedRenderer, active=1)
- _insert_column_with_attributes(self._view_job, -1, "Post-Processor", ppRenderer, text=2)
-
- #
- # description
- #
- self._entry_description = self.find_widget("entryDescription")
-
- #
- # extensions
- #
- self._entry_new_extension = self.find_widget("entryNewExtension")
-
- self._store_extension = Gtk.ListStore(str)
-
- self._view_extension = self.find_widget("treeviewExtension")
- self._view_extension.set_model(self._store_extension)
- _insert_column_with_attributes(self._view_extension, -1, "", Gtk.CellRendererText(), text=0)
- self._view_extension.set_headers_visible(False)
-
- self._button_add_extension = self.find_widget("buttonAddExtension")
- self._button_remove_extension = self.find_widget("buttonRemoveExtension")
-
- self._radio_user_accel = self.find_widget("radioAccelUser")
- self._entry_accel = self.find_widget("entryAccel")
-
- self.connect_signals({ "on_entryNewJob_changed" : self._on_new_job_changed,
- "on_entryNewExtension_changed" : self._on_new_extension_changed,
- "on_buttonAddJob_clicked" : self._on_add_job_clicked,
- "on_buttonRemoveJob_clicked" : self._on_remove_job_clicked,
- "on_treeviewJob_cursor_changed" : self._on_job_cursor_changed,
- "on_treeviewExtension_cursor_changed" : self._on_extension_cursor_changed,
- "on_buttonAbort_clicked" : self._on_abort_clicked,
- "on_buttonOkay_clicked" : self._on_okay_clicked,
- "on_buttonRemoveExtension_clicked" : self._on_remove_extension_clicked,
- "on_buttonAddExtension_clicked" : self._on_add_extension_clicked,
- "on_buttonMoveUpJob_clicked" : self._on_move_up_job_clicked,
- "on_radioAccelUser_toggled" : self._on_accel_user_toggled })
-
- return self._dialog
-
- def _on_accel_user_toggled(self, togglebutton):
- enabled = togglebutton.get_active()
- self._entry_accel.set_sensitive(enabled)
-
- def _on_move_up_job_clicked(self, button):
- store, iter = self._view_job.get_selection().get_selected()
- store.swap(iter)
-
- def _on_add_extension_clicked(self, button):
- extension = self._entry_new_extension.get_text()
- self._store_extension.append([extension])
-
- def _on_remove_extension_clicked(self, button):
- store, it = self._view_extension.get_selection().get_selected()
- store.remove(it)
-
- def _on_job_command_edited(self, renderer, path, text):
- """
- The command template has been edited
- """
- self._store_job.set(self._store_job.get_iter_from_string(path), 0, text)
-
- def _on_job_pp_edited(self, renderer, path, text):
- """
- Another post processor has been selected
- """
- self._store_job.set(self._store_job.get_iter_from_string(path), 2, text)
-
- def _on_must_succeed_toggled(self, renderer, path):
- """
- The 'must succeed' flag has been toggled
- """
- value = self._store_job.get(self._store_job.get_iter_from_string(path), 1)[0]
- self._store_job.set(self._store_job.get_iter_from_string(path), 1, not value)
-
- def _on_add_job_clicked(self, button):
- """
- Add a new job
- """
- command = self._entry_new_job.get_text()
- self._store_job.append([command, True, "GenericPostProcessor"])
-
- def _on_remove_job_clicked(self, button):
- store, it = self._view_job.get_selection().get_selected()
- store.remove(it)
-
- def _on_new_job_changed(self, widget):
- """
- The entry for a new command template has changed
- """
- self._button_add_job.set_sensitive(len(self._entry_new_job.get_text()) > 0)
-
- def _on_new_extension_changed(self, widget):
- self._button_add_extension.set_sensitive(len(self._entry_new_extension.get_text()) > 0)
-
- def _on_job_cursor_changed(self, tree_view):
- store, iter = tree_view.get_selection().get_selected()
- if not iter:
- return
- self._button_remove_job.set_sensitive(True)
-
- first_row_selected = (store.get_string_from_iter(iter) == "0")
- self._button_job_up.set_sensitive(not first_row_selected)
-
- def _on_extension_cursor_changed(self, tree_view):
- store, it = tree_view.get_selection().get_selected()
- if not it:
- return
- self._button_remove_extension.set_sensitive(True)
-
- def _on_abort_clicked(self, button):
- self._dialog.hide()
-
- def _on_okay_clicked(self, button):
- self._dialog.hide()
-
- def _validate_tool(self):
- """
- Validate the dialog contents
- """
- errors = []
-
- if len(self._store_job) == 0:
- errors.append("You have not specified any jobs.")
-
- if len(errors):
- self._buttonApply.set_sensitive(False)
- else:
- self._buttonApply.set_sensitive(True)
-
- if len(errors) == 1:
- self._labelProfileValidate.set_markup(errors[0])
- elif len(errors) > 1:
- self._labelProfileValidate.set_markup("\n".join([" * %s" % e for e in errors]))
- else:
- self._labelProfileValidate.set_markup("Remember to run all commands in batch mode (e.g. append <tt>-interaction batchmode</tt> to <tt>latex</tt>)")
-
+ """
+ Wraps the dialog for setting up a Tool
+ """
+
+ filename = find_resource("ui/configure_tool.ui")
+
+ _dialog = None
+
+ def run(self, tool):
+ """
+ Runs the dialog and returns the updated Tool or None on abort
+ """
+ dialog = self._get_dialog()
+
+ self._tool = tool
+
+ # load Tool
+ self._entry_label.set_text(tool.label)
+ self._entry_description.set_text(tool.description)
+
+ self._store_job.clear()
+ for job in tool.jobs:
+ self._store_job.append([job.command_template, job.must_succeed, job.post_processor.name])
+
+ self._store_extension.clear()
+ for ext in tool.extensions:
+ self._store_extension.append([ext])
+
+ if tool.accelerator is None:
+ self._radio_user_accel.set_active(False)
+ self._entry_accel.set_text("")
+ else:
+ self._radio_user_accel.set_active(True)
+ self._entry_accel.set_text(tool.accelerator)
+
+ if dialog.run() == 1:
+ #
+ # okay clicked - update the Tool object
+ #
+ tool.label = self._entry_label.get_text()
+ tool.description = self._entry_description.get_text()
+
+ tool.jobs = []
+ for row in self._store_job:
+ pp_class = self._tool_preferences.POST_PROCESSORS[row[2]]
+ tool.jobs.append(Job(row[0], row[1], pp_class))
+
+ tool.extensions = []
+ for row in self._store_extension:
+ tool.extensions.append(row[0])
+
+ # TODO: validate accelerator!
+ if self._radio_user_accel.get_active():
+ tool.accelerator = self._entry_accel.get_text()
+ else:
+ tool.accelerator = None
+
+ return tool
+ else:
+ return None
+
+ def _get_dialog(self):
+ if not self._dialog:
+ #
+ # build the dialog
+ #
+ self._preferences = Preferences()
+
+ self._dialog = self.find_widget("dialogConfigureTool")
+ self._button_okay = self.find_widget("buttonOkay")
+ self._labelProfileValidate = self.find_widget("labelHint")
+
+ #
+ # label
+ #
+ self._entry_label = self.find_widget("entryLabel")
+
+ #
+ # jobs
+ #
+ self._entry_new_job = self.find_widget("entryNewJob")
+ self._button_add_job = self.find_widget("buttonAddJob")
+ self._button_remove_job = self.find_widget("buttonRemoveJob")
+ self._button_job_up = self.find_widget("buttonMoveUpJob")
+ self._view_job = self.find_widget("treeviewJob")
+
+ self._store_job = Gtk.ListStore(str, bool, str) # command, mustSucceed, postProcessor
+
+ self._view_job.set_model(self._store_job)
+
+ mustSucceedRenderer = Gtk.CellRendererToggle()
+ mustSucceedRenderer.connect("toggled", self._on_must_succeed_toggled)
+
+ commandRenderer = Gtk.CellRendererText()
+ commandRenderer.connect("edited", self._on_job_command_edited)
+
+ self._store_pp = Gtk.ListStore(str)
+ for p in self._tool_preferences.POST_PROCESSORS.iterkeys():
+ self._store_pp.append([p])
+
+ ppRenderer = Gtk.CellRendererCombo()
+ ppRenderer.set_property("editable", True)
+ ppRenderer.set_property("model", self._store_pp)
+ ppRenderer.set_property("text_column", 0)
+ ppRenderer.set_property("has_entry", False)
+
+ ppRenderer.connect("edited", self._on_job_pp_edited)
+
+ _insert_column_with_attributes(self._view_job, -1, "Command", commandRenderer, text=0, editable=True)
+ _insert_column_with_attributes(self._view_job, -1, "Must Succeed", mustSucceedRenderer, active=1)
+ _insert_column_with_attributes(self._view_job, -1, "Post-Processor", ppRenderer, text=2)
+
+ #
+ # description
+ #
+ self._entry_description = self.find_widget("entryDescription")
+
+ #
+ # extensions
+ #
+ self._entry_new_extension = self.find_widget("entryNewExtension")
+
+ self._store_extension = Gtk.ListStore(str)
+
+ self._view_extension = self.find_widget("treeviewExtension")
+ self._view_extension.set_model(self._store_extension)
+ _insert_column_with_attributes(self._view_extension, -1, "", Gtk.CellRendererText(), text=0)
+ self._view_extension.set_headers_visible(False)
+
+ self._button_add_extension = self.find_widget("buttonAddExtension")
+ self._button_remove_extension = self.find_widget("buttonRemoveExtension")
+
+ self._radio_user_accel = self.find_widget("radioAccelUser")
+ self._entry_accel = self.find_widget("entryAccel")
+
+ self.connect_signals({ "on_entryNewJob_changed" : self._on_new_job_changed,
+ "on_entryNewExtension_changed" : self._on_new_extension_changed,
+ "on_buttonAddJob_clicked" : self._on_add_job_clicked,
+ "on_buttonRemoveJob_clicked" : self._on_remove_job_clicked,
+ "on_treeviewJob_cursor_changed" : self._on_job_cursor_changed,
+ "on_treeviewExtension_cursor_changed" : self._on_extension_cursor_changed,
+ "on_buttonAbort_clicked" : self._on_abort_clicked,
+ "on_buttonOkay_clicked" : self._on_okay_clicked,
+ "on_buttonRemoveExtension_clicked" : self._on_remove_extension_clicked,
+ "on_buttonAddExtension_clicked" : self._on_add_extension_clicked,
+ "on_buttonMoveUpJob_clicked" : self._on_move_up_job_clicked,
+ "on_radioAccelUser_toggled" : self._on_accel_user_toggled })
+
+ return self._dialog
+
+ def _on_accel_user_toggled(self, togglebutton):
+ enabled = togglebutton.get_active()
+ self._entry_accel.set_sensitive(enabled)
+
+ def _on_move_up_job_clicked(self, button):
+ store, iter = self._view_job.get_selection().get_selected()
+ store.swap(iter)
+
+ def _on_add_extension_clicked(self, button):
+ extension = self._entry_new_extension.get_text()
+ self._store_extension.append([extension])
+
+ def _on_remove_extension_clicked(self, button):
+ store, it = self._view_extension.get_selection().get_selected()
+ store.remove(it)
+
+ def _on_job_command_edited(self, renderer, path, text):
+ """
+ The command template has been edited
+ """
+ self._store_job.set(self._store_job.get_iter_from_string(path), 0, text)
+
+ def _on_job_pp_edited(self, renderer, path, text):
+ """
+ Another post processor has been selected
+ """
+ self._store_job.set(self._store_job.get_iter_from_string(path), 2, text)
+
+ def _on_must_succeed_toggled(self, renderer, path):
+ """
+ The 'must succeed' flag has been toggled
+ """
+ value = self._store_job.get(self._store_job.get_iter_from_string(path), 1)[0]
+ self._store_job.set(self._store_job.get_iter_from_string(path), 1, not value)
+
+ def _on_add_job_clicked(self, button):
+ """
+ Add a new job
+ """
+ command = self._entry_new_job.get_text()
+ self._store_job.append([command, True, "GenericPostProcessor"])
+
+ def _on_remove_job_clicked(self, button):
+ store, it = self._view_job.get_selection().get_selected()
+ store.remove(it)
+
+ def _on_new_job_changed(self, widget):
+ """
+ The entry for a new command template has changed
+ """
+ self._button_add_job.set_sensitive(len(self._entry_new_job.get_text()) > 0)
+
+ def _on_new_extension_changed(self, widget):
+ self._button_add_extension.set_sensitive(len(self._entry_new_extension.get_text()) > 0)
+
+ def _on_job_cursor_changed(self, tree_view):
+ store, iter = tree_view.get_selection().get_selected()
+ if not iter:
+ return
+ self._button_remove_job.set_sensitive(True)
+
+ first_row_selected = (store.get_string_from_iter(iter) == "0")
+ self._button_job_up.set_sensitive(not first_row_selected)
+
+ def _on_extension_cursor_changed(self, tree_view):
+ store, it = tree_view.get_selection().get_selected()
+ if not it:
+ return
+ self._button_remove_extension.set_sensitive(True)
+
+ def _on_abort_clicked(self, button):
+ self._dialog.hide()
+
+ def _on_okay_clicked(self, button):
+ self._dialog.hide()
+
+ def _validate_tool(self):
+ """
+ Validate the dialog contents
+ """
+ errors = []
+
+ if len(self._store_job) == 0:
+ errors.append("You have not specified any jobs.")
+
+ if len(errors):
+ self._buttonApply.set_sensitive(False)
+ else:
+ self._buttonApply.set_sensitive(True)
+
+ if len(errors) == 1:
+ self._labelProfileValidate.set_markup(errors[0])
+ elif len(errors) > 1:
+ self._labelProfileValidate.set_markup("\n".join([" * %s" % e for e in errors]))
+ else:
+ self._labelProfileValidate.set_markup("Remember to run all commands in batch mode (e.g. append <tt>-interaction batchmode</tt> to <tt>latex</tt>)")
+
class PreferencesDialog(GladeInterface):
- """
- This controls the configure dialog
- """
-
- _log = getLogger("PreferencesWizard")
-
- filename = find_resource("ui/configure.ui")
- _dialog = None
-
- @property
- def dialog(self):
- if not self._dialog:
- self._preferences = Preferences()
-
- self._dialog = self.find_widget("notebook1")
-
- #
- # recent bibliographies
- #
- self._storeBibs = Gtk.ListStore(str)
-
-# for bib in self._preferences.bibliographies:
-# self._storeBibs.append([bib.filename])
-
- self._viewBibs = self.find_widget("treeviewBibs")
- self._viewBibs.set_model(self._storeBibs)
- _insert_column_with_attributes(self._viewBibs, -1, "Filename", Gtk.CellRendererText(), text=0)
-
- #
- # tools
- #
- self._tool_preferences = ToolPreferences()
-
- # grab widgets
-
- self._button_delete_tool = self.find_widget("buttonDeleteTool")
- self._button_move_up_tool = self.find_widget("buttonMoveUpTool")
- self._button_configure_tool = self.find_widget("buttonConfigureTool")
-
- self._store_tool = Gtk.ListStore(str, str, object) # label markup, extensions, Tool instance
-
- self._view_tool = self.find_widget("treeviewProfiles")
- self._view_tool.set_model(self._store_tool)
- _insert_column_with_attributes(self._view_tool, -1, "Label", Gtk.CellRendererText(), markup=0)
- _insert_column_with_attributes(self._view_tool, -1, "File Extensions", Gtk.CellRendererText(), text=1)
-
- # init tool and listen to tool changes
- self._load_tools()
- self._tool_preferences.connect("tools-changed", self._on_tools_changed)
-
-
- # misc
- check_hide_box = self.find_widget("checkHideBox")
- check_hide_box.set_active(self._preferences.get_bool("hide-box-warnings"))
-
-
- check_show_toolbar = self.find_widget("checkShowToolbar")
- check_show_toolbar.set_active(self._preferences.get_bool("show-latex-toolbar"))
-
-
- filechooser_tmp = self.find_widget("filechooserTemplates")
- filechooser_tmp.set_filename(self._preferences.TEMPLATE_DIR)
-
-
- #
- # proxies for SpinButtons
- #
- self._proxies = [PreferencesSpinButtonProxy(self.find_widget("spinMaxBibSize"), "maximum-bibtex-size")]
-
- #
- # signals
- #
- self.connect_signals({ "on_buttonClose_clicked" : self._on_close_clicked,
- "on_treeviewProfiles_cursor_changed" : self._on_tool_cursor_changed,
- "on_buttonAddBib_clicked" : self._on_add_bib_clicked,
- "on_buttonRemoveBib_clicked" : self._on_delete_bib_clicked,
- "on_buttonNewProfile_clicked" : self._on_new_tool_clicked,
- "on_buttonMoveUpTool_clicked" : self._on_tool_up_clicked,
- "on_buttonMoveDownTool_clicked" : self._on_tool_down_clicked,
- "on_buttonConfigureTool_clicked" : self._on_configure_tool_clicked,
- "on_buttonDeleteTool_clicked" : self._on_delete_tool_clicked,
- "on_checkHideBox_toggled" : self._on_hide_box_toggled,
- "on_filechooserTemplates_selection_changed" : self._on_templates_dir_changed,
- "on_checkShowToolbar_toggled" : self._on_show_toolbar_toggled })
-
- return self._dialog
-
- def _on_show_toolbar_toggled(self, togglebutton):
- value = togglebutton.get_active()
- self._preferences.set("show-latex-toolbar", value)
-
- def _on_templates_dir_changed(self, filechooser):
- folder = filechooser.get_filename()
- if folder is None:
- return
-
- def _on_hide_box_toggled(self, togglebutton):
- value = togglebutton.get_active()
- self._preferences.set("hide-box-warnings", value)
-
- def _on_tools_changed(self, tools):
- self._load_tools()
-
- def _load_tools(self):
- # save cursor
- store, iter = self._view_tool.get_selection().get_selected()
- if iter is None:
- restore_cursor = False
- else:
- path = store.get_string_from_iter(iter)
- restore_cursor = True
-
- # reload tools
- self._store_tool.clear()
- for tool in self._tool_preferences.tools:
- self._store_tool.append(["<b>%s</b>" % tool.label, ", ".join(tool.extensions), tool])
-
- # restore cursor
- if restore_cursor:
- self._view_tool.set_cursor(path)
-
- def _on_configure_tool_clicked(self, button):
- store, it = self._view_tool.get_selection().get_selected()
- tool = store.get_value(it, 2)
-
- dialog = ConfigureToolDialog()
-
- if not dialog.run(tool) is None:
- self._preferences.save_or_update_tool(tool)
-
- def _on_delete_tool_clicked(self, button):
- store, it = self._view_tool.get_selection().get_selected()
- tool = store.get_value(it, 2)
-
- self._preferences.delete_tool(tool)
-
- def _on_tool_up_clicked(self, button):
- """
- Move up the selected tool
- """
- store, iter = self._view_tool.get_selection().get_selected()
- tool_1 = store.get_value(iter, 2)
-
- index_previous = int(store.get_string_from_iter(iter)) - 1
- assert index_previous >= 0
-
- iter_previous = store.get_iter_from_string(str(index_previous))
- tool_2 = store.get_value(iter_previous, 2)
-
- self._preferences.swap_tools(tool_1, tool_2)
-
- # adjust selection
- self._view_tool.get_selection().select_path(str(index_previous))
-
- def _on_tool_down_clicked(self, button):
- """
- Move down the selected tool
- """
- store, iter = self._view_tool.get_selection().get_selected()
- tool_1 = store.get_value(iter, 2)
-
- index_next = int(store.get_string_from_iter(iter)) + 1
- assert index_next < len(store)
-
- iter_next = store.get_iter_from_string(str(index_next))
- tool_2 = store.get_value(iter_next, 2)
-
- # swap tools in preferences
- self._tool_preferences.swap_tools(tool_1, tool_2)
-
- # adjust selection
- self._view_tool.get_selection().select_path(str(index_next))
-
- def _on_new_tool_clicked(self, button):
- dialog = ConfigureToolDialog()
-
- tool = Tool("New Tool", [], "", [".tex"])
-
- if not dialog.run(tool) is None:
- self._tool_preferences.save_or_update_tool(tool)
-
- def _on_delete_bib_clicked(self, button):
- pass
-
- def _on_add_bib_clicked(self, button):
- pass
-
- def _on_close_clicked(self, button):
- self._dialog.hide()
-
- def _on_tool_cursor_changed(self, treeView):
- """
- The cursor in the tools view has changed
- """
- store, it = treeView.get_selection().get_selected()
- if not it:
- return
-
- self._profile = store.get_value(it, 1)
-
- self._button_delete_tool.set_sensitive(True)
- self._button_move_up_tool.set_sensitive(True)
- self._button_configure_tool.set_sensitive(True)
-
-
-
-
-
+ """
+ This controls the configure dialog
+ """
+
+ _log = getLogger("PreferencesWizard")
+
+ filename = find_resource("ui/configure.ui")
+ _dialog = None
+
+ @property
+ def dialog(self):
+ if not self._dialog:
+ self._preferences = Preferences()
+
+ self._dialog = self.find_widget("notebook1")
+
+ #
+ # recent bibliographies
+ #
+ self._storeBibs = Gtk.ListStore(str)
+
+# for bib in self._preferences.bibliographies:
+# self._storeBibs.append([bib.filename])
+
+ self._viewBibs = self.find_widget("treeviewBibs")
+ self._viewBibs.set_model(self._storeBibs)
+ _insert_column_with_attributes(self._viewBibs, -1, "Filename", Gtk.CellRendererText(), text=0)
+
+ #
+ # tools
+ #
+ self._tool_preferences = ToolPreferences()
+
+ # grab widgets
+
+ self._button_delete_tool = self.find_widget("buttonDeleteTool")
+ self._button_move_up_tool = self.find_widget("buttonMoveUpTool")
+ self._button_configure_tool = self.find_widget("buttonConfigureTool")
+
+ self._store_tool = Gtk.ListStore(str, str, object) # label markup, extensions, Tool instance
+
+ self._view_tool = self.find_widget("treeviewProfiles")
+ self._view_tool.set_model(self._store_tool)
+ _insert_column_with_attributes(self._view_tool, -1, "Label", Gtk.CellRendererText(), markup=0)
+ _insert_column_with_attributes(self._view_tool, -1, "File Extensions", Gtk.CellRendererText(), text=1)
+
+ # init tool and listen to tool changes
+ self._load_tools()
+ self._tool_preferences.connect("tools-changed", self._on_tools_changed)
+
+
+ # misc
+ check_hide_box = self.find_widget("checkHideBox")
+ check_hide_box.set_active(self._preferences.get_bool("hide-box-warnings"))
+
+
+ check_show_toolbar = self.find_widget("checkShowToolbar")
+ check_show_toolbar.set_active(self._preferences.get_bool("show-latex-toolbar"))
+
+
+ filechooser_tmp = self.find_widget("filechooserTemplates")
+ filechooser_tmp.set_filename(self._preferences.TEMPLATE_DIR)
+
+
+ #
+ # proxies for SpinButtons
+ #
+ self._proxies = [PreferencesSpinButtonProxy(self.find_widget("spinMaxBibSize"), "maximum-bibtex-size")]
+
+ #
+ # signals
+ #
+ self.connect_signals({ "on_buttonClose_clicked" : self._on_close_clicked,
+ "on_treeviewProfiles_cursor_changed" : self._on_tool_cursor_changed,
+ "on_buttonAddBib_clicked" : self._on_add_bib_clicked,
+ "on_buttonRemoveBib_clicked" : self._on_delete_bib_clicked,
+ "on_buttonNewProfile_clicked" : self._on_new_tool_clicked,
+ "on_buttonMoveUpTool_clicked" : self._on_tool_up_clicked,
+ "on_buttonMoveDownTool_clicked" : self._on_tool_down_clicked,
+ "on_buttonConfigureTool_clicked" : self._on_configure_tool_clicked,
+ "on_buttonDeleteTool_clicked" : self._on_delete_tool_clicked,
+ "on_checkHideBox_toggled" : self._on_hide_box_toggled,
+ "on_filechooserTemplates_selection_changed" : self._on_templates_dir_changed,
+ "on_checkShowToolbar_toggled" : self._on_show_toolbar_toggled })
+
+ return self._dialog
+
+ def _on_show_toolbar_toggled(self, togglebutton):
+ value = togglebutton.get_active()
+ self._preferences.set("show-latex-toolbar", value)
+
+ def _on_templates_dir_changed(self, filechooser):
+ folder = filechooser.get_filename()
+ if folder is None:
+ return
+
+ def _on_hide_box_toggled(self, togglebutton):
+ value = togglebutton.get_active()
+ self._preferences.set("hide-box-warnings", value)
+
+ def _on_tools_changed(self, tools):
+ self._load_tools()
+
+ def _load_tools(self):
+ # save cursor
+ store, iter = self._view_tool.get_selection().get_selected()
+ if iter is None:
+ restore_cursor = False
+ else:
+ path = store.get_string_from_iter(iter)
+ restore_cursor = True
+
+ # reload tools
+ self._store_tool.clear()
+ for tool in self._tool_preferences.tools:
+ self._store_tool.append(["<b>%s</b>" % tool.label, ", ".join(tool.extensions), tool])
+
+ # restore cursor
+ if restore_cursor:
+ self._view_tool.set_cursor(path)
+
+ def _on_configure_tool_clicked(self, button):
+ store, it = self._view_tool.get_selection().get_selected()
+ tool = store.get_value(it, 2)
+
+ dialog = ConfigureToolDialog()
+
+ if not dialog.run(tool) is None:
+ self._preferences.save_or_update_tool(tool)
+
+ def _on_delete_tool_clicked(self, button):
+ store, it = self._view_tool.get_selection().get_selected()
+ tool = store.get_value(it, 2)
+
+ self._preferences.delete_tool(tool)
+
+ def _on_tool_up_clicked(self, button):
+ """
+ Move up the selected tool
+ """
+ store, iter = self._view_tool.get_selection().get_selected()
+ tool_1 = store.get_value(iter, 2)
+
+ index_previous = int(store.get_string_from_iter(iter)) - 1
+ assert index_previous >= 0
+
+ iter_previous = store.get_iter_from_string(str(index_previous))
+ tool_2 = store.get_value(iter_previous, 2)
+
+ self._preferences.swap_tools(tool_1, tool_2)
+
+ # adjust selection
+ self._view_tool.get_selection().select_path(str(index_previous))
+
+ def _on_tool_down_clicked(self, button):
+ """
+ Move down the selected tool
+ """
+ store, iter = self._view_tool.get_selection().get_selected()
+ tool_1 = store.get_value(iter, 2)
+
+ index_next = int(store.get_string_from_iter(iter)) + 1
+ assert index_next < len(store)
+
+ iter_next = store.get_iter_from_string(str(index_next))
+ tool_2 = store.get_value(iter_next, 2)
+
+ # swap tools in preferences
+ self._tool_preferences.swap_tools(tool_1, tool_2)
+
+ # adjust selection
+ self._view_tool.get_selection().select_path(str(index_next))
+
+ def _on_new_tool_clicked(self, button):
+ dialog = ConfigureToolDialog()
+
+ tool = Tool("New Tool", [], "", [".tex"])
+
+ if not dialog.run(tool) is None:
+ self._tool_preferences.save_or_update_tool(tool)
+
+ def _on_delete_bib_clicked(self, button):
+ pass
+
+ def _on_add_bib_clicked(self, button):
+ pass
+
+ def _on_close_clicked(self, button):
+ self._dialog.hide()
+
+ def _on_tool_cursor_changed(self, treeView):
+ """
+ The cursor in the tools view has changed
+ """
+ store, it = treeView.get_selection().get_selected()
+ if not it:
+ return
+
+ self._profile = store.get_value(it, 1)
+
+ self._button_delete_tool.set_sensitive(True)
+ self._button_move_up_tool.set_sensitive(True)
+ self._button_configure_tool.set_sensitive(True)
+
+
+
+
+
diff --git a/latex/preferences/tools.py b/latex/preferences/tools.py
index 3192d46..091273b 100644
--- a/latex/preferences/tools.py
+++ b/latex/preferences/tools.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -30,205 +30,205 @@ from ..tools.postprocess import GenericPostProcessor, RubberPostProcessor, LaTeX
from ..util import singleton
def str_to_bool(x):
- """
- Converts a string to a boolean value
- """
- if type(x) is bool:
- return x
- elif type(x) is str or type(x) is unicode:
- try:
- return {"false" : False, "0" : False, "true" : True, "1" : True}[x.strip().lower()]
- except KeyError:
- print "str_to_bool: unsupported value %s" % x
- else:
- print "str_to_bool: unsupported type %s" % str(type(x))
+ """
+ Converts a string to a boolean value
+ """
+ if type(x) is bool:
+ return x
+ elif type(x) is str or type(x) is unicode:
+ try:
+ return {"false" : False, "0" : False, "true" : True, "1" : True}[x.strip().lower()]
+ except KeyError:
+ print "str_to_bool: unsupported value %s" % x
+ else:
+ print "str_to_bool: unsupported type %s" % str(type(x))
@singleton
class ToolPreferences(GObject.GObject):
- __gsignals__ = {
- "tools-changed": (
- GObject.SignalFlags.RUN_LAST, None, []),
- }
-
- _log = getLogger("ToolPreferences")
-
- # maps names to classes
- POST_PROCESSORS = {"GenericPostProcessor" : GenericPostProcessor,
- "LaTeXPostProcessor" : LaTeXPostProcessor,
- "RubberPostProcessor" : RubberPostProcessor}
-
- def __init__(self):
- GObject.GObject.__init__(self)
- self.__tool_objects = None
- self.__tool_ids = None
- self.__tools_changed = False
- self.__tools = ElementTree.parse(
- find_resource("tools.xml", MODE_READWRITE)).getroot()
- self._log.debug("Constructed")
-
- def __notify_tools_changed(self):
- self.emit("tools-changed")
-
- @property
- def tools(self):
- """
- Return all Tools
- """
- self.__tool_ids = {}
-
- tools = []
-
- for tool_element in self.__tools.findall("tool"):
- jobs = []
- for job_element in tool_element.findall("job"):
- jobs.append(Job(job_element.text.strip(), str_to_bool(job_element.get("mustSucceed")), self.POST_PROCESSORS[job_element.get("postProcessor")]))
-
- assert not tool_element.get("extensions") is None
-
- extensions = tool_element.get("extensions").split()
- accelerator = tool_element.get("accelerator")
- id = tool_element.get("id")
- tool = Tool(tool_element.get("label"), jobs, tool_element.get("description"), accelerator, extensions)
- self.__tool_ids[tool] = id
-
- tools.append(tool)
-
- return tools
-
- def __find_tool_element(self, id):
- """
- Find the tool element with the given id
- """
- for element in self.__tools.findall("tool"):
- if element.get("id") == id:
- return element
- self._log.warning("<tool id='%s'> not found" % id)
- return None
-
- def save_or_update_tool(self, tool):
- """
- Save or update the XML subtree for the given Tool
-
- @param tool: a Tool object
- """
- tool_element = None
- if tool in self.__tool_ids:
- # find tool tag
- self._log.debug("Tool element found, updating...")
-
- id = self.__tool_ids[tool]
- tool_element = self.__find_tool_element(id)
- else:
- # create new tool tag
- self._log.debug("Creating new Tool...")
-
- id = str(uuid4())
- self.__tool_ids[tool] = id
-
- tool_element = ElementTree.SubElement(self.__tools, "tool")
- tool_element.set("id", id)
-
- tool_element.set("label", tool.label)
- tool_element.set("description", tool.description)
- tool_element.set("extensions", " ".join(tool.extensions))
- if tool.accelerator is None:
- if "accelerator" in tool_element.attrib.keys():
- del tool_element.attrib["accelerator"]
- else:
- tool_element.set("accelerator", tool.accelerator)
-
- # remove all jobs
- for job_element in tool_element.findall("job"):
- tool_element.remove(job_element)
-
- # append new jobs
- for job in tool.jobs:
- job_element = ElementTree.SubElement(tool_element, "job")
- job_element.set("mustSucceed", str(job.must_succeed))
- job_element.set("postProcessor", job.post_processor.name)
- job_element.text = job.command_template
-
- self.__tools_changed = True
- self.__notify_tools_changed()
-
- def swap_tools(self, tool_1, tool_2):
- """
- Swap the order of two Tools
- """
- # grab their ids
- id_1 = self.__tool_ids[tool_1]
- id_2 = self.__tool_ids[tool_2]
-
- if id_1 == id_2:
- self._log.warning("Two tools have the same id. Please modify tools.xml to have unique id's.")
- return
-
- self._log.debug("Tool IDs are {%s: %s, %s, %s}" % (tool_1.label, id_1, tool_2.label, id_2))
-
- tool_element_1 = None
- tool_element_2 = None
-
- # find the XML elements and current indexes of the tools
- i = 0
- for tool_element in self.__tools:
- if tool_element.get("id") == id_1:
- tool_element_1 = tool_element
- index_1 = i
- elif tool_element.get("id") == id_2:
- tool_element_2 = tool_element
- index_2 = i
-
- if not (tool_element_1 is None or tool_element_2 is None):
- break
-
- i += 1
-
- self._log.debug("Found XML elements, indexes are {%s: %s, %s, %s}" % (tool_1.label, index_1, tool_2.label, index_2))
-
- # successively replace each of them by the other in the XML model
- self.__tools.remove(tool_element_1)
- self.__tools.insert(index_1, tool_element_2)
-
- self._log.debug("Replaced first tool by second in list")
-
- self.__tools.remove(tool_element_2)
- self.__tools.insert(index_2, tool_element_1)
-
- self._log.debug("Replaced second tool by first in list")
-
- # notify changes
- self.__tools_changed = True
- self.__notify_tools_changed()
-
- def delete_tool(self, tool):
- """
- Delete the given Tool
-
- @param tool: a Tool object
- """
- try:
- id = self.__tool_ids[tool]
- tool_element = self.__find_tool_element(id)
- self.__tools.remove(tool_element)
-
- del self.__tool_ids[tool]
-
- self.__tools_changed = True
- except KeyError, e:
- self._log.error("delete_tool: %s" % e)
-
- self.__notify_tools_changed()
-
- def save(self):
- """
- Save the preferences to XML
- """
- if self.__tools_changed:
- self._log.debug("Saving tools...")
-
- tree = ElementTree.ElementTree(self.__tools)
- tree.write(find_resource("tools.xml", MODE_READWRITE), encoding="utf-8")
-
- self.__tools_changed = False
-
+ __gsignals__ = {
+ "tools-changed": (
+ GObject.SignalFlags.RUN_LAST, None, []),
+ }
+
+ _log = getLogger("ToolPreferences")
+
+ # maps names to classes
+ POST_PROCESSORS = {"GenericPostProcessor" : GenericPostProcessor,
+ "LaTeXPostProcessor" : LaTeXPostProcessor,
+ "RubberPostProcessor" : RubberPostProcessor}
+
+ def __init__(self):
+ GObject.GObject.__init__(self)
+ self.__tool_objects = None
+ self.__tool_ids = None
+ self.__tools_changed = False
+ self.__tools = ElementTree.parse(
+ find_resource("tools.xml", MODE_READWRITE)).getroot()
+ self._log.debug("Constructed")
+
+ def __notify_tools_changed(self):
+ self.emit("tools-changed")
+
+ @property
+ def tools(self):
+ """
+ Return all Tools
+ """
+ self.__tool_ids = {}
+
+ tools = []
+
+ for tool_element in self.__tools.findall("tool"):
+ jobs = []
+ for job_element in tool_element.findall("job"):
+ jobs.append(Job(job_element.text.strip(), str_to_bool(job_element.get("mustSucceed")), self.POST_PROCESSORS[job_element.get("postProcessor")]))
+
+ assert not tool_element.get("extensions") is None
+
+ extensions = tool_element.get("extensions").split()
+ accelerator = tool_element.get("accelerator")
+ id = tool_element.get("id")
+ tool = Tool(tool_element.get("label"), jobs, tool_element.get("description"), accelerator, extensions)
+ self.__tool_ids[tool] = id
+
+ tools.append(tool)
+
+ return tools
+
+ def __find_tool_element(self, id):
+ """
+ Find the tool element with the given id
+ """
+ for element in self.__tools.findall("tool"):
+ if element.get("id") == id:
+ return element
+ self._log.warning("<tool id='%s'> not found" % id)
+ return None
+
+ def save_or_update_tool(self, tool):
+ """
+ Save or update the XML subtree for the given Tool
+
+ @param tool: a Tool object
+ """
+ tool_element = None
+ if tool in self.__tool_ids:
+ # find tool tag
+ self._log.debug("Tool element found, updating...")
+
+ id = self.__tool_ids[tool]
+ tool_element = self.__find_tool_element(id)
+ else:
+ # create new tool tag
+ self._log.debug("Creating new Tool...")
+
+ id = str(uuid4())
+ self.__tool_ids[tool] = id
+
+ tool_element = ElementTree.SubElement(self.__tools, "tool")
+ tool_element.set("id", id)
+
+ tool_element.set("label", tool.label)
+ tool_element.set("description", tool.description)
+ tool_element.set("extensions", " ".join(tool.extensions))
+ if tool.accelerator is None:
+ if "accelerator" in tool_element.attrib.keys():
+ del tool_element.attrib["accelerator"]
+ else:
+ tool_element.set("accelerator", tool.accelerator)
+
+ # remove all jobs
+ for job_element in tool_element.findall("job"):
+ tool_element.remove(job_element)
+
+ # append new jobs
+ for job in tool.jobs:
+ job_element = ElementTree.SubElement(tool_element, "job")
+ job_element.set("mustSucceed", str(job.must_succeed))
+ job_element.set("postProcessor", job.post_processor.name)
+ job_element.text = job.command_template
+
+ self.__tools_changed = True
+ self.__notify_tools_changed()
+
+ def swap_tools(self, tool_1, tool_2):
+ """
+ Swap the order of two Tools
+ """
+ # grab their ids
+ id_1 = self.__tool_ids[tool_1]
+ id_2 = self.__tool_ids[tool_2]
+
+ if id_1 == id_2:
+ self._log.warning("Two tools have the same id. Please modify tools.xml to have unique id's.")
+ return
+
+ self._log.debug("Tool IDs are {%s: %s, %s, %s}" % (tool_1.label, id_1, tool_2.label, id_2))
+
+ tool_element_1 = None
+ tool_element_2 = None
+
+ # find the XML elements and current indexes of the tools
+ i = 0
+ for tool_element in self.__tools:
+ if tool_element.get("id") == id_1:
+ tool_element_1 = tool_element
+ index_1 = i
+ elif tool_element.get("id") == id_2:
+ tool_element_2 = tool_element
+ index_2 = i
+
+ if not (tool_element_1 is None or tool_element_2 is None):
+ break
+
+ i += 1
+
+ self._log.debug("Found XML elements, indexes are {%s: %s, %s, %s}" % (tool_1.label, index_1, tool_2.label, index_2))
+
+ # successively replace each of them by the other in the XML model
+ self.__tools.remove(tool_element_1)
+ self.__tools.insert(index_1, tool_element_2)
+
+ self._log.debug("Replaced first tool by second in list")
+
+ self.__tools.remove(tool_element_2)
+ self.__tools.insert(index_2, tool_element_1)
+
+ self._log.debug("Replaced second tool by first in list")
+
+ # notify changes
+ self.__tools_changed = True
+ self.__notify_tools_changed()
+
+ def delete_tool(self, tool):
+ """
+ Delete the given Tool
+
+ @param tool: a Tool object
+ """
+ try:
+ id = self.__tool_ids[tool]
+ tool_element = self.__find_tool_element(id)
+ self.__tools.remove(tool_element)
+
+ del self.__tool_ids[tool]
+
+ self.__tools_changed = True
+ except KeyError, e:
+ self._log.error("delete_tool: %s" % e)
+
+ self.__notify_tools_changed()
+
+ def save(self):
+ """
+ Save the preferences to XML
+ """
+ if self.__tools_changed:
+ self._log.debug("Saving tools...")
+
+ tree = ElementTree.ElementTree(self.__tools)
+ tree.write(find_resource("tools.xml", MODE_READWRITE), encoding="utf-8")
+
+ self.__tools_changed = False
+
diff --git a/latex/relpath.py b/latex/relpath.py
index 5aedd2f..9bb4767 100644
--- a/latex/relpath.py
+++ b/latex/relpath.py
@@ -3,15 +3,15 @@
# http://www.koders.com/python/fid663562CE0A76349E3DA5EE4E0747B1B733A510AD.aspx
#----------------------------------------------------------------------
-# Name: relpath.py
+# Name: relpath.py
# Purpose:
#
-# Author: Riaan Booysen
+# Author: Riaan Booysen
#
-# Created: 1999
-# RCS-ID: $Id: relpath.py,v 1.9 2006/10/09 15:14:32 riaan Exp $
+# Created: 1999
+# RCS-ID: $Id: relpath.py,v 1.9 2006/10/09 15:14:32 riaan Exp $
# Copyright: (c) 1999 - 2003 Riaan Booysen
-# Licence: GPL
+# Licence: GPL
#----------------------------------------------------------------------
##b = 'c:\\a\\b\\f\\d'
@@ -22,68 +22,68 @@
import os
def splitpath(apath):
- """ Splits a path into a list of directory names """
- path_list = []
- drive, apath = os.path.splitdrive(apath)
- head, tail = os.path.split(apath)
- while 1:
- if tail:
- path_list.insert(0, tail)
- newhead, tail = os.path.split(head)
- if newhead == head:
- break
- else:
- head = newhead
- if drive:
- path_list.insert(0, drive)
- return path_list
+ """ Splits a path into a list of directory names """
+ path_list = []
+ drive, apath = os.path.splitdrive(apath)
+ head, tail = os.path.split(apath)
+ while 1:
+ if tail:
+ path_list.insert(0, tail)
+ newhead, tail = os.path.split(head)
+ if newhead == head:
+ break
+ else:
+ head = newhead
+ if drive:
+ path_list.insert(0, drive)
+ return path_list
def relpath(base, comp):
- """ Return a path to file comp relative to path base. """
- protsplitbase = base.split('://')
- if len(protsplitbase) == 1:
- baseprot, nbase = 'file', protsplitbase[0]
- elif len(protsplitbase) == 2:
- baseprot, nbase = protsplitbase
- elif len(protsplitbase) == 3:
- baseprot, nbase, zipentry = protsplitbase
- else:
- raise Exception, 'Unhandled path %s'%`protsplitbase`
+ """ Return a path to file comp relative to path base. """
+ protsplitbase = base.split('://')
+ if len(protsplitbase) == 1:
+ baseprot, nbase = 'file', protsplitbase[0]
+ elif len(protsplitbase) == 2:
+ baseprot, nbase = protsplitbase
+ elif len(protsplitbase) == 3:
+ baseprot, nbase, zipentry = protsplitbase
+ else:
+ raise Exception, 'Unhandled path %s'%`protsplitbase`
- protsplitcomp = comp.split('://')
- if len(protsplitcomp) == 1:
- compprot, ncomp = 'file', protsplitcomp[0]
- elif len(protsplitcomp) == 2:
- compprot, ncomp = protsplitcomp
- elif len(protsplitcomp) == 3:
- compprot, ncomp, zipentry = protsplitcomp
- else:
- raise Exception, 'Unhandled path %s'%`protsplitcomp`
+ protsplitcomp = comp.split('://')
+ if len(protsplitcomp) == 1:
+ compprot, ncomp = 'file', protsplitcomp[0]
+ elif len(protsplitcomp) == 2:
+ compprot, ncomp = protsplitcomp
+ elif len(protsplitcomp) == 3:
+ compprot, ncomp, zipentry = protsplitcomp
+ else:
+ raise Exception, 'Unhandled path %s'%`protsplitcomp`
- if baseprot != compprot:
- return comp
+ if baseprot != compprot:
+ return comp
- base_drive, base_path = os.path.splitdrive(nbase)
- comp_drive, comp_path = os.path.splitdrive(ncomp)
- base_path_list = splitpath(base_path)
- comp_path_list = splitpath(comp_path)
+ base_drive, base_path = os.path.splitdrive(nbase)
+ comp_drive, comp_path = os.path.splitdrive(ncomp)
+ base_path_list = splitpath(base_path)
+ comp_path_list = splitpath(comp_path)
- if base_drive != comp_drive:
- return comp
+ if base_drive != comp_drive:
+ return comp
- # relative path defaults to the list of files with
- # a greater index then the entire base
- rel_path = comp_path_list[len(base_path_list):]
- # find the first directory for which the 2 paths differ
- found = -1
- idx = 0
- for idx in range(len(base_path_list)):
- if base_path_list[idx].lower() != comp_path_list[idx].lower():
- rel_path = comp_path_list[idx:]
- found = 0
- break
- for cnt in range(max(len(base_path_list) - idx + found, 0)):
- rel_path.insert(0, os.pardir)
+ # relative path defaults to the list of files with
+ # a greater index then the entire base
+ rel_path = comp_path_list[len(base_path_list):]
+ # find the first directory for which the 2 paths differ
+ found = -1
+ idx = 0
+ for idx in range(len(base_path_list)):
+ if base_path_list[idx].lower() != comp_path_list[idx].lower():
+ rel_path = comp_path_list[idx:]
+ found = 0
+ break
+ for cnt in range(max(len(base_path_list) - idx + found, 0)):
+ rel_path.insert(0, os.pardir)
- return os.path.join(*rel_path)
+ return os.path.join(*rel_path)
diff --git a/latex/tools/__init__.py b/latex/tools/__init__.py
index 45708bc..1b5f43d 100644
--- a/latex/tools/__init__.py
+++ b/latex/tools/__init__.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -29,109 +29,109 @@ from logging import getLogger
class Tool(object):
- """
- The model of a tool. This is to be stored in preferences.
- """
- def __init__(self, label, jobs, description, accelerator, extensions=[]):
- """
- @param label: a label used when displaying the Tool in the UI
- @param jobs: a list of Job objects
- @param description: a descriptive string used as tooltip
- @param accelerator: a key combination for activating this tool
- @param extensions: a list of file extensions for which this Tool can be used
- """
- self.label = label
- self.jobs = jobs
- self.description = description
- self.extensions = extensions
- self.accelerator = accelerator
-
- def __str__(self):
- return "Tool{%s}" % self.label
-
-
+ """
+ The model of a tool. This is to be stored in preferences.
+ """
+ def __init__(self, label, jobs, description, accelerator, extensions=[]):
+ """
+ @param label: a label used when displaying the Tool in the UI
+ @param jobs: a list of Job objects
+ @param description: a descriptive string used as tooltip
+ @param accelerator: a key combination for activating this tool
+ @param extensions: a list of file extensions for which this Tool can be used
+ """
+ self.label = label
+ self.jobs = jobs
+ self.description = description
+ self.extensions = extensions
+ self.accelerator = accelerator
+
+ def __str__(self):
+ return "Tool{%s}" % self.label
+
+
class Job(object):
- """
- A Job is one command to be executed in a Tool
-
- Command templates may contain the following placeholders:
-
- * $filename : the full filename of the processed file
- * $directory : the parent directory of the processed file
- * $shortname : the filename of the processed file without extension ('/dir/doc' for '/dir/doc.tex')
- """
- def __init__(self, command_template, must_succeed, post_processor):
- """
- Construct a Job
-
- @param command_template: a template string for the command to be executed
- @param must_succeed: if True this Job may cause the whole Tool to fail
- @param post_processor: a class implementing IPostProcessor
- """
- self._command_template = command_template
- self._must_succeed = must_succeed
- self._post_processor = post_processor
-
- @property
- def command_template(self):
- return self._command_template
-
- @property
- def must_succeed(self):
- return self._must_succeed
-
- @property
- def post_processor(self):
- return self._post_processor
+ """
+ A Job is one command to be executed in a Tool
+
+ Command templates may contain the following placeholders:
+
+ * $filename : the full filename of the processed file
+ * $directory : the parent directory of the processed file
+ * $shortname : the filename of the processed file without extension ('/dir/doc' for '/dir/doc.tex')
+ """
+ def __init__(self, command_template, must_succeed, post_processor):
+ """
+ Construct a Job
+
+ @param command_template: a template string for the command to be executed
+ @param must_succeed: if True this Job may cause the whole Tool to fail
+ @param post_processor: a class implementing IPostProcessor
+ """
+ self._command_template = command_template
+ self._must_succeed = must_succeed
+ self._post_processor = post_processor
+
+ @property
+ def command_template(self):
+ return self._command_template
+
+ @property
+ def must_succeed(self):
+ return self._must_succeed
+
+ @property
+ def post_processor(self):
+ return self._post_processor
from gi.repository import Gtk
from ..base import Action
-
+
class ToolAction(Action):
- """
- This hooks Tools in the UI. A ToolAction is instantiated for each registered Tool.
- """
-
- _log = getLogger("ToolAction")
-
- def __init__(self, tool):
- self._tool = tool
- self._runner = ToolRunner()
-
- @property
- def label(self):
- return self._tool.label
-
- @property
- def stock_id(self):
- return Gtk.STOCK_EXECUTE
-
- @property
- def accelerator(self):
- return None
-
- @property
- def tooltip(self):
- return self._tool.description
-
- def activate(self, context):
- self._log.debug("activate: %s" % self._tool)
-
- tool_view = context.find_view(None, "ToolView")
-
- if context.active_editor:
- from ..preferences import Preferences # FIXME: circ dep
- # FIXME: find better way to save
- context._window_decorator.save_file()
-
- self._runner.run(context.active_editor.file, self._tool, tool_view)
- self._log.debug("activate: " + str(context.active_editor.file))
- else:
- self._log.error("No active editor")
-
+ """
+ This hooks Tools in the UI. A ToolAction is instantiated for each registered Tool.
+ """
+
+ _log = getLogger("ToolAction")
+
+ def __init__(self, tool):
+ self._tool = tool
+ self._runner = ToolRunner()
+
+ @property
+ def label(self):
+ return self._tool.label
+
+ @property
+ def stock_id(self):
+ return Gtk.STOCK_EXECUTE
+
+ @property
+ def accelerator(self):
+ return None
+
+ @property
+ def tooltip(self):
+ return self._tool.description
+
+ def activate(self, context):
+ self._log.debug("activate: %s" % self._tool)
+
+ tool_view = context.find_view(None, "ToolView")
+
+ if context.active_editor:
+ from ..preferences import Preferences # FIXME: circ dep
+ # FIXME: find better way to save
+ context._window_decorator.save_file()
+
+ self._runner.run(context.active_editor.file, self._tool, tool_view)
+ self._log.debug("activate: " + str(context.active_editor.file))
+ else:
+ self._log.error("No active editor")
+
from os import chdir
from util import Process
@@ -141,131 +141,131 @@ from ..base.resources import PLUGIN_PATH
class ToolRunner(Process):
- """
- This runs a Tool in a subprocess
- """
-
- _log = getLogger("ToolRunner")
-
- def run(self, file, tool, issue_handler):
- """
- @param file: a File object
- @param tool: a Tool object
- @param issue_handler: an object implementing IStructuredIssueHandler
- """
- self._file = file
- self._stdout_text = ""
- self._stderr_text = ""
- self._job_iter = iter(tool.jobs)
-
- # init the IStructuredIssueHandler
- self._issue_handler = issue_handler
- self._issue_handler.clear()
- self._root_issue_partition = self._issue_handler.add_partition("<b>%s</b>" % tool.label, "running", None)
- self._issue_partitions = {}
- for job in tool.jobs:
- self._issue_partitions[job] = self._issue_handler.add_partition(job.command_template, "running", self._root_issue_partition)
-
- # change working directory to prevent issues with relative paths
- chdir(file.dirname)
-
- # enable abort
- self._issue_handler.set_abort_enabled(True, self.abort)
-
- # run
- self.__proceed()
-
- def __proceed(self):
- try:
- self._job = self._job_iter.next()
-
- command_template = Template(self._job.command_template)
- command = command_template.safe_substitute({"filename" : self._file.path,
- "shortname" : self._file.shortname,
- "directory" : self._file.dirname,
- "plugin_path" : PLUGIN_PATH})
-
- self._issue_handler.set_partition_state(self._issue_partitions[self._job], "running")
-
- self.execute(command)
- except StopIteration:
- # Tool finished successfully
- self._issue_handler.set_partition_state(self._root_issue_partition, "succeeded")
- # disable abort
-
- # FIXME: CRASHES
- # self._issue_handler.set_abort_enabled(False, None)
-
- self._on_tool_succeeded()
-
- def _on_stdout(self, text):
- """
- """
- self._log.error("_stdout: " + text)
- self._stdout_text += text
-
- def _on_stderr(self, text):
- """
- """
- self._log.debug("_stderr: " + text)
- self._stderr_text += text
-
- def _on_abort(self):
- """
- """
- self._log.debug("_abort")
- # disable abort
- self._issue_handler.set_abort_enabled(False, None)
- # mark Tool and all Jobs as aborted
- self._issue_handler.set_partition_state(self._root_issue_partition, "aborted")
- self._issue_handler.set_partition_state(self._issue_partitions[self._job], "aborted")
- for job in self._job_iter:
- self._issue_handler.set_partition_state(self._issue_partitions[job], "aborted")
-
- def _on_exit(self, condition):
- """
- """
- self._log.debug("_exit")
-
- assert self._job
-
- # create post-processor instance
- post_processor = self._job.post_processor()
-
- # run post-processor
- post_processor.process(self._file, self._stdout_text, self._stderr_text, condition)
-
- # show issues
- self._issue_handler.append_issues(self._issue_partitions[self._job], post_processor.issues)
-
- if post_processor.successful:
- self._issue_handler.set_partition_state(self._issue_partitions[self._job], "succeeded")
- self.__proceed()
- else:
- self._issue_handler.set_partition_state(self._issue_partitions[self._job], "failed")
- if self._job.must_succeed:
- # whole Tool failed
- self._issue_handler.set_partition_state(self._root_issue_partition, "failed")
- # disable abort
- self._issue_handler.set_abort_enabled(False, None)
-
- self._on_tool_failed()
- else:
- self.__proceed()
-
- def _on_tool_succeeded(self):
- """
- The Tool has finished successfully
-
- To be overridden by subclass
- """
-
- def _on_tool_failed(self):
- """
- The Tool has failed
-
- To be overridden by subclass
- """
-
+ """
+ This runs a Tool in a subprocess
+ """
+
+ _log = getLogger("ToolRunner")
+
+ def run(self, file, tool, issue_handler):
+ """
+ @param file: a File object
+ @param tool: a Tool object
+ @param issue_handler: an object implementing IStructuredIssueHandler
+ """
+ self._file = file
+ self._stdout_text = ""
+ self._stderr_text = ""
+ self._job_iter = iter(tool.jobs)
+
+ # init the IStructuredIssueHandler
+ self._issue_handler = issue_handler
+ self._issue_handler.clear()
+ self._root_issue_partition = self._issue_handler.add_partition("<b>%s</b>" % tool.label, "running", None)
+ self._issue_partitions = {}
+ for job in tool.jobs:
+ self._issue_partitions[job] = self._issue_handler.add_partition(job.command_template, "running", self._root_issue_partition)
+
+ # change working directory to prevent issues with relative paths
+ chdir(file.dirname)
+
+ # enable abort
+ self._issue_handler.set_abort_enabled(True, self.abort)
+
+ # run
+ self.__proceed()
+
+ def __proceed(self):
+ try:
+ self._job = self._job_iter.next()
+
+ command_template = Template(self._job.command_template)
+ command = command_template.safe_substitute({"filename" : self._file.path,
+ "shortname" : self._file.shortname,
+ "directory" : self._file.dirname,
+ "plugin_path" : PLUGIN_PATH})
+
+ self._issue_handler.set_partition_state(self._issue_partitions[self._job], "running")
+
+ self.execute(command)
+ except StopIteration:
+ # Tool finished successfully
+ self._issue_handler.set_partition_state(self._root_issue_partition, "succeeded")
+ # disable abort
+
+ # FIXME: CRASHES
+ # self._issue_handler.set_abort_enabled(False, None)
+
+ self._on_tool_succeeded()
+
+ def _on_stdout(self, text):
+ """
+ """
+ self._log.error("_stdout: " + text)
+ self._stdout_text += text
+
+ def _on_stderr(self, text):
+ """
+ """
+ self._log.debug("_stderr: " + text)
+ self._stderr_text += text
+
+ def _on_abort(self):
+ """
+ """
+ self._log.debug("_abort")
+ # disable abort
+ self._issue_handler.set_abort_enabled(False, None)
+ # mark Tool and all Jobs as aborted
+ self._issue_handler.set_partition_state(self._root_issue_partition, "aborted")
+ self._issue_handler.set_partition_state(self._issue_partitions[self._job], "aborted")
+ for job in self._job_iter:
+ self._issue_handler.set_partition_state(self._issue_partitions[job], "aborted")
+
+ def _on_exit(self, condition):
+ """
+ """
+ self._log.debug("_exit")
+
+ assert self._job
+
+ # create post-processor instance
+ post_processor = self._job.post_processor()
+
+ # run post-processor
+ post_processor.process(self._file, self._stdout_text, self._stderr_text, condition)
+
+ # show issues
+ self._issue_handler.append_issues(self._issue_partitions[self._job], post_processor.issues)
+
+ if post_processor.successful:
+ self._issue_handler.set_partition_state(self._issue_partitions[self._job], "succeeded")
+ self.__proceed()
+ else:
+ self._issue_handler.set_partition_state(self._issue_partitions[self._job], "failed")
+ if self._job.must_succeed:
+ # whole Tool failed
+ self._issue_handler.set_partition_state(self._root_issue_partition, "failed")
+ # disable abort
+ self._issue_handler.set_abort_enabled(False, None)
+
+ self._on_tool_failed()
+ else:
+ self.__proceed()
+
+ def _on_tool_succeeded(self):
+ """
+ The Tool has finished successfully
+
+ To be overridden by subclass
+ """
+
+ def _on_tool_failed(self):
+ """
+ The Tool has failed
+
+ To be overridden by subclass
+ """
+
diff --git a/latex/tools/postprocess.py b/latex/tools/postprocess.py
index 18f0976..33467bd 100644
--- a/latex/tools/postprocess.py
+++ b/latex/tools/postprocess.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -30,198 +30,197 @@ from ..util import escape
class PostProcessor(object):
- """
- The contract for a post-processor
- """
-
- def process(self, file, stdout, stderr, condition):
- """
- @param file: the File processed by the Tool
- @param stdout: the output written to STDOUT
- @param stderr: the output written to STDERR
- @param condition: the exit condition of the Tool process
- """
- raise NotImplementedError
-
- @property
- def successful(self):
- """
- Return whether the Tool process was successful
- """
- raise NotImplementedError
-
- @property
- def issues(self):
- """
- Return a list of Issues
- """
- raise NotImplementedError
-
- @property
- def summary(self):
- """
- Return a short string summarizing the result of the Tool process
- """
- raise NotImplementedError
-
-
+ """
+ The contract for a post-processor
+ """
+
+ def process(self, file, stdout, stderr, condition):
+ """
+ @param file: the File processed by the Tool
+ @param stdout: the output written to STDOUT
+ @param stderr: the output written to STDERR
+ @param condition: the exit condition of the Tool process
+ """
+ raise NotImplementedError
+
+ @property
+ def successful(self):
+ """
+ Return whether the Tool process was successful
+ """
+ raise NotImplementedError
+
+ @property
+ def issues(self):
+ """
+ Return a list of Issues
+ """
+ raise NotImplementedError
+
+ @property
+ def summary(self):
+ """
+ Return a short string summarizing the result of the Tool process
+ """
+ raise NotImplementedError
+
+
class GenericPostProcessor(PostProcessor):
- """
- This just interprets the exit condition of the process
- """
-
- _log = getLogger("GenericPostProcessor")
-
- name = "GenericPostProcessor"
-
- def __init__(self):
- self._issues = None
- self._summary = None
-
- def process(self, file, stdout, stderr, condition):
- self._issues = []
- self._summary = ""
- self._condition = condition
-
- @property
- def successful(self):
- return not bool(self._condition)
-
- @property
- def issues(self):
- return self._issues
-
- @property
- def summary(self):
- return self._summary
-
-
+ """
+ This just interprets the exit condition of the process
+ """
+
+ _log = getLogger("GenericPostProcessor")
+
+ name = "GenericPostProcessor"
+
+ def __init__(self):
+ self._issues = None
+ self._summary = None
+
+ def process(self, file, stdout, stderr, condition):
+ self._issues = []
+ self._summary = ""
+ self._condition = condition
+
+ @property
+ def successful(self):
+ return not bool(self._condition)
+
+ @property
+ def issues(self):
+ return self._issues
+
+ @property
+ def summary(self):
+ return self._summary
+
+
class LaTeXPostProcessor(PostProcessor):
- """
- This post-processor generates messages from a standard LaTeX log with
- default error format (NOT using "-file-line-error")
- """
-
- _log = getLogger("LatexPostProcessor")
-
- name = "LaTeXPostProcessor"
-
- _PATTERN = pattern = re.compile(r"(^! (?P<text>.*?)$)|(^l\.(?P<line>[0-9]+))", re.MULTILINE)
-
- def __init__(self):
- pass
-
- def process(self, file, stdout, stderr, condition):
- self._file = file
-
- @property
- def successful(self):
- return True
-
- @property
- def summary(self):
- return self._summary
-
- @property
- def issues(self):
- try:
- log = open("%s.log" % self._file.shortname).read()
-
- # check for wrong format
- if log.find("file:line:error") > 0:
- return [Issue("The file:line:error format is not supported. Please remove that switch.", None, None, self._file, Issue.SEVERITY_ERROR)]
-
- # process log file and extract tuples like (message, line)
- tuples = []
- for match in self._PATTERN.finditer(log):
- if match.group("text"):
- tuple = [match.group("text"), 0]
- elif match.group("line"):
- tuple[1] = match.group("line")
- tuples.append(tuple)
-
- # generate issues from tuples
- self._issues = []
- for tuple in tuples:
- text = tuple[0]
- line = int(tuple[1]) - 1
- self._issues.append(Issue(text, line, None, self._file, Issue.SEVERITY_ERROR, Issue.POSITION_LINE))
-
- return self._issues
-
- except IOError:
- return [Issue("No LaTeX log file found", None, None, self._file, Issue.SEVERITY_ERROR)]
+ """
+ This post-processor generates messages from a standard LaTeX log with
+ default error format (NOT using "-file-line-error")
+ """
+
+ _log = getLogger("LatexPostProcessor")
+
+ name = "LaTeXPostProcessor"
+
+ _PATTERN = pattern = re.compile(r"(^! (?P<text>.*?)$)|(^l\.(?P<line>[0-9]+))", re.MULTILINE)
+
+ def __init__(self):
+ pass
+
+ def process(self, file, stdout, stderr, condition):
+ self._file = file
+
+ @property
+ def successful(self):
+ return True
+
+ @property
+ def summary(self):
+ return self._summary
+
+ @property
+ def issues(self):
+ try:
+ log = open("%s.log" % self._file.shortname).read()
+
+ # check for wrong format
+ if log.find("file:line:error") > 0:
+ return [Issue("The file:line:error format is not supported. Please remove that switch.", None, None, self._file, Issue.SEVERITY_ERROR)]
+
+ # process log file and extract tuples like (message, line)
+ tuples = []
+ for match in self._PATTERN.finditer(log):
+ if match.group("text"):
+ tuple = [match.group("text"), 0]
+ elif match.group("line"):
+ tuple[1] = match.group("line")
+ tuples.append(tuple)
+
+ # generate issues from tuples
+ self._issues = []
+ for tuple in tuples:
+ text = tuple[0]
+ line = int(tuple[1]) - 1
+ self._issues.append(Issue(text, line, None, self._file, Issue.SEVERITY_ERROR, Issue.POSITION_LINE))
+
+ return self._issues
+
+ except IOError:
+ return [Issue("No LaTeX log file found", None, None, self._file, Issue.SEVERITY_ERROR)]
class RubberPostProcessor(PostProcessor):
- """
- This is a post-processor for rubber
- """
-
- _log = getLogger("RubberPostProcessor")
-
- name = "RubberPostProcessor"
-
- _pattern = re.compile(r"(?P<file>[a-zA-Z0-9./_-]+)(:(?P<line>[0-9\-]+))?:(?P<text>.*?)$", re.MULTILINE)
-
- def __init__(self):
- self._issues = None
- self._summary = None
- self._successful = False
-
- # FIXME: circ dep
- from ..preferences import Preferences
-
- self._hide_box_warnings = Preferences().get_bool("hide-box-warnings")
-
- @property
- def successful(self):
- return self._successful
-
- @property
- def issues(self):
- return self._issues
-
- @property
- def summary(self):
- return self._summary
-
- def process(self, file, stdout, stderr, condition):
- from ..base import File # FIXME: this produces a circ dep on toplevel
-
- self._issues = []
-
- self._log.debug("process(): stdout=\"%s\", stderr=\"%s\"" % (stdout, stderr))
-
- self._successful = not bool(condition)
-
- for match in self._pattern.finditer(stderr):
- severity = Issue.SEVERITY_ERROR
-
- # text
- text = match.group("text")
-
- if "Underfull" in text or "Overfull" in text:
- if self._hide_box_warnings:
- continue
- else:
- severity = Issue.SEVERITY_WARNING
-
- # line(s)
- lineFrom, lineTo = None, None
-
- line = match.group("line")
-
- if line:
- parts = line.split("-")
- lineFrom = int(parts[0]) - 1
- if len(parts) > 1:
- lineTo = int(parts[1]) - 1
-
- # filename
- filename = "%s/%s" % (file.dirname, match.group("file"))
-
- self._issues.append(Issue(escape(text), lineFrom, lineTo, File(filename), severity, Issue.POSITION_LINE))
-
-
-
\ No newline at end of file
+ """
+ This is a post-processor for rubber
+ """
+
+ _log = getLogger("RubberPostProcessor")
+
+ name = "RubberPostProcessor"
+
+ _pattern = re.compile(r"(?P<file>[a-zA-Z0-9./_-]+)(:(?P<line>[0-9\-]+))?:(?P<text>.*?)$", re.MULTILINE)
+
+ def __init__(self):
+ self._issues = None
+ self._summary = None
+ self._successful = False
+
+ # FIXME: circ dep
+ from ..preferences import Preferences
+
+ self._hide_box_warnings = Preferences().get_bool("hide-box-warnings")
+
+ @property
+ def successful(self):
+ return self._successful
+
+ @property
+ def issues(self):
+ return self._issues
+
+ @property
+ def summary(self):
+ return self._summary
+
+ def process(self, file, stdout, stderr, condition):
+ from ..base import File # FIXME: this produces a circ dep on toplevel
+
+ self._issues = []
+
+ self._log.debug("process(): stdout=\"%s\", stderr=\"%s\"" % (stdout, stderr))
+
+ self._successful = not bool(condition)
+
+ for match in self._pattern.finditer(stderr):
+ severity = Issue.SEVERITY_ERROR
+
+ # text
+ text = match.group("text")
+
+ if "Underfull" in text or "Overfull" in text:
+ if self._hide_box_warnings:
+ continue
+ else:
+ severity = Issue.SEVERITY_WARNING
+
+ # line(s)
+ lineFrom, lineTo = None, None
+
+ line = match.group("line")
+
+ if line:
+ parts = line.split("-")
+ lineFrom = int(parts[0]) - 1
+ if len(parts) > 1:
+ lineTo = int(parts[1]) - 1
+
+ # filename
+ filename = "%s/%s" % (file.dirname, match.group("file"))
+
+ self._issues.append(Issue(escape(text), lineFrom, lineTo, File(filename), severity, Issue.POSITION_LINE))
+
+
diff --git a/latex/tools/util.py b/latex/tools/util.py
index 466d233..a923141 100644
--- a/latex/tools/util.py
+++ b/latex/tools/util.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -31,92 +31,91 @@ from gi.repository import GObject
class Process(object):
- """
- This runs a command in a child process and polls the output
- """
-
- __log = getLogger("Process")
-
- # intervall of polling stdout of the child process
- __POLL_INTERVAL = 250
-
- def execute(self, command):
- self.__log.debug("execute: %s" % command)
-
- # run child process
- self.__process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
-
- # unblock pipes
- fcntl.fcntl(self.__process.stdout, fcntl.F_SETFL, os.O_NONBLOCK)
- fcntl.fcntl(self.__process.stderr, fcntl.F_SETFL, os.O_NONBLOCK)
-
- # monitor process and pipes
- self.__handlers = [ GObject.timeout_add(self.__POLL_INTERVAL, self.__on_stdout),
- GObject.timeout_add(self.__POLL_INTERVAL, self.__on_stderr),
- GObject.child_watch_add(self.__process.pid, self.__on_exit) ]
-
- def abort(self):
- """
- Abort the running process
- """
- if self.__process:
- for handler in self.__handlers:
- GObject.source_remove(handler)
-
- try:
- os.kill(self.__process.pid, signal.SIGTERM)
-
- self._on_abort()
- except OSError, e:
- self.__log.error("Failed to abort process: %s" % e)
-
- def __on_stdout(self):
- try:
- s = self.__process.stdout.read()
- if len(s):
- self._on_stdout(s)
- except IOError:
- pass
- return True
-
- def __on_stderr(self):
- try:
- s = self.__process.stderr.read()
- if len(s):
- self._on_stderr(s)
- except IOError:
- pass
- return True
-
- def __on_exit(self, pid, condition):
- for handler in self.__handlers:
- GObject.source_remove(handler)
-
- # read remaining output
- self.__on_stdout()
- self.__on_stderr()
-
- self._on_exit(condition)
-
- def _on_stdout(self, text):
- """
- To be overridden
- """
-
- def _on_stderr(self, text):
- """
- To be overridden
- """
-
- def _on_abort(self):
- """
- To be overridden
- """
-
- def _on_exit(self, condition):
- """
- To be overridden
- """
-
-
\ No newline at end of file
+ """
+ This runs a command in a child process and polls the output
+ """
+
+ __log = getLogger("Process")
+
+ # intervall of polling stdout of the child process
+ __POLL_INTERVAL = 250
+
+ def execute(self, command):
+ self.__log.debug("execute: %s" % command)
+
+ # run child process
+ self.__process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+
+ # unblock pipes
+ fcntl.fcntl(self.__process.stdout, fcntl.F_SETFL, os.O_NONBLOCK)
+ fcntl.fcntl(self.__process.stderr, fcntl.F_SETFL, os.O_NONBLOCK)
+
+ # monitor process and pipes
+ self.__handlers = [ GObject.timeout_add(self.__POLL_INTERVAL, self.__on_stdout),
+ GObject.timeout_add(self.__POLL_INTERVAL, self.__on_stderr),
+ GObject.child_watch_add(self.__process.pid, self.__on_exit) ]
+
+ def abort(self):
+ """
+ Abort the running process
+ """
+ if self.__process:
+ for handler in self.__handlers:
+ GObject.source_remove(handler)
+
+ try:
+ os.kill(self.__process.pid, signal.SIGTERM)
+
+ self._on_abort()
+ except OSError, e:
+ self.__log.error("Failed to abort process: %s" % e)
+
+ def __on_stdout(self):
+ try:
+ s = self.__process.stdout.read()
+ if len(s):
+ self._on_stdout(s)
+ except IOError:
+ pass
+ return True
+
+ def __on_stderr(self):
+ try:
+ s = self.__process.stderr.read()
+ if len(s):
+ self._on_stderr(s)
+ except IOError:
+ pass
+ return True
+
+ def __on_exit(self, pid, condition):
+ for handler in self.__handlers:
+ GObject.source_remove(handler)
+
+ # read remaining output
+ self.__on_stdout()
+ self.__on_stderr()
+
+ self._on_exit(condition)
+
+ def _on_stdout(self, text):
+ """
+ To be overridden
+ """
+
+ def _on_stderr(self, text):
+ """
+ To be overridden
+ """
+
+ def _on_abort(self):
+ """
+ To be overridden
+ """
+
+ def _on_exit(self, condition):
+ """
+ To be overridden
+ """
+
diff --git a/latex/tools/views.py b/latex/tools/views.py
index 9bf5367..2008da3 100755
--- a/latex/tools/views.py
+++ b/latex/tools/views.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -34,167 +34,167 @@ from ..issues import Issue, IStructuredIssueHandler
class ToolView(BottomView, IStructuredIssueHandler):
- """
- """
-
- _log = getLogger("ToolView")
-
- label = "Tools"
- icon = Gtk.Image.new_from_stock(Gtk.STOCK_CONVERT, Gtk.IconSize.MENU)
- scope = View.SCOPE_WINDOW
-
- _ICON_RUN = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/run.png"))
- _ICON_FAIL = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/error.png"))
- _ICON_SUCCESS = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/okay.png"))
- _ICON_ERROR = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/error.png"))
- _ICON_WARNING = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/warning.png"))
- _ICON_ABORT = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/abort.png"))
-
- def __init__(self, context):
- BottomView.__init__(self, context)
- self._handlers = {}
-
- def init(self, context):
- self._log.debug("init")
-
- self._context = context
-
- self._scroll = Gtk.ScrolledWindow()
- self._scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
- self._scroll.set_shadow_type(Gtk.ShadowType.IN)
-
- self._store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str, str, str, object) # icon, message, file, line, Issue object
-
- self._view = Gtk.TreeView(model=self._store)
-
- column = Gtk.TreeViewColumn("Job")
-
- pixbuf_renderer = Gtk.CellRendererPixbuf()
- column.pack_start(pixbuf_renderer, False)
- column.add_attribute(pixbuf_renderer, "pixbuf", 0)
-
- text_renderer = Gtk.CellRendererText()
- column.pack_start(text_renderer, True)
- column.add_attribute(text_renderer, "markup", 1)
-
- self._view.append_column(column)
- self._view.append_column(Gtk.TreeViewColumn("File", Gtk.CellRendererText(), text=2))
- self._view.append_column(Gtk.TreeViewColumn("Line", Gtk.CellRendererText(), text=3))
-
- self._handlers[self._view] = self._view.connect("row-activated", self._on_row_activated)
-
- self._scroll.add(self._view)
-
- self.pack_start(self._scroll, True, True, 0)
-
- # toolbar
-
- self._buttonCancel = Gtk.ToolButton(icon_name=Gtk.STOCK_STOP)
- self._buttonCancel.set_sensitive(False)
- self._buttonCancel.set_tooltip_text("Abort Job")
- self._handlers[self._buttonCancel] = self._buttonCancel.connect("clicked", self._on_abort_clicked)
-
- self._buttonDetails = Gtk.ToolButton(icon_name=Gtk.STOCK_INFO)
- self._buttonDetails.set_sensitive(False)
- self._buttonDetails.set_tooltip_text("Show Detailed Output")
- self._handlers[self._buttonDetails] = self._buttonDetails.connect("clicked", self._on_details_clicked)
-
- self._toolbar = Gtk.Toolbar()
- self._toolbar.set_style(Gtk.ToolbarStyle.ICONS)
- self._toolbar.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR) # FIXME: deprecated???
- self._toolbar.set_orientation(Gtk.Orientation.VERTICAL)
- self._toolbar.insert(self._buttonCancel, -1)
- self._toolbar.insert(self._buttonDetails, -1)
-
- self.pack_start(self._toolbar, False, False, 0)
-
- # theme like gtk3
- ctx = self._scroll.get_style_context()
- ctx.set_junction_sides(Gtk.JunctionSides.RIGHT)
-
- ctx = self._toolbar.get_style_context()
- ctx.set_junction_sides(Gtk.JunctionSides.LEFT | Gtk.JunctionSides.RIGHT)
- ctx.add_class("inline-toolbar")
-
-
- def _on_abort_clicked(self, button):
- self._abort_method.__call__()
-
- def _on_details_clicked(self, button):
- """
- The details button has been clicked
- """
-
- def _on_row_activated(self, view, path, column):
- issue = self._store.get(self._store.get_iter(path), 4)[0]
- if issue:
- self._context.activate_editor(issue.file)
- if self._context.active_editor:
- self._context.active_editor.select_lines(issue.start)
- else:
- self._log.error("No Editor object for calling select_lines")
-
- def clear(self):
- self.assure_init()
- self._store.clear()
-
- def set_abort_enabled(self, enabled, method):
- # see issues.IStructuredIssueHandler.set_abort_enabled
-
- self._abort_method = method
- self._buttonCancel.set_sensitive(enabled)
-
- def add_partition(self, label, state, parent_partition_id=None):
- """
- Add a new partition
-
- @param label: a label used in the UI
- @return: a unique id for the partition (here a Gtk.TreeIter)
- """
- icon = None
- if state == "running":
- icon = self._ICON_RUN
- elif state == "succeeded":
- icon = self._ICON_SUCCESS
- elif state == "failed":
- icon = self._ICON_FAIL
- elif state == "aborted":
- icon = self._ICON_ABORT
-
- self._view.expand_all()
-
- return self._store.append(parent_partition_id, [icon, label, "", "", None])
-
- def set_partition_state(self, partition_id, state):
- # see IStructuredIssueHandler.set_partition_state
-
- icon = None
- if state == "running":
- icon = self._ICON_RUN
- elif state == "succeeded":
- icon = self._ICON_SUCCESS
- elif state == "failed":
- icon = self._ICON_FAIL
- elif state == "aborted":
- icon = self._ICON_ABORT
-
- self._store.set_value(partition_id, 0, icon)
-
- def append_issues(self, partition_id, issues):
- for issue in issues:
- icon = None
- if issue.severity == Issue.SEVERITY_WARNING:
- icon = self._ICON_WARNING
- elif issue.severity == Issue.SEVERITY_ERROR:
- icon = self._ICON_ERROR
- self._store.append(partition_id, [icon, issue.message, issue.file.basename, str(issue.start), issue])
-
- self._log.debug(str(issue))
-
- self._view.expand_all()
-
- def destroy(self):
- for obj in self._handlers:
- obj.disconnect(self._handlers[obj])
- BottomView.destroy(self)
-
+ """
+ """
+
+ _log = getLogger("ToolView")
+
+ label = "Tools"
+ icon = Gtk.Image.new_from_stock(Gtk.STOCK_CONVERT, Gtk.IconSize.MENU)
+ scope = View.SCOPE_WINDOW
+
+ _ICON_RUN = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/run.png"))
+ _ICON_FAIL = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/error.png"))
+ _ICON_SUCCESS = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/okay.png"))
+ _ICON_ERROR = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/error.png"))
+ _ICON_WARNING = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/warning.png"))
+ _ICON_ABORT = GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/abort.png"))
+
+ def __init__(self, context):
+ BottomView.__init__(self, context)
+ self._handlers = {}
+
+ def init(self, context):
+ self._log.debug("init")
+
+ self._context = context
+
+ self._scroll = Gtk.ScrolledWindow()
+ self._scroll.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ self._scroll.set_shadow_type(Gtk.ShadowType.IN)
+
+ self._store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str, str, str, object) # icon, message, file, line, Issue object
+
+ self._view = Gtk.TreeView(model=self._store)
+
+ column = Gtk.TreeViewColumn("Job")
+
+ pixbuf_renderer = Gtk.CellRendererPixbuf()
+ column.pack_start(pixbuf_renderer, False)
+ column.add_attribute(pixbuf_renderer, "pixbuf", 0)
+
+ text_renderer = Gtk.CellRendererText()
+ column.pack_start(text_renderer, True)
+ column.add_attribute(text_renderer, "markup", 1)
+
+ self._view.append_column(column)
+ self._view.append_column(Gtk.TreeViewColumn("File", Gtk.CellRendererText(), text=2))
+ self._view.append_column(Gtk.TreeViewColumn("Line", Gtk.CellRendererText(), text=3))
+
+ self._handlers[self._view] = self._view.connect("row-activated", self._on_row_activated)
+
+ self._scroll.add(self._view)
+
+ self.pack_start(self._scroll, True, True, 0)
+
+ # toolbar
+
+ self._buttonCancel = Gtk.ToolButton(icon_name=Gtk.STOCK_STOP)
+ self._buttonCancel.set_sensitive(False)
+ self._buttonCancel.set_tooltip_text("Abort Job")
+ self._handlers[self._buttonCancel] = self._buttonCancel.connect("clicked", self._on_abort_clicked)
+
+ self._buttonDetails = Gtk.ToolButton(icon_name=Gtk.STOCK_INFO)
+ self._buttonDetails.set_sensitive(False)
+ self._buttonDetails.set_tooltip_text("Show Detailed Output")
+ self._handlers[self._buttonDetails] = self._buttonDetails.connect("clicked", self._on_details_clicked)
+
+ self._toolbar = Gtk.Toolbar()
+ self._toolbar.set_style(Gtk.ToolbarStyle.ICONS)
+ self._toolbar.set_icon_size(Gtk.IconSize.SMALL_TOOLBAR) # FIXME: deprecated???
+ self._toolbar.set_orientation(Gtk.Orientation.VERTICAL)
+ self._toolbar.insert(self._buttonCancel, -1)
+ self._toolbar.insert(self._buttonDetails, -1)
+
+ self.pack_start(self._toolbar, False, False, 0)
+
+ # theme like gtk3
+ ctx = self._scroll.get_style_context()
+ ctx.set_junction_sides(Gtk.JunctionSides.RIGHT)
+
+ ctx = self._toolbar.get_style_context()
+ ctx.set_junction_sides(Gtk.JunctionSides.LEFT | Gtk.JunctionSides.RIGHT)
+ ctx.add_class("inline-toolbar")
+
+
+ def _on_abort_clicked(self, button):
+ self._abort_method.__call__()
+
+ def _on_details_clicked(self, button):
+ """
+ The details button has been clicked
+ """
+
+ def _on_row_activated(self, view, path, column):
+ issue = self._store.get(self._store.get_iter(path), 4)[0]
+ if issue:
+ self._context.activate_editor(issue.file)
+ if self._context.active_editor:
+ self._context.active_editor.select_lines(issue.start)
+ else:
+ self._log.error("No Editor object for calling select_lines")
+
+ def clear(self):
+ self.assure_init()
+ self._store.clear()
+
+ def set_abort_enabled(self, enabled, method):
+ # see issues.IStructuredIssueHandler.set_abort_enabled
+
+ self._abort_method = method
+ self._buttonCancel.set_sensitive(enabled)
+
+ def add_partition(self, label, state, parent_partition_id=None):
+ """
+ Add a new partition
+
+ @param label: a label used in the UI
+ @return: a unique id for the partition (here a Gtk.TreeIter)
+ """
+ icon = None
+ if state == "running":
+ icon = self._ICON_RUN
+ elif state == "succeeded":
+ icon = self._ICON_SUCCESS
+ elif state == "failed":
+ icon = self._ICON_FAIL
+ elif state == "aborted":
+ icon = self._ICON_ABORT
+
+ self._view.expand_all()
+
+ return self._store.append(parent_partition_id, [icon, label, "", "", None])
+
+ def set_partition_state(self, partition_id, state):
+ # see IStructuredIssueHandler.set_partition_state
+
+ icon = None
+ if state == "running":
+ icon = self._ICON_RUN
+ elif state == "succeeded":
+ icon = self._ICON_SUCCESS
+ elif state == "failed":
+ icon = self._ICON_FAIL
+ elif state == "aborted":
+ icon = self._ICON_ABORT
+
+ self._store.set_value(partition_id, 0, icon)
+
+ def append_issues(self, partition_id, issues):
+ for issue in issues:
+ icon = None
+ if issue.severity == Issue.SEVERITY_WARNING:
+ icon = self._ICON_WARNING
+ elif issue.severity == Issue.SEVERITY_ERROR:
+ icon = self._ICON_ERROR
+ self._store.append(partition_id, [icon, issue.message, issue.file.basename, str(issue.start), issue])
+
+ self._log.debug(str(issue))
+
+ self._view.expand_all()
+
+ def destroy(self):
+ for obj in self._handlers:
+ obj.disconnect(self._handlers[obj])
+ BottomView.destroy(self)
+
diff --git a/latex/util.py b/latex/util.py
index 77b8119..51953f9 100644
--- a/latex/util.py
+++ b/latex/util.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -29,65 +29,65 @@ import logging
from gi.repository import GdkPixbuf
def singleton(cls):
- """
- Singleton decorator that works with GObject derived types. The 'recommended'
- python one - http://wiki.python.org/moin/PythonDecoratorLibrary#Singleton
- does not (interacts badly with GObjectMeta
- """
- instances = {}
- def getinstance():
- if cls not in instances:
- instances[cls] = cls()
- return instances[cls]
- return getinstance
+ """
+ Singleton decorator that works with GObject derived types. The 'recommended'
+ python one - http://wiki.python.org/moin/PythonDecoratorLibrary#Singleton
+ does not (interacts badly with GObjectMeta
+ """
+ instances = {}
+ def getinstance():
+ if cls not in instances:
+ instances[cls] = cls()
+ return instances[cls]
+ return getinstance
def require(arg_name, *allowed_types):
- """
- Type-checking decorator (see http://code.activestate.com/recipes/454322/)
-
- Usage:
-
- @require("x", int, float)
- @require("y", float)
- def foo(x, y):
- return x+y
-
- print foo(1, 2.5) # Prints 3.5.
- print foo(2.0, 2.5) # Prints 4.5.
- print foo("asdf", 2.5) # Raises TypeError exception.
- print foo(1, 2) # Raises TypeError exception.
- """
- def make_wrapper(f):
- if hasattr(f, "wrapped_args"):
- wrapped_args = getattr(f, "wrapped_args")
- else:
- code = f.func_code
- wrapped_args = list(code.co_varnames[:code.co_argcount])
-
- try:
- arg_index = wrapped_args.index(arg_name)
- except ValueError:
- raise NameError, arg_name
-
- def wrapper(*args, **kwargs):
- if len(args) > arg_index:
- arg = args[arg_index]
- if not isinstance(arg, allowed_types):
- type_list = " or ".join(str(allowed_type) for allowed_type in allowed_types)
- raise TypeError, "Expected '%s' to be %s; was %s." % (arg_name, type_list, type(arg))
- else:
- if arg_name in kwargs:
- arg = kwargs[arg_name]
- if not isinstance(arg, allowed_types):
- type_list = " or ".join(str(allowed_type) for allowed_type in allowed_types)
- raise TypeError, "Expected '%s' to be %s; was %s." % (arg_name, type_list, type(arg))
-
- return f(*args, **kwargs)
-
- wrapper.wrapped_args = wrapped_args
- return wrapper
-
- return make_wrapper
+ """
+ Type-checking decorator (see http://code.activestate.com/recipes/454322/)
+
+ Usage:
+
+ @require("x", int, float)
+ @require("y", float)
+ def foo(x, y):
+ return x+y
+
+ print foo(1, 2.5) # Prints 3.5.
+ print foo(2.0, 2.5) # Prints 4.5.
+ print foo("asdf", 2.5) # Raises TypeError exception.
+ print foo(1, 2) # Raises TypeError exception.
+ """
+ def make_wrapper(f):
+ if hasattr(f, "wrapped_args"):
+ wrapped_args = getattr(f, "wrapped_args")
+ else:
+ code = f.func_code
+ wrapped_args = list(code.co_varnames[:code.co_argcount])
+
+ try:
+ arg_index = wrapped_args.index(arg_name)
+ except ValueError:
+ raise NameError, arg_name
+
+ def wrapper(*args, **kwargs):
+ if len(args) > arg_index:
+ arg = args[arg_index]
+ if not isinstance(arg, allowed_types):
+ type_list = " or ".join(str(allowed_type) for allowed_type in allowed_types)
+ raise TypeError, "Expected '%s' to be %s; was %s." % (arg_name, type_list, type(arg))
+ else:
+ if arg_name in kwargs:
+ arg = kwargs[arg_name]
+ if not isinstance(arg, allowed_types):
+ type_list = " or ".join(str(allowed_type) for allowed_type in allowed_types)
+ raise TypeError, "Expected '%s' to be %s; was %s." % (arg_name, type_list, type(arg))
+
+ return f(*args, **kwargs)
+
+ wrapper.wrapped_args = wrapped_args
+ return wrapper
+
+ return make_wrapper
from gi.repository import Gtk
@@ -96,111 +96,111 @@ from xml.sax import saxutils
class StringReader(object):
- """
- A simple string reader that is able to push back one character
- """
- def __init__(self, string):
- self._iter = iter(string)
- self.offset = 0
- self._pushbackChar = None
- self._pushbackFlag = False
-
- def read(self):
- if self._pushbackFlag:
- self._pushbackFlag = False
- return self._pushbackChar
- else:
- self.offset += 1
- return self._iter.next()
-
- def unread(self, char):
- #assert not self._pushbackFlag
-
- self._pushbackChar = char
- self._pushbackFlag = True
+ """
+ A simple string reader that is able to push back one character
+ """
+ def __init__(self, string):
+ self._iter = iter(string)
+ self.offset = 0
+ self._pushbackChar = None
+ self._pushbackFlag = False
+
+ def read(self):
+ if self._pushbackFlag:
+ self._pushbackFlag = False
+ return self._pushbackChar
+ else:
+ self.offset += 1
+ return self._iter.next()
+
+ def unread(self, char):
+ #assert not self._pushbackFlag
+
+ self._pushbackChar = char
+ self._pushbackFlag = True
def escape(string, remove_newlines=True):
- """
- Prepares a string for inclusion in Pango markup and error messages
- """
- s = saxutils.escape(string)
- s = s.replace("\n", " ")
- s = s.replace("\"", """)
- return s
-
+ """
+ Prepares a string for inclusion in Pango markup and error messages
+ """
+ s = saxutils.escape(string)
+ s = s.replace("\n", " ")
+ s = s.replace("\"", """)
+ return s
+
def open_error(message, secondary_message=None):
- """
- Popup an error dialog window
- """
- dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT,
- Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message)
- if secondary_message:
- # TODO: why not use markup?
- dialog.format_secondary_text(secondary_message)
- dialog.run()
- dialog.destroy()
+ """
+ Popup an error dialog window
+ """
+ dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, message)
+ if secondary_message:
+ # TODO: why not use markup?
+ dialog.format_secondary_text(secondary_message)
+ dialog.run()
+ dialog.destroy()
def open_info(message, secondary_message=None):
- """
- Popup an info dialog window
- """
- dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT,
- Gtk.MessageType.INFO, Gtk.ButtonsType.OK, message)
- if secondary_message:
- dialog.format_secondary_markup(secondary_message)
- dialog.run()
- dialog.destroy()
-
+ """
+ Popup an info dialog window
+ """
+ dialog = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ Gtk.MessageType.INFO, Gtk.ButtonsType.OK, message)
+ if secondary_message:
+ dialog.format_secondary_markup(secondary_message)
+ dialog.run()
+ dialog.destroy()
+
def verbose(function):
- """
- 'verbose'-decorator. This runs the decorated method in a try-except-block
- and shows an error dialog on exception.
- """
- def decorated_function(*args, **kw):
- try:
- return function(*args, **kw)
- except Exception, e:
- stack = traceback.format_exc(limit=10)
- open_error(str(e), stack)
- return decorated_function
+ """
+ 'verbose'-decorator. This runs the decorated method in a try-except-block
+ and shows an error dialog on exception.
+ """
+ def decorated_function(*args, **kw):
+ try:
+ return function(*args, **kw)
+ except Exception, e:
+ stack = traceback.format_exc(limit=10)
+ open_error(str(e), stack)
+ return decorated_function
class GladeInterface(object):
- """
- Utility base class for interfaces loaded from a Glade definition
- """
-
- __log = logging.getLogger("GladeInterface")
-
- filename = None
-
- def __init__(self):
- self.__tree = None
-
- def __get_tree(self):
- if not self.__tree:
- self.__tree = Gtk.Builder()
- self.__tree.add_from_file(self.filename)
- return self.__tree
-
- def find_widget(self, name):
- """
- Find a widget by its name
- """
- widget = self.__get_tree().get_object(name)
- if widget is None:
- self.__log.error("Widget '%s' could not be found in interface description '%s'" % (name, self.filename))
- return widget
-
- def connect_signals(self, mapping):
- """
- Auto-connect signals
- """
- self.__get_tree().connect_signals(mapping)
+ """
+ Utility base class for interfaces loaded from a Glade definition
+ """
+
+ __log = logging.getLogger("GladeInterface")
+
+ filename = None
+
+ def __init__(self):
+ self.__tree = None
+
+ def __get_tree(self):
+ if not self.__tree:
+ self.__tree = Gtk.Builder()
+ self.__tree.add_from_file(self.filename)
+ return self.__tree
+
+ def find_widget(self, name):
+ """
+ Find a widget by its name
+ """
+ widget = self.__get_tree().get_object(name)
+ if widget is None:
+ self.__log.error("Widget '%s' could not be found in interface description '%s'" % (name, self.filename))
+ return widget
+
+ def connect_signals(self, mapping):
+ """
+ Auto-connect signals
+ """
+ self.__get_tree().connect_signals(mapping)
from uuid import uuid1
@@ -210,44 +210,44 @@ from base import Action
class IconAction(Action):
- """
- A utility class for creating actions with a custom icon instead of
- a gtk stock id.
-
- The subclass must provide a field 'icon'.
- """
-
- __stock_id = None
-
- def __init__(self, *args, **kwargs):
- self.__icon_factory = kwargs["icon_factory"]
-
- @property
- def icon(self):
- """
- Return a File object for the icon to use
- """
- raise NotImplementedError
-
- def __init_stock_id(self):
- #
- # generate a new stock id
- #
- self.__stock_id = str(uuid1())
- self.__icon_factory.add(
- self.__stock_id,
- Gtk.IconSet.new_from_pixbuf(
- GdkPixbuf.Pixbuf.new_from_file(self.icon.path)))
-
- @property
- def stock_id(self):
- if self.icon:
- if not self.__stock_id:
- self.__init_stock_id()
- return self.__stock_id
- else:
- return None
-
-
-
-
+ """
+ A utility class for creating actions with a custom icon instead of
+ a gtk stock id.
+
+ The subclass must provide a field 'icon'.
+ """
+
+ __stock_id = None
+
+ def __init__(self, *args, **kwargs):
+ self.__icon_factory = kwargs["icon_factory"]
+
+ @property
+ def icon(self):
+ """
+ Return a File object for the icon to use
+ """
+ raise NotImplementedError
+
+ def __init_stock_id(self):
+ #
+ # generate a new stock id
+ #
+ self.__stock_id = str(uuid1())
+ self.__icon_factory.add(
+ self.__stock_id,
+ Gtk.IconSet.new_from_pixbuf(
+ GdkPixbuf.Pixbuf.new_from_file(self.icon.path)))
+
+ @property
+ def stock_id(self):
+ if self.icon:
+ if not self.__stock_id:
+ self.__init_stock_id()
+ return self.__stock_id
+ else:
+ return None
+
+
+
+
diff --git a/latex/views.py b/latex/views.py
index 61cafe1..4623d98 100644
--- a/latex/views.py
+++ b/latex/views.py
@@ -11,7 +11,7 @@
#
# 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
+# 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
@@ -33,176 +33,176 @@ from util import escape
class IssueView(BottomView):
- """
- """
-
- _log = getLogger("IssueView")
-
- label = "Issues"
- icon = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.MENU)
- scope = View.SCOPE_EDITOR
-
- def __init__(self, context, editor):
- BottomView.__init__(self, context)
- self._editor = editor
- self._handlers = {}
-
- def init(self, context):
- self._log.debug("init")
-
- self._preferences = Preferences()
- self._preferences.connect("preferences-changed", self._on_preferences_changed)
- self._show_tasks = self._preferences.get_bool("issues-show-tasks")
- self._show_warnings = self._preferences.get_bool("issues-show-warnings")
-
- self._context = context
-
- self._icons = { Issue.SEVERITY_WARNING : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/warning.png")),
- Issue.SEVERITY_ERROR : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/error.png")),
- Issue.SEVERITY_INFO : None,
- Issue.SEVERITY_TASK : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/task.png")) }
-
- self._store = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, object)
-
- self._view = Gtk.TreeView(model=self._store)
-
- column = Gtk.TreeViewColumn()
- column.set_title("Message")
-
- pixbuf_renderer = Gtk.CellRendererPixbuf()
- column.pack_start(pixbuf_renderer, False)
- column.add_attribute(pixbuf_renderer, "pixbuf", 0)
-
- text_renderer = Gtk.CellRendererText()
- column.pack_start(text_renderer, True)
- column.add_attribute(text_renderer, "markup", 1)
-
- self._view.append_column(column)
-
- column = Gtk.TreeViewColumn()
- column.set_title("File")
- text_renderer2 = Gtk.CellRendererText()
- column.pack_start(text_renderer2, True)
- column.add_attribute(text_renderer2, "markup", 2)
- self._view.insert_column(column, -1)
- self._handlers[self._view] = self._view.connect("row-activated", self._on_row_activated)
-
- self._scr = Gtk.ScrolledWindow()
-
- self._scr.add(self._view)
- self._scr.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
- self._scr.set_shadow_type(Gtk.ShadowType.IN)
-
- self.pack_start(self._scr, True, True, 0)
-
- # toolbar
-
- self._button_warnings = Gtk.ToggleToolButton()
- self._button_warnings.set_tooltip_text("Show/Hide Warnings")
- image = Gtk.Image()
- image.set_from_file(find_resource("icons/warning.png"))
- self._button_warnings.set_icon_widget(image)
- self._button_warnings.set_active(self._show_warnings)
- self._handlers[self._button_warnings] = self._button_warnings.connect("toggled", self.__on_warnings_toggled)
-
- self._button_tasks = Gtk.ToggleToolButton()
- self._button_tasks.set_tooltip_text("Show/Hide Tasks")
- imageTask = Gtk.Image()
- imageTask.set_from_file(find_resource("icons/task.png"))
- self._button_tasks.set_icon_widget(imageTask)
- self._button_tasks.set_active(self._show_tasks)
- self._handlers[self._button_tasks] = self._button_tasks.connect("toggled", self.__on_tasks_toggled)
-
- toolbar = Gtk.Toolbar()
- toolbar.set_orientation(Gtk.Orientation.VERTICAL)
- toolbar.set_style(Gtk.ToolbarStyle.ICONS)
- toolbar.set_icon_size(Gtk.IconSize.MENU)
- toolbar.insert(self._button_warnings, -1)
- toolbar.insert(self._button_tasks, -1)
-
- self.pack_start(toolbar, False, False, 0)
-
- # theme like gtk3
- ctx = self._scr.get_style_context()
- ctx.set_junction_sides(Gtk.JunctionSides.RIGHT)
-
- ctx = toolbar.get_style_context()
- ctx.set_junction_sides(Gtk.JunctionSides.LEFT | Gtk.JunctionSides.RIGHT)
- ctx.add_class("inline-toolbar")
-
- self._issues = []
-
- self._log.debug("init finished")
-
- def _on_row_activated(self, view, path, column):
- """
- A row has been double-clicked on
- """
- issue = self._store.get(self._store.get_iter(path), 3)[0]
-
- self._context.activate_editor(issue.file)
-
- #~ # FIXME: this doesn't work correctly
- #~ if not self._context.active_editor is None:
- #~ self._context.active_editor.select(issue.start, issue.end)
- self._editor.select(issue.start, issue.end)
-
- def _on_preferences_changed(self, prefs, key, value):
- if key == "issues-show-warnings" or key == "issues-show-tasks":
- # update filter
- self._store.clear()
- for issue, local in self._issues:
- self.__append_issue_filtered(issue, local)
-
- def __on_tasks_toggled(self, togglebutton):
- self._show_tasks = togglebutton.get_active()
- self._preferences.set("issues-show-tasks", self._show_tasks)
-
- def __on_warnings_toggled(self, togglebutton):
- self._show_warnings = togglebutton.get_active()
- self._preferences.set("issues-show-warnings", self._show_warnings)
-
- def clear(self):
- """
- Remove all issues from the view
- """
- self.assure_init()
- self._store.clear()
- self._issues = []
-
- def append_issue(self, issue, local=True):
- """
- Append a new Issue to the view
-
- @param issue: the Issue object
- @param local: indicates whether the Issue occured in the edited file or not
- """
- self.assure_init()
- self._issues.append((issue, local))
- self.__append_issue_filtered(issue, local)
-
- def __append_issue_filtered(self, issue, local):
- if issue.severity == Issue.SEVERITY_WARNING:
- if self._show_warnings:
- self.__do_append_issue(issue, local)
- elif issue.severity == Issue.SEVERITY_TASK:
- if self._show_tasks:
- self.__do_append_issue(issue, local)
- else:
- self.__do_append_issue(issue, local)
-
- def __do_append_issue(self, issue, local):
- if local:
- message = issue.message
- filename = escape(issue.file.basename)
- else:
- message = "<span color='%s'>%s</span>" % (self._preferences.get("light-foreground-color"), issue.message)
- filename = "<span color='%s'>%s</span>" % (self._preferences.get("light-foreground-color"), issue.file.basename)
- self._store.append([self._icons[issue.severity], message, filename, issue])
-
- def destroy(self):
- del self._editor
- for obj in self._handlers:
- obj.disconnect(self._handlers[obj])
- BottomView.destroy(self)
-
+ """
+ """
+
+ _log = getLogger("IssueView")
+
+ label = "Issues"
+ icon = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_INFO, Gtk.IconSize.MENU)
+ scope = View.SCOPE_EDITOR
+
+ def __init__(self, context, editor):
+ BottomView.__init__(self, context)
+ self._editor = editor
+ self._handlers = {}
+
+ def init(self, context):
+ self._log.debug("init")
+
+ self._preferences = Preferences()
+ self._preferences.connect("preferences-changed", self._on_preferences_changed)
+ self._show_tasks = self._preferences.get_bool("issues-show-tasks")
+ self._show_warnings = self._preferences.get_bool("issues-show-warnings")
+
+ self._context = context
+
+ self._icons = { Issue.SEVERITY_WARNING : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/warning.png")),
+ Issue.SEVERITY_ERROR : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/error.png")),
+ Issue.SEVERITY_INFO : None,
+ Issue.SEVERITY_TASK : GdkPixbuf.Pixbuf.new_from_file(find_resource("icons/task.png")) }
+
+ self._store = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, object)
+
+ self._view = Gtk.TreeView(model=self._store)
+
+ column = Gtk.TreeViewColumn()
+ column.set_title("Message")
+
+ pixbuf_renderer = Gtk.CellRendererPixbuf()
+ column.pack_start(pixbuf_renderer, False)
+ column.add_attribute(pixbuf_renderer, "pixbuf", 0)
+
+ text_renderer = Gtk.CellRendererText()
+ column.pack_start(text_renderer, True)
+ column.add_attribute(text_renderer, "markup", 1)
+
+ self._view.append_column(column)
+
+ column = Gtk.TreeViewColumn()
+ column.set_title("File")
+ text_renderer2 = Gtk.CellRendererText()
+ column.pack_start(text_renderer2, True)
+ column.add_attribute(text_renderer2, "markup", 2)
+ self._view.insert_column(column, -1)
+ self._handlers[self._view] = self._view.connect("row-activated", self._on_row_activated)
+
+ self._scr = Gtk.ScrolledWindow()
+
+ self._scr.add(self._view)
+ self._scr.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ self._scr.set_shadow_type(Gtk.ShadowType.IN)
+
+ self.pack_start(self._scr, True, True, 0)
+
+ # toolbar
+
+ self._button_warnings = Gtk.ToggleToolButton()
+ self._button_warnings.set_tooltip_text("Show/Hide Warnings")
+ image = Gtk.Image()
+ image.set_from_file(find_resource("icons/warning.png"))
+ self._button_warnings.set_icon_widget(image)
+ self._button_warnings.set_active(self._show_warnings)
+ self._handlers[self._button_warnings] = self._button_warnings.connect("toggled", self.__on_warnings_toggled)
+
+ self._button_tasks = Gtk.ToggleToolButton()
+ self._button_tasks.set_tooltip_text("Show/Hide Tasks")
+ imageTask = Gtk.Image()
+ imageTask.set_from_file(find_resource("icons/task.png"))
+ self._button_tasks.set_icon_widget(imageTask)
+ self._button_tasks.set_active(self._show_tasks)
+ self._handlers[self._button_tasks] = self._button_tasks.connect("toggled", self.__on_tasks_toggled)
+
+ toolbar = Gtk.Toolbar()
+ toolbar.set_orientation(Gtk.Orientation.VERTICAL)
+ toolbar.set_style(Gtk.ToolbarStyle.ICONS)
+ toolbar.set_icon_size(Gtk.IconSize.MENU)
+ toolbar.insert(self._button_warnings, -1)
+ toolbar.insert(self._button_tasks, -1)
+
+ self.pack_start(toolbar, False, False, 0)
+
+ # theme like gtk3
+ ctx = self._scr.get_style_context()
+ ctx.set_junction_sides(Gtk.JunctionSides.RIGHT)
+
+ ctx = toolbar.get_style_context()
+ ctx.set_junction_sides(Gtk.JunctionSides.LEFT | Gtk.JunctionSides.RIGHT)
+ ctx.add_class("inline-toolbar")
+
+ self._issues = []
+
+ self._log.debug("init finished")
+
+ def _on_row_activated(self, view, path, column):
+ """
+ A row has been double-clicked on
+ """
+ issue = self._store.get(self._store.get_iter(path), 3)[0]
+
+ self._context.activate_editor(issue.file)
+
+ #~ # FIXME: this doesn't work correctly
+ #~ if not self._context.active_editor is None:
+ #~ self._context.active_editor.select(issue.start, issue.end)
+ self._editor.select(issue.start, issue.end)
+
+ def _on_preferences_changed(self, prefs, key, value):
+ if key == "issues-show-warnings" or key == "issues-show-tasks":
+ # update filter
+ self._store.clear()
+ for issue, local in self._issues:
+ self.__append_issue_filtered(issue, local)
+
+ def __on_tasks_toggled(self, togglebutton):
+ self._show_tasks = togglebutton.get_active()
+ self._preferences.set("issues-show-tasks", self._show_tasks)
+
+ def __on_warnings_toggled(self, togglebutton):
+ self._show_warnings = togglebutton.get_active()
+ self._preferences.set("issues-show-warnings", self._show_warnings)
+
+ def clear(self):
+ """
+ Remove all issues from the view
+ """
+ self.assure_init()
+ self._store.clear()
+ self._issues = []
+
+ def append_issue(self, issue, local=True):
+ """
+ Append a new Issue to the view
+
+ @param issue: the Issue object
+ @param local: indicates whether the Issue occured in the edited file or not
+ """
+ self.assure_init()
+ self._issues.append((issue, local))
+ self.__append_issue_filtered(issue, local)
+
+ def __append_issue_filtered(self, issue, local):
+ if issue.severity == Issue.SEVERITY_WARNING:
+ if self._show_warnings:
+ self.__do_append_issue(issue, local)
+ elif issue.severity == Issue.SEVERITY_TASK:
+ if self._show_tasks:
+ self.__do_append_issue(issue, local)
+ else:
+ self.__do_append_issue(issue, local)
+
+ def __do_append_issue(self, issue, local):
+ if local:
+ message = issue.message
+ filename = escape(issue.file.basename)
+ else:
+ message = "<span color='%s'>%s</span>" % (self._preferences.get("light-foreground-color"), issue.message)
+ filename = "<span color='%s'>%s</span>" % (self._preferences.get("light-foreground-color"), issue.file.basename)
+ self._store.append([self._icons[issue.severity], message, filename, issue])
+
+ def destroy(self):
+ del self._editor
+ for obj in self._handlers:
+ obj.disconnect(self._handlers[obj])
+ BottomView.destroy(self)
+
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]