[gedit-latex] Move editor class to from base/__init__.py to base/editor.py



commit a5597fa471935517d8949aea00ea09f74e8be12a
Author: Ignacio Casal Quinteiro <icq gnome org>
Date:   Thu Jul 7 18:18:09 2011 +0200

    Move editor class to from base/__init__.py to base/editor.py

 latex/base/Makefile.am |    1 +
 latex/base/__init__.py |  590 ----------------------------------------------
 latex/base/editor.py   |  611 ++++++++++++++++++++++++++++++++++++++++++++++++
 latex/bibtex/editor.py |    2 +-
 latex/latex/editor.py  |    3 +-
 5 files changed, 615 insertions(+), 592 deletions(-)
---
diff --git a/latex/base/Makefile.am b/latex/base/Makefile.am
index 0d2d640..f943dfb 100644
--- a/latex/base/Makefile.am
+++ b/latex/base/Makefile.am
@@ -5,6 +5,7 @@ plugin_PYTHON = \
 	completion.py \
 	config.py \
 	decorators.py \
+	editor.py \
 	file.py \
 	__init__.py \
 	job.py \
diff --git a/latex/base/__init__.py b/latex/base/__init__.py
index 26adec5..e1431d6 100644
--- a/latex/base/__init__.py
+++ b/latex/base/__init__.py
@@ -221,596 +221,6 @@ class Proposal(object):
         return cmp(self.label.lower(), other.label.lower())
 
 
-import re
-from uuid import uuid1
-import time
-
-from .completion import CompletionDistributor
-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)
-
-
 class WindowContext(object):
     """
     The WindowContext is passed to Editors and is used to
diff --git a/latex/base/editor.py b/latex/base/editor.py
new file mode 100644
index 0000000..c6fce1d
--- /dev/null
+++ b/latex/base/editor.py
@@ -0,0 +1,611 @@
+# -*- coding: utf-8 -*-
+
+# This file is part of the Gedit LaTeX Plugin
+#
+# Copyright (C) 2010 Michael Zeising
+#
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public Licence as published by the Free Software
+# Foundation; either version 2 of the Licence, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public Licence for more
+# details.
+#
+# You should have received a copy of the GNU General Public Licence along with
+# this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
+# Street, Fifth Floor, Boston, MA  02110-1301, USA
+
+import re
+from uuid import uuid1
+import time
+
+from gi.repository import GObject, Gtk, Gdk
+from .completion import CompletionDistributor
+from .templates import TemplateDelegate
+from logging import getLogger
+
+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)
+
+# ex:ts=4:et:
diff --git a/latex/bibtex/editor.py b/latex/bibtex/editor.py
index fba2ab5..0fe7663 100644
--- a/latex/bibtex/editor.py
+++ b/latex/bibtex/editor.py
@@ -24,7 +24,7 @@ bibtex.editor
 
 from logging import getLogger
 
-from ..base import Editor
+from ..base.editor import Editor
 from ..preferences import Preferences
 from ..issues import Issue, IIssueHandler, MockIssueHandler
 from ..util import verbose
diff --git a/latex/latex/editor.py b/latex/latex/editor.py
index 6a9b9e8..7f7a497 100644
--- a/latex/latex/editor.py
+++ b/latex/latex/editor.py
@@ -30,7 +30,8 @@ from logging import getLogger
 
 if BENCHMARK: import time
 
-from ..base import Editor, File
+from ..base.editor import Editor
+from ..base.file import File
 from completion import LaTeXCompletionHandler
 from ..issues import Issue, IIssueHandler
 from ..util import verbose, open_error



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