# -*- coding: utf-8 -*-

# Copyright (c) 2002, 2003 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing the editor component of the eric3 IDE.
"""

import re
import os
import glob

from qtext import QextScintilla, QextScintillaPrinter, QextScintillaMacro
from qt import *

from Icons.FileIcons import IconFilePython
from Debugger.DebugIcons import IconBreak, IconCBreak, IconBreakDisabled, \
    IconTBreak, IconTCBreak
from Debugger.EditBreakpointDialog import EditBreakpointDialog
from Checks.TabnannyDialog import TabnannyDialog
from Checks.SyntaxCheckerDialog import SyntaxCheckerDialog
from UI.CodeMetricsDialog import CodeMetricsDialog
from UI.PyCoverageDialog import PyCoverageDialog
from UI.PyProfileDialog import PyProfileDialog
from EditorIcons import *
from Icons.Icons import IconClose
from Printer import Printer
import Preferences
import Utilities

class Editor(QextScintilla):
    """
    Class implementing the editor component of the eric3 IDE.
    
    Signals
    
        modificationStatusChanged(boolean, editor) -- emitted when the
            modification status has changed
            
        cursorChanged(string, int, int) -- emitted when the cursor position
            was changed
            
        editorSaved(string) -- emitted after the editor has been saved
            
        captionChanged(string, editor) -- emitted when the caption is
            updated. Typically due to a readOnly attribute change.
            
        breakpointToggled(editor) -- emitted when a breakpoint is toggled
        
        bookmarkToggled(editor) -- emitted when a bookmark is toggled
    """
    def __init__(self,dbs,fn=None,parent=None,name=None,flags=0):
        """
        Constructor
        
        Arguments
        
            dbs -- reference to the debug server object
            
            fn -- name of the file to be opened (string). If it is None,
                a new (empty) editor is opened
            
            parent -- parent widget (viewmanager) of this editor (QWidget)
            
            name -- name of this editor (string or QString)
            
            flags -- window flags
        """
        QextScintilla.__init__(self, parent, name, flags | Qt.WDestructiveClose)
        self.setUtf8(1)
        
        self.dbs = dbs
        self.fileName = fn
        self.vm = parent
        
        # clear some variables
        self.lastHighlight = None   # remember the last highlighted line
        self.lastErrorMarker = None   # remember the last error line
        self.lastCurrMarker = None    # remember the last current line
        self.breaks = {}    # key: marker handle, 
                            # value: (lineno, condition, temporary, enabled, ignorecount)
        self.bookmarks = [] # bookmarks are just a list of handles to the
                            # bookmark markers
        self.condHistory = QStringList()
        self.lexer = None
        self.encoding = None
        self.apiLanguage = ''
        self.lastModified = 0
        self.fileInfo = None
        self.zoom = 0
        self.line = -1
        self.inReopenPrompt = 0 
            # true when the prompt to reload a changed source is present
            
        self.macros = {}    # list of defined macros
        self.curMacro = None
        self.recording = 0
        
        self.connect(self, SIGNAL('modificationChanged(bool)'), 
                    self.handleModificationChanged)
        self.connect(self, SIGNAL('cursorPositionChanged(int,int)'),
                    self.handleCursorPositionChanged)
        try:
            self.connect(self, SIGNAL('modificationAttempted()'),
                    self.handleModificationReadOnly)
        except:
            pass
        
        # define the markers
        # markers for margin 1
        try:
            self.breakpoint = self.markerDefine(IconBreak)
            self.cbreakpoint = self.markerDefine(IconCBreak)
            self.tbreakpoint = self.markerDefine(IconTBreak)
            self.tcbreakpoint = self.markerDefine(IconTCBreak)
            self.dbreakpoint = self.markerDefine(IconBreakDisabled)
            self.bookmark = self.markerDefine(IconBookmark)
        except:
            self.breakpoint = self.markerDefine(QextScintilla.Circle)
            self.setMarkerForegroundColor(Qt.red, self.breakpoint)
            self.setMarkerBackgroundColor(Qt.red, self.breakpoint)
            
            self.cbreakpoint = self.markerDefine(QextScintilla.Circle)
            self.setMarkerForegroundColor(QColor("#FFAA00"), self.cbreakpoint)
            self.setMarkerBackgroundColor(QColor("#FFAA00"), self.cbreakpoint)
            
            self.tbreakpoint = self.markerDefine(QextScintilla.Circle)
            self.setMarkerForegroundColor(Qt.darkRed, self.tbreakpoint)
            self.setMarkerBackgroundColor(Qt.darkRed, self.tbreakpoint)
            
            self.tcbreakpoint = self.markerDefine(QextScintilla.Circle)
            self.setMarkerForegroundColor(QColor("#772200"), self.tcbreakpoint)
            self.setMarkerBackgroundColor(QColor("#772200"), self.tcbreakpoint)
            
            self.dbreakpoint = self.markerDefine(QextScintilla.Circle)
            self.setMarkerForegroundColor(QColor("#888888"), self.dbreakpoint)
            self.setMarkerBackgroundColor(QColor("#888888"), self.dbreakpoint)
            
            self.bookmark = self.markerDefine(QextScintilla.RightArrow)
            self.setMarkerForegroundColor(QColor("#0000FF"), self.bookmark)
            self.setMarkerBackgroundColor(QColor("#0000FF"), self.bookmark)
        
        # set the line markers
        self.currentline = self.markerDefine(QextScintilla.Background)
        self.errorline = self.markerDefine(QextScintilla.Background)
        self.setLineMarkerColours()
        
        
        margin1Mask = (1 << self.breakpoint) | \
                      (1 << self.cbreakpoint) | \
                      (1 << self.tbreakpoint) | \
                      (1 << self.tcbreakpoint) | \
                      (1 << self.dbreakpoint) | \
                      (1 << self.currentline) | \
                      (1 << self.errorline) | \
                      (1 << self.bookmark)
        self.breakpointMask = (1 << self.breakpoint) | \
                              (1 << self.cbreakpoint) | \
                              (1 << self.tbreakpoint) | \
                              (1 << self.tcbreakpoint) | \
                              (1 << self.dbreakpoint)
        
        # configure the margins
        # margin 1 for markers (i.e. breakpoints, highlights)
        self.setMarginWidth(1, 16)
        self.setMarginSensitivity(1, 1)
        self.setMarginMarkerMask(1, margin1Mask)
        
        self.connect(self, SIGNAL('marginClicked(int, int, Qt::ButtonState)'),
                    self.handleMarginClicked)
        
        # set margin 0 and 2 configuration
        # margin 0 is used for line numbers
        # margin 2 is the folding margin
        self.setMargin0and2()
        
        if self.fileName is not None:
            self.fileInfo = QFileInfo(self.fileName)
            self.fileInfo.setCaching(0)
            self.readFile(self.fileName)
            self.gotoLine(0)
            ext = self.getExtension(self.fileName)
            self.bindLexer(ext)
            
        # set the text display
        self.setTextDisplay()
        
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()
        
        # perform automatic eol conversion
        if Preferences.getEditor("AutomaticEOLConversion"):
            self.convertEols(self.eolMode())
            
        sh = self.sizeHint()
        if sh.height() < 300:
            sh.setHeight(300)
        self.resize(sh)
            
        # Make sure tabbing through a QWorkspace works.
        self.setFocusPolicy(QWidget.StrongFocus)

        self._updateReadOnly(1)

        QWhatsThis.add(self,self.trUtf8(
            """<b>A Source Editor Window</b>"""
            """<p>This window is used to display and edit a Python source file."""
            """  You can open as many of these as you like. The name of the file"""
            """ is displayed in the window's titlebar.</p>"""
            """<p>In order to set breakpoints just click in the space between"""
            """ the line numbers and the fold markers. Via the context menu"""
            """ of the margins they may be edited.</p>"""
        ))

        # Set the editors size if it is too big for the parent.
        if parent is not None:
            req = self.size()
            bnd = req.boundedTo(parent.size())

            if bnd.width() < req.width() or bnd.height() < req.height():
                self.resize(bnd)
                
        self.initContextMenu()
        self.initMarginContextMenu()
        
    def initContextMenu(self):
        """
        Private method used to setup the context menu
        """
        self.menuIds = {}
        self.menu = QPopupMenu()
        
        self.checksMenu = self.initContextMenuChecks()
        self.showMenu = self.initContextMenuShow()
        self.languagesMenu = self.initContextMenuLanguages()
        
        self.menuIds["Undo"] = \
            self.menu.insertItem(QIconSet(IconEditUndo),
            self.trUtf8('Undo'), self.undo)
        self.menuIds["Redo"] = \
            self.menu.insertItem(QIconSet(IconEditRedo),
            self.trUtf8('Redo'), self.redo)
        self.menuIds["Revert"] = \
            self.menu.insertItem(self.trUtf8("Revert to last saved state"),
                self.revertToUnmodified)
        self.menu.insertSeparator()
        self.menuIds["Cut"] = \
            self.menu.insertItem(QIconSet(IconEditCut),
            self.trUtf8('Cut'), self.cut)
        self.menuIds["Copy"] = \
            self.menu.insertItem(QIconSet(IconEditCopy),
            self.trUtf8('Copy'), self.copy)
        self.menu.insertItem(QIconSet(IconEditPaste),
            self.trUtf8('Paste'), self.paste)
        self.menu.insertSeparator()
        self.menu.insertItem(QIconSet(IconEditIndent),
            self.trUtf8('Indent'), self.indentLineOrSelection)
        self.menu.insertItem(QIconSet(IconEditUnindent),
            self.trUtf8('Unindent'), self.unindentLineOrSelection)
        self.menuIds["Comment"] = \
            self.menu.insertItem(QIconSet(IconEditComment),
                self.trUtf8('Comment'), self.commentLineOrSelection)
        self.menuIds["Uncomment"] = \
            self.menu.insertItem(QIconSet(IconEditUncomment),
                self.trUtf8('Uncomment'), self.uncommentLineOrSelection)
        self.menuIds["StreamComment"] = \
            self.menu.insertItem(self.trUtf8('Stream Comment'), 
                self.streamCommentLineOrSelection)
        self.menuIds["BoxComment"] = \
            self.menu.insertItem(self.trUtf8('Box Comment'), 
                self.boxCommentLineOrSelection)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Select to brace'), self.selectToMatchingBrace)
        self.menu.insertItem(self.trUtf8('Select all'), self.handleSelectAll)
        self.menu.insertItem(self.trUtf8('Deselect all'), self.handleDeselectAll)
        self.menu.insertSeparator()
        self.menuIds["Languages"] = \
            self.menu.insertItem(self.trUtf8("Languages"), self.languagesMenu)
        self.menu.insertItem(self.trUtf8('Autocomplete from Document'), 
            self.autoCompleteFromDocument)
        self.menuIds["acAPI"] = \
            self.menu.insertItem(self.trUtf8('Autocomplete from APIs'),
            self.autoCompleteFromAPIs)
        self.menu.insertSeparator()
        self.menuIds["Check"] = \
            self.menu.insertItem(self.trUtf8('Check'), self.checksMenu)
        self.menu.insertSeparator()
        self.menuIds["Show"] = \
            self.menu.insertItem(self.trUtf8('Show'), self.showMenu)
        self.menu.insertSeparator()
        self.menu.insertItem(QIconSet(IconClose),
            self.trUtf8('Close'), self.handleContextClose)
        self.menu.insertSeparator()
        self.menuIds["Save"] = \
            self.menu.insertItem(QIconSet(IconFileSave),
            self.trUtf8('Save'), self.handleContextSave)
        self.menu.insertItem(QIconSet(IconFileSaveAs),
            self.trUtf8('Save As...'), self.handleContextSaveAs)
        self.menu.insertSeparator()
        self.menu.insertItem(QIconSet(IconPrint),
            self.trUtf8('Print'), self.printFile)
        self.menuIds["PrintSel"] = \
            self.menu.insertItem(QIconSet(IconPrint),
            self.trUtf8('Print Selection'), self.printSelection)
            
        self.connect(self.menu, SIGNAL('aboutToShow()'), self.handleShowContextMenu)

    def initContextMenuChecks(self):
        """
        Private method used to setup the Checks context sub menu.
        """
        menu = QPopupMenu()
        
        menu.insertItem(self.trUtf8('Syntax...'), self.handleSyntaxCheck)
        menu.insertItem(self.trUtf8('Indentations...'), self.handleTabnanny)
        
        return menu

    def initContextMenuShow(self):
        """
        Private method used to setup the Show context sub menu.
        """
        menu = QPopupMenu()
        
        menu.insertItem(self.trUtf8('Code metrics...'), self.handleCodeMetrics)
        self.coverageMenuItem = \
            menu.insertItem(self.trUtf8('Code coverage...'), self.handleCodeCoverage)
        self.profileMenuItem = \
            menu.insertItem(self.trUtf8('Profile data...'), self.handleProfileData)
        
        self.connect(menu, SIGNAL('aboutToShow()'), self.handleShowShowMenu)
        
        return menu
        
    def initContextMenuLanguages(self):
        """
        Private method used to setup the Languages context sub menu.
        """
        menu = QPopupMenu()
        
        # this list must be kept in synch with the supported languages and the order
        # of the menu items defined below.
        self.supportedLanguages = [\
            (self.trUtf8("C/C++"), 'cpp'), 
            (self.trUtf8("C#"), 'cs'), 
            (self.trUtf8("HTML"), 'html'), 
            (self.trUtf8("IDL"), 'idl'), 
            (self.trUtf8("Java"), 'java'), 
            (self.trUtf8("JavaScript"), 'js'), 
            (self.trUtf8("PHP"), 'php'), 
            (self.trUtf8("Pyrex"), 'pyx'),
            (self.trUtf8("Python"), 'py'), 
            (self.trUtf8("SQL"), 'sql'), 
            (self.trUtf8("XML"), 'xml'),
        ]
        
        idx = 0
        
        for lang in self.supportedLanguages:
            id = menu.insertItem(lang[0], self.handleLanguage)
            menu.setItemParameter(id, idx)
            
            idx += 1
        
        return menu
        
    def initMarginContextMenu(self):
        """
        Private method used to setup the context menu for the margins
        """
        self.marginMenuIds = {}
        self.marginMenu = QPopupMenu()
        self.marginMenu.setCheckable(1)
        
        self.marginMenu.insertItem(self.trUtf8('Toggle bookmark'),
            self.handleToggleBookmark)
        self.marginMenuIds["NextBookmark"] = \
            self.marginMenu.insertItem(self.trUtf8('Next bookmark'),
                self.handleNextBookmark)
        self.marginMenuIds["PreviousBookmark"] = \
            self.marginMenu.insertItem(self.trUtf8('Previous bookmark'),
                self.handlePreviousBookmark)
        self.marginMenuIds["ClearBookmark"] = \
            self.marginMenu.insertItem(self.trUtf8('Clear all bookmarks'),
                self.handleClearBookmarks)
        self.marginMenu.insertSeparator()
        self.marginMenuIds["Breakpoint"] = \
            self.marginMenu.insertItem(self.trUtf8('Toggle breakpoint'), 
                self.handleToggleBreakpoint)
        self.marginMenuIds["TempBreakpoint"] = \
            self.marginMenu.insertItem('Toggle temporary breakpoint', 
                self.handleTemporaryBreakpoint)
        self.marginMenuIds["EditBreakpoint"] = \
            self.marginMenu.insertItem(self.trUtf8('Edit breakpoint...'), 
                self.handleEditBreakpoint)
        self.marginMenuIds["EnableBreakpoint"] = \
            self.marginMenu.insertItem(self.trUtf8('Enable breakpoint'), 
                self.handleToggleBreakpointEnabled)
        self.marginMenuIds["NextBreakpoint"] = \
            self.marginMenu.insertItem(self.trUtf8('Next breakpoint'), 
                self.handleNextBreakpoint)
        self.marginMenuIds["PreviousBreakpoint"] = \
            self.marginMenu.insertItem(self.trUtf8('Previous breakpoint'), 
                self.handlePreviousBreakpoint)
        self.marginMenuIds["ClearBreakpoint"] = \
            self.marginMenu.insertItem(self.trUtf8('Clear all breakpoints'), 
                self.handleClearBreakpoints)
        self.marginMenu.insertSeparator()
        self.marginMenuIds["LMBbookmarks"] = \
            self.marginMenu.insertItem(self.trUtf8('LMB toggles bookmarks'),
                self.handleLMBbookmarks)
        self.marginMenuIds["LMBbreakpoints"] = \
            self.marginMenu.insertItem(self.trUtf8('LMB toggles breakpoints'),
                self.handleLMBbreakpoints)
        self.marginMenu.setItemChecked(self.marginMenuIds["LMBbookmarks"], 0)
        self.marginMenu.setItemChecked(self.marginMenuIds["LMBbreakpoints"], 1)
                
        self.connect(self.marginMenu, SIGNAL('aboutToShow()'), 
            self.handleShowMarginContextMenu)
        
    def handleLanguage(self, idx):
        """
        Private method to handle the selection of a lexer language.
        
        Arguments
        
            idx -- index of the lexer language (int)
        """
        self.setLanguage(self.supportedLanguages[idx][1])
        
    def setLanguage(self, language):
        """
        Private method to set a lexer language.
        
        Arguments
        
            language -- language (extension) of the desired lexer (string)
        """
        self.bindLexer(language)
                
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()

    def getExtension(self, fname):
        """
        Private method to get the fileextension without a leading '.'.
        
        Arguments
        
            fname -- filename (string)
            
        Returns
        
            extension of the filename (string)
        """
        dummy, ext = os.path.splitext(fname)

        # now split off .
        if ext.startswith('.'):
            return ext[1:]
        else:
            return ext

    def bindLexer(self, language):
        """
        Private slot to set the correct lexer depending on language.
        
        Arguments
        
            language -- language (extension) of the desired lexer (string)
        """
        if language in ['py', 'pyx']:
            from LexerPython import LexerPython
            self.lexer = LexerPython(self)
            self.apiLanguage = "Python"
        elif language in ['cpp', 'cxx', 'cc', 'c', 'hpp', 'hh', 'h']:
            from LexerCPP import LexerCPP
            self.lexer = LexerCPP(self)
            self.apiLanguage = "C++"
        elif language == 'cs':
            from LexerCSharp import LexerCSharp
            self.lexer = LexerCSharp(self)
            self.apiLanguage = "C#"
        elif language == 'idl':
            from LexerIDL import LexerIDL
            self.lexer = LexerIDL(self)
            self.apiLanguage = "IDL"
        elif language == 'java':
            from LexerJava import LexerJava
            self.lexer = LexerJava(self)
            self.apiLanguage = "Java"
        elif language == 'js':
            from LexerJavaScript import LexerJavaScript
            self.lexer = LexerJavaScript(self)
            self.apiLanguage = "JavaScript"
        elif language == 'sql':
            try:
                from LexerSQL import LexerSQL
                self.lexer = LexerSQL(self)
                self.apiLanguage = "SQL"
            except ImportError:
                return
        elif language in ['html', 'htm', 'asp', 'shtml', 'css', \
                    'php', 'php3', 'php4', 'phtml', 'xml', 'xsl', \
                    'xslt', 'dtd', 'docbook']:
            try:
                from LexerHTML import LexerHTML
                self.lexer = LexerHTML(self)
                self.apiLanguage = "HTML"
            except ImportError:
                return
        else:
            return
        
        # get the font for style 0 and set it as the default font
        key = '/eric3/Scintilla/%s/style0/font' % str(self.lexer.language())
        fontstr, ok = Preferences.Prefs.settings.readEntry(key)
        if ok:
            fdesc = QStringList.split(',', fontstr)
            font = QFont(fdesc[0], int(str(fdesc[1])))
            self.lexer.setDefaultFont(font)
        self.setLexer(self.lexer)
        self.lexer.readSettings(Preferences.Prefs.settings, "/eric3/Scintilla")
        
        # now set the lexer properties
        if language in ['py', 'pyx']:
            # Python lexer stuff
            self.lexer.setIndentationWarning(Preferences.getEditor("PythonBadIndentation"))
            self.lexer.setFoldComments(Preferences.getEditor("PythonFoldComment"))
            self.lexer.setFoldQuotes(Preferences.getEditor("PythonFoldString"))
            self.lexer.setAutoIndentStyle(QextScintilla.AiMaintain)
        elif language in ['cpp', 'cxx', 'cc', 'c', 'hpp', 'hh', 'h', 'cs', 'idl', 'java', 'js']:
            # C++ lexer stuff
            self.lexer.setFoldComments(Preferences.getEditor("CppFoldComment"))
            self.lexer.setFoldPreprocessor(Preferences.getEditor("CppFoldPreprocessor"))
            try:
                self.lexer.setFoldAtElse(Preferences.getEditor("CppFoldAtElse"))
            except AttributeError:
                pass
            self.lexer.setAutoIndentStyle(QextScintilla.AiOpening | QextScintilla.AiClosing)
        elif language in ['html', 'htm', 'asp', 'shtml', 'css', \
                    'php', 'php3', 'php4', 'phtml', 'xml', 'xsl', \
                    'xslt', 'dtd', 'docbook']:
            try:
                self.lexer.setFoldPreprocessor(Preferences.getEditor("CppFoldPreprocessor"))
                self.lexer.setCaseSensitiveTags(Preferences.getEditor("HtmlCaseSensitiveTags"))
            except AttributeError:
                pass
        
    def getLexer(self):
        """
        Public method to retrieve a reference to the lexer object.
        
        Returns
        
            the lexer object (Lexer)
        """
        return self.lexer
        
    def handleModificationChanged(self, m):
        """
        Private slot to handle the modificationChanged signal. 
        
        It emits the signal modificationStatusChanged with parameters
        m and self.
        
        Arguments
        
            m -- modification status
        """
        self.emit(PYSIGNAL('modificationStatusChanged'), (m, self))
        
    def handleCursorPositionChanged(self, line, pos):
        """
        Private slot to handle the cursorPositionChanged signal. 
        
        It emits the signal cursorChanged with parameters fileName, 
        line and pos.
        
        Arguments
        
            line -- line number of the cursor
            
            pos -- position in line of the cursor
        """
        self.emit(PYSIGNAL('cursorChanged'), (self.fileName, line+1, pos))
        
    def handleModificationReadOnly(self):
        """
        Private slot to handle the modificationAttempted signal.
        """
        QMessageBox.warning(None,
            self.trUtf8("Modification of Read Only file"),
            self.trUtf8("""You are attempting to change a read only file. Please save to a different file first."""),
            self.trUtf8("&OK"),
            None,
            None,
            0, -1)
        
    def getFileName(self):
        """
        Public method to return the name of the file being displayed.
        
        Returns
        
            filename of the displayed file (string)
        """
        return self.fileName

    def highlightVisible(self):
        """
        Public method to make sure that the highlight is visible.
        """
        if self.lastHighlight is not None:
            lineno = self.markerLine(self.lastHighlight)
            self.ensureVisible(lineno+1)
        
    def highlight(self,line=None,error=0):
        """
        Public method to highlight (or de-highlight) a particular line.
        
        Arguments
        
            line -- line number to highlight
            
            error -- flag indicating whether the error highlight should be used
        """
        if line is None:
            self.lastHighlight = None
            if self.lastErrorMarker is not None:
                self.markerDeleteHandle(self.lastErrorMarker)
            self.lastErrorMarker = None
            if self.lastCurrMarker is not None:
                self.markerDeleteHandle(self.lastCurrMarker)
            self.lastCurrMarker = None
        else:
            if error:
                if self.lastErrorMarker is not None:
                    self.markerDeleteHandle(self.lastErrorMarker)
                self.lastErrorMarker = self.markerAdd(line-1, self.errorline)
                self.lastHighlight = self.lastErrorMarker
            else:
                if self.lastCurrMarker is not None:
                    self.markerDeleteHandle(self.lastCurrMarker)
                self.lastCurrMarker = self.markerAdd(line-1, self.currentline)
                self.lastHighlight = self.lastCurrMarker

    def getHighlightPosition(self):
        """
        Public method to return the position of the highlight bar.
        
        Returns
        
            line number of the highlight bar
        """
        if self.lastHighlight is not None:
            return self.markerLine(self.lastHighlight)
        else:
            return 1
            
    def clearBreakpoint(self, line):
        """
        Public method to clear a breakpoint.
        
        Note: This doesn't clear the breakpoint in the debugger,
        it just deletes it from the editor internal list of breakpoints.
        
        Arguments
        
            line -- linenumber of the breakpoint
        """
        for handle, (ln, _, _, _, _) in self.breaks.items():
            if self.markerLine(handle) == line-1:
                break
        else:
            # not found, simply ignore it
            return
            
        del self.breaks[handle]
        self.markerDeleteHandle(handle)
        
    def toggleBreakpoint(self, line, temporary = 0):
        """
        Public method to toggle a breakpoint.
        
        Arguments
        
            line -- line number of the breakpoint
            
            temporary -- flag indicating a temporary breakpoint
        """
        for handle, (ln, _, _, _, _) in self.breaks.items():
            if self.markerLine(handle) == line-1:
                break
        else:
            # set a new breakpoint
            marker = temporary and self.tbreakpoint or self.breakpoint
            handle = self.markerAdd(line-1, marker)
            self.breaks[handle] = (line, None, temporary, 1, 0)
            self.dbs.remoteBreakpoint(self.fileName, line, 1, None, temporary)
            self.emit(PYSIGNAL('breakpointToggled'), (self,))
            return
            
        # delete breakpoint
        del self.breaks[handle]
        self.markerDeleteHandle(handle)
        self.dbs.remoteBreakpoint(self.fileName, ln, 0)
        self.emit(PYSIGNAL('breakpointToggled'), (self,))
        
    def toggleBreakpointEnabled(self, line):
        """
        Public method to toggle a breakpoints enabled status.
        
        Arguments
        
            line -- line number of the breakpoint
        """
        for handle, (ln, cond, temp, enabled, ignorecount) in self.breaks.items():
            if self.markerLine(handle) == line-1:
                break
        else:
            # no breakpoint found on that line
            return
            
        if enabled:
            # disable it
            marker = self.dbreakpoint
            enabled = 0
        else:
            # enable it
            if cond:
                marker = temp and self.tcbreakpoint or self.cbreakpoint
            else:
                marker = temp and self.tbreakpoint or self.breakpoint
            enabled = 1
            
        del self.breaks[handle]
        self.markerDeleteHandle(handle)
        handle = self.markerAdd(line-1, marker)
        self.breaks[handle] = (line, cond, temp, enabled, ignorecount)
        self.dbs.remoteBreakpointEnable(self.fileName, ln, enabled)
        
    def setBreakpointIgnoreCount(self, line, count):
        """
        Public method to set a breakpoints ignore count.
        
        Arguments
        
            line -- line number of the breakpoint
            
            count -- ignore count for the breakpoint (integer)
        """
        for handle, (ln, _, _, _, _) in self.breaks.items():
            if self.markerLine(handle) == line-1:
                break
        else:
            # no breakpoint found on that line
            return
            
        self.breaks[handle][4] = count
        self.dbs.remoteBreakpointIgnore(self.fileName, ln, count)
            
    def setBreakpointProperties(self, line, properties):
        """
        Public method to set a breakpoints properties.
        
        Arguments
        
            line -- line number of the breakpoint
            
            properties -- properties for the breakpoint (tuple)
                (condition, temporary flag, enabled flag, ignore count)
        """
        for handle, (ln, _, _, _, _) in self.breaks.items():
            if self.markerLine(handle) == line-1:
                break
        else:
            # no breakpoint found on that line
            return
            
        # step 1: delete the breakpoint
        self.dbs.remoteBreakpoint(self.fileName, ln, 0)
        del self.breaks[handle]
        self.markerDeleteHandle(handle)
        
        # step 2: set a new breakpoint
        self.newBreakpointWithProperties(line, properties)
    
    def newBreakpointWithProperties(self, line, properties):
        """
        Public method to set a new breakpoint and its properties.
        
        Arguments
        
            line -- line number of the breakpoint
            
            properties -- properties for the breakpoint (tuple)
                (condition, temporary flag, enabled flag, ignore count)
        """
        if not properties[2]:
            marker = self.dbreakpoint
        elif properties[0]:
            marker = properties[1] and self.tcbreakpoint or self.cbreakpoint
        else:
            marker = properties[1] and self.tbreakpoint or self.breakpoint
            
        handle = self.markerAdd(line-1, marker)
        self.dbs.remoteBreakpoint(self.fileName, line, 1, properties[0], properties[1])
        if not properties[2]:
            self.dbs.remoteBreakpointEnable(self.fileName, line, 0)
        if properties[3]:
            self.dbs.remoteBreakpointIgnore(self.fileName, line, properties[3])
        self.breaks[handle] = (line,) + properties
        self.emit(PYSIGNAL('breakpointToggled'), (self,))
            
    def curLineHasBreakpoint(self):
        """
        Public method to check for the presence of a breakpoint at the current line.
        
        Returns
        
            flag indicating the presence of a breakpoint (boolean)
        """
        line, _ = self.getCursorPosition()
        return self.markersAtLine(line) & self.breakpointMask != 0
        
    def hasBreakpoints(self):
        """
        Public method to check for the presence of breakpoints.
        
        Returns
        
            flag indicating the presence of breakpoints (boolean)
        """
        return len(self.breaks) > 0
        
    def handleTemporaryBreakpoint(self):
        if self.line < 0:
            self.line, index = self.getCursorPosition()
        self.line += 1
        self.toggleBreakpoint(self.line, 1)
        self.line = -1
        
    def handleToggleBreakpoint(self):
        """
        Private slot to handle the 'Toggle breakpoint' context menu action.
        """
        if self.line < 0:
            self.line, index = self.getCursorPosition()
        self.line += 1
        self.toggleBreakpoint(self.line)
        self.line = -1
        
    def handleToggleBreakpointEnabled(self):
        """
        Private slot to handle the 'Enable/Disable breakpoint' context menu action.
        """
        if self.line < 0:
            self.line, index = self.getCursorPosition()
        self.line += 1
        self.toggleBreakpointEnabled(self.line)
        self.line = -1
        
    def handleEditBreakpoint(self):
        """
        Private slot to handle the 'Edit breakpoint' context menu action.
        """
        if self.line < 0:
            self.line, index = self.getCursorPosition()
        found = 0
        for handle, (ln, cond, temp, enabled, ignorecount) in self.breaks.items():
            if self.markerLine(handle) == self.line:
                found = 1
                break
            
        if found:
            dlg = EditBreakpointDialog((cond, temp, enabled, ignorecount),
                self.condHistory, self, modal = 1)
            if dlg.exec_loop() == QDialog.Accepted:
                cond, temp, enabled, ignorecount = dlg.getData()
                if cond is not None:
                    self.condHistory.remove(cond)
                    self.condHistory.prepend(cond)
                self.setBreakpointProperties(self.line+1, 
                    (cond, temp, enabled, ignorecount))
            
        self.line = -1
        
    def handleNextBreakpoint(self):
        """
        Private slot to handle the 'Next breakpoint' context menu action.
        """
        line, index = self.getCursorPosition()
        if line == self.lines()-1:
            line = 0
        else:
            line += 1
        bpline = self.markerFindNext(line, self.breakpointMask)
        if bpline < 0:
            # wrap around
            bpline = self.markerFindNext(0, self.breakpointMask)
        if bpline >= 0:
            self.setCursorPosition(bpline, 0)
            self.ensureLineVisible(bpline)
        
    def handlePreviousBreakpoint(self):
        """
        Private slot to handle the 'Previous breakpoint' context menu action.
        """
        line, index = self.getCursorPosition()
        if line == 0:
            line = self.lines()-1
        else:
            line -= 1
        bpline = self.markerFindPrevious(line, self.breakpointMask)
        if bpline < 0:
            # wrap around
            bpline = self.markerFindPrevious(self.lines()-1, self.breakpointMask)
        if bpline >= 0:
            self.setCursorPosition(bpline, 0)
            self.ensureLineVisible(bpline)
        
    def handleClearBreakpoints(self):
        """
        Private slot to handle the 'Clear all breakpoints' context menu action.
        """
        for handle, (ln, cond, _, _, _) in self.breaks.items():
            self.toggleBreakpoint(ln)
        self.breaks = {}
    
    def getBreakpoints(self):
        """
        Public method to retrieve all breakpoints.
        
        This method will update the internal list of breakpoints before
        the list is returned.
        
        Returns
        
            a list of tuples with linenumber and condition
        """
        for handle, (ln, cond, temp, enabled, ignorecount) in self.breaks.items():
            line = self.markerLine(handle)+1
            if line != ln:
                self.breaks[handle] = (line, cond, temp, enabled, ignorecount)
        
        return self.breaks.values()
        
    def printFile(self):
        """
        Public slot to print the text.
        """
        printer = Printer()
        sb = qApp.mainWidget().statusBar()
        if printer.setup(self):
            sb.message(self.trUtf8('Printing...'))
            qApp.setOverrideCursor(Qt.waitCursor)
            qApp.processEvents()
            fn = self.getFileName()
            if fn is not None:
                printer.setDocName(os.path.basename(fn))
            else:
                printer.setDocName(self.trUtf8('Noname'))
            res = printer.printRange(self)
            qApp.restoreOverrideCursor()
            if res:
                sb.message(self.trUtf8('Printing completed'), 2000)
            else:
                sb.message(self.trUtf8('Error while printing'), 2000)
        else:
            sb.message(self.trUtf8('Printing aborted'), 2000)
    
    def printSelection(self):
        """
        Public slot to print the selected text.
        """
        if not self.hasSelectedText():
            QMessageBox.warning(self, self.trUtf8('Print Selection'),
                self.trUtf8('There is no selected text, printing aborted.'),
                QMessageBox.Ok, QMessageBox.NoButton)
            return
            
        # get the selection
        fromLine, fromIndex, toLine, toIndex = self.getSelection()
        if toIndex == 0:
            toLine -= 1
        
        printer = Printer()
        sb = qApp.mainWidget().statusBar()
        if printer.setup(self):
            sb.message(self.trUtf8('Printing...'))
            qApp.setOverrideCursor(Qt.waitCursor)
            qApp.processEvents()
            fn = self.getFileName()
            if fn is not None:
                printer.setDocName(os.path.basename(fn))
            else:
                printer.setDocName(self.trUtf8('Noname'))
            # Qscintilla seems to print two lines more than told
            res = printer.printRange(self, fromLine, toLine-2) 
            qApp.restoreOverrideCursor()
            if res:
                sb.message(self.trUtf8('Printing completed'), 2000)
            else:
                sb.message(self.trUtf8('Error while printing'), 2000)
        else:
            sb.message(self.trUtf8('Printing aborted'), 2000)
    
    def readFile(self, fn):
        """
        Public slot to read the text from a file.
        
        Arguments
        
            fn -- filename to read from (string or QString)
        """
        fn = str(fn)
        try:
            f = open(fn, 'rb')
        except:
            QMessageBox.critical(self.vm, self.trUtf8('Open File'),
                self.trUtf8('The file <b>%1</b> could not be opened.')
                    .arg(fn))
            raise
            
        qApp.setOverrideCursor(Qt.waitCursor)
        
        txt, self.encoding = Utilities.decode(f.read())
        if (not Preferences.getEditor("TabForIndentation")) and \
                Preferences.getEditor("ConvertTabsOnLoad"):
            txt = txt.expandtabs(Preferences.getEditor("TabWidth"))

        self.setText(txt)
        
        f.close()
        
        qApp.restoreOverrideCursor()
        
        self.lastModified = self.fileInfo.lastModified()
    
    def writeFile(self, fn):
        """
        Public slot to write the text to a file.
        
        Arguments
        
            fn -- filename to write to (string or QString)
            
        Returns
        
            flag indicating success
        """
        fn = str(fn)
        txt = Utilities.encode(unicode(self.text()), self.encoding)
        # work around glitch in scintilla: always make sure, 
        # that the last line is terminated properly
        m = self.eolMode()
        eol = None
        if m == QextScintilla.EolWindows:
            eol = '\r\n'
        elif m == QextScintilla.EolUnix:
            eol = '\n'
        elif m == QextScintilla.EolMac:
            eol = '\r'
        if eol:
            if len(txt) >= len(eol):
                if txt[-len(eol):] != eol:
                    txt += eol
            else:
                txt += eol
        
        # create a backup file, if the option is set
        if Preferences.getEditor("CreateBackupFile"):
            bfn = '%s~' % fn
            try:
                os.remove(bfn)
            except:
                # if there was an error, ignore it
                pass
            try:
                os.rename(fn, bfn)
            except:
                # if there was an error, ignore it
                pass
                
        # now write text to the file fn
        try:
            f = open(fn, 'wb')
            f.write(txt)
            f.close()
            return 1
        except IOError, why:
            QMessageBox.critical(self, self.trUtf8('Save File'),
                self.trUtf8('The file <b>%1</b> could not be saved.<br>Reason: %2')
                    .arg(fn).arg(str(why)))
            return 0
        
    def saveFile(self, saveas = 0, path = None):
        """
        Public slot to save the text to a file.
        
        Arguments
        
            saveas -- flag indicating a 'save as' action
            
            path -- directory to save the file in (string or QString)
            
        Returns
        
            tuple of two values (boolean, string) giving a success indicator and
            the name of the saved file
        """
        if not saveas and not self.isModified():
            return (0, None)      # do nothing if text wasn't changed
            
        newName = None
        if saveas or self.fileName is None:
            selectedFilter = QString('')
            fn = QFileDialog.getSaveFileName(path,
                self.trUtf8("Python Files (*.py);;"
                    "Pyrex Files (*.pyx);;"
                    "IDL Files (*.idl);;"
                    "C Files (*.c);;"
                    "C++ Files (*.cpp);;"
                    "C++/C Header Files (*.h);;"
                    "C# Files (*.cs);;"
                    "HTML Files (*.html);;"
                    "PHP Files (*.php);;"
                    "ASP Files (*.asp);;"
                    "CSS Files (*.css);;"
                    "XML Files (*.xml);;"
                    "XSL Files (*.xsl);;"
                    "DTD Files (*.dtd);;"
                    "Java Files (*.java);;"
                    "JavaScript Files (*.js);;"
                    "SQL Files (*.sql);;"
                    "Docbook Files (*.docbook);;"
                    "All Files (*)"), self, None,
                self.trUtf8("Save File"), selectedFilter, 0)
                
            if not fn.isNull():
                ext = QFileInfo(fn).extension()
                if ext.isEmpty():
                    ex = selectedFilter.section('(*',1,1).section(')',0,0)
                    if not ex.isEmpty():
                        fn.append(ex)
                if QFileInfo(fn).exists():
                    abort = QMessageBox.warning(self,
                        self.trUtf8("Save File"),
                        self.trUtf8("The file <b>%1</b> already exists.")
                            .arg(fn),
                        self.trUtf8("&Overwrite"),
                        self.trUtf8("&Abort"), None, 1)
                    if abort:
                        return (0, None)
                fn = str(QDir.convertSeparators(fn))
                newName = fn
            else:
                return (0, None)
        else:
            fn = self.fileName
        
        if self.writeFile(fn):
            self.fileName = fn
            self.setModified(0)
            self.setReadOnly(0)
            self.setCaption(self.fileName)
            if self.lexer is None:
                ext = self.getExtension(self.fileName)
                self.bindLexer(ext)
                
                # set the autocompletion and calltips function
                self.setAutoCompletion()
                self.setCallTips()
                
            if self.fileInfo is None or saveas:
                self.fileInfo = QFileInfo(self.fileName)
                self.fileInfo.setCaching(0)
            self.lastModified = self.fileInfo.lastModified()
            if newName is not None:
                self.vm.addToRecentList(newName)
            self.emit(PYSIGNAL('editorSaved'), (self.fileName,))
            return (1, self.fileName)
        else:
            return (0, None)
        
    def saveFileAs(self, path = None):
        """
        Public slot to save a file with a new name.
        
            saveas -- flag indicating a 'save as' action
            
            path -- directory to save the file in (string or QString)
            
        Returns
        
            tuple of two values (boolean, string) giving a success indicator and
            the name of the saved file
        """
        return self.saveFile(1, path)

    def ensureVisible(self, line):
        """
        Public slot to ensure, that the specified line is visible.
        
        Arguments
        
            line -- line number to make visible
        """
        self.ensureLineVisible(line-1)
        
    def handleMarginClicked(self, margin, line, state):
        """
        Private slot to handle the marginClicked signal.
        
        Arguments
        
            margin -- id of the clicked margin
            
            line -- line number pf the click
            
            state -- mouse button state
        """
        if margin == 1:
            if self.marginMenu.isItemChecked(self.marginMenuIds["LMBbreakpoints"]):
                self.toggleBreakpoint(line+1)
            else:
                self.handleBookmark(line+1)

    def commentLine(self):
        """
        Public slot to comment the current line.
        """
        if self.lexer is None or not self.lexer.canBlockComment():
            return
            
        line, index = self.getCursorPosition()
        self.beginUndoAction()
        self.insertAt(self.lexer.commentStr(), line, 0)
        self.endUndoAction()
        
    def uncommentLine(self):
        """
        Public slot to uncomment the current line.
        
        This happens only, if it was commented by using
        the commentLine() or commentSelection() slots
        """
        if self.lexer is None or not self.lexer.canBlockComment():
            return
            
        commentStr = self.lexer.commentStr()
        line, index = self.getCursorPosition()
        
        # check if line starts with our comment string (i.e. was commented
        # by our comment...() slots
        if not self.text(line).startsWith(commentStr):
            return
            
        # now remove the comment string
        self.beginUndoAction()
        self.setSelection(line, 0, line, commentStr.length())
        self.removeSelectedText()
        self.endUndoAction()
        
    def commentSelection(self):
        """
        Public slot to comment the current selection.
        """
        if self.lexer is None or not self.lexer.canBlockComment():
            return
            
        if not self.hasSelectedText():
            return
            
        commentStr = self.lexer.commentStr()
        
        # get the selection boundaries
        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
        if indexTo == 0:
            endLine = lineTo - 1
        else:
            endLine = lineTo
            
        self.beginUndoAction()
        # iterate over the lines
        for line in range(lineFrom, endLine+1):
            self.insertAt(commentStr, line, 0)
            
        # change the selection accordingly
        self.setSelection(lineFrom, 0, endLine+1, 0)
        self.endUndoAction()
        
    def uncommentSelection(self):
        """
        Public slot to uncomment the current selection. 
        
        This happens only, if it was commented by using
        the commentLine() or commentSelection() slots
        """
        if self.lexer is None or not self.lexer.canBlockComment():
            return
            
        if not self.hasSelectedText():
            return
            
        commentStr = self.lexer.commentStr()
        
        # get the selection boundaries
        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
        if indexTo == 0:
            endLine = lineTo - 1
        else:
            endLine = lineTo
            
        self.beginUndoAction()
        # iterate over the lines
        for line in range(lineFrom, endLine+1):
            # check if line starts with our comment string (i.e. was commented
            # by our comment...() slots
            if not self.text(line).startsWith(commentStr):
                continue
            
            self.setSelection(line, 0, line, commentStr.length())
            self.removeSelectedText()
            
            # adjust selection start
            if line == lineFrom:
                indexFrom -= commentStr.length()
                if indexFrom < 0:
                    indexFrom = 0
                    
            # adjust selection end
            if line == lineTo:
                indexTo -= commentStr.length()
                if indexTo < 0:
                    indexTo = 0
                    
        # change the selection accordingly
        self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
        self.endUndoAction()
        
    def commentLineOrSelection(self):
        """
        Public slot to comment the current line or current selection.
        """
        if self.hasSelectedText():
            self.commentSelection()
        else:
            self.commentLine()

    def uncommentLineOrSelection(self):
        """
        Public slot to uncomment the current line or current selection.
        
        This happens only, if it was commented by using
        the commentLine() or commentSelection() slots
        """
        if self.hasSelectedText():
            self.uncommentSelection()
        else:
            self.uncommentLine()

    def streamCommentLine(self):
        """
        Public slot to stream comment the current line.
        """
        if self.lexer is None or not self.lexer.canStreamComment():
            return
            
        commentStr = self.lexer.streamCommentStr()
        line, index = self.getCursorPosition()
        
        self.beginUndoAction()
        self.insertAt(commentStr['end'], line, self.lineLength(line))
        self.insertAt(commentStr['start'], line, 0)
        self.endUndoAction()
        
    def streamCommentSelection(self):
        """
        Public slot to comment the current selection.
        """
        if self.lexer is None or not self.lexer.canStreamComment():
            return
            
        if not self.hasSelectedText():
            return
            
        commentStr = self.lexer.streamCommentStr()
        
        # get the selection boundaries
        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
        if indexTo == 0:
            endLine = lineTo - 1
            endIndex = self.lineLength(endLine)
        else:
            endLine = lineTo
            endIndex = indexTo
            
        self.beginUndoAction()
        self.insertAt(commentStr['end'], endLine, endIndex)
        self.insertAt(commentStr['start'], lineFrom, indexFrom)
        
        # change the selection accordingly
        if indexTo > 0:
            indexTo += commentStr['end'].length()
            if lineFrom == endLine:
                indexTo += commentStr['start'].length()
        self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
        self.endUndoAction()
        
    def streamCommentLineOrSelection(self):
        """
        Public slot to stream comment the current line or current selection.
        """
        if self.hasSelectedText():
            self.streamCommentSelection()
        else:
            self.streamCommentLine()

    def boxCommentLine(self):
        """
        Public slot to box comment the current line.
        """
        if self.lexer is None or not self.lexer.canBoxComment():
            return
            
        commentStr = self.lexer.boxCommentStr()
        line, index = self.getCursorPosition()
        
        self.beginUndoAction()
        self.insertAt('\n', line, self.lineLength(line))
        self.insertAt(commentStr['end'], line + 1, 0)
        self.insertAt(commentStr['middle'], line, 0)
        self.insertAt('\n', line, 0)
        self.insertAt(commentStr['start'], line, 0)
        self.endUndoAction()
        
    def boxCommentSelection(self):
        """
        Public slot to box comment the current selection.
        """
        if self.lexer is None or not self.lexer.canBoxComment():
            return
            
        if not self.hasSelectedText():
            return
            
        commentStr = self.lexer.boxCommentStr()
        
        # get the selection boundaries
        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
        if indexTo == 0:
            endLine = lineTo - 1
        else:
            endLine = lineTo
            
        self.beginUndoAction()
        # iterate over the lines
        for line in range(lineFrom, endLine+1):
            self.insertAt(commentStr['middle'], line, 0)
        
        # now do the comments before and after the selection
        self.insertAt('\n', endLine, self.lineLength(endLine))
        self.insertAt(commentStr['end'], endLine + 1, 0)
        self.insertAt('\n', lineFrom, 0)
        self.insertAt(commentStr['start'], lineFrom, 0)
            
        # change the selection accordingly
        self.setSelection(lineFrom, 0, endLine+3, 0)
        self.endUndoAction()
        
    def boxCommentLineOrSelection(self):
        """
        Public slot to box comment the current line or current selection.
        """
        if self.hasSelectedText():
            self.boxCommentSelection()
        else:
            self.boxCommentLine()

    def indentLine(self, indent = 1):
        """
        Private method to indent or unindent the current line. 
        
        Arguments
        
            indent -- flag indicating an indent operation
                If the flag is true, an indent operation is performed.
                Otherwise the current line is unindented.
        """
        line, index = self.getCursorPosition()
        self.beginUndoAction()
        if indent:
            self.indent(line)
        else:
            self.unindent(line)
        self.endUndoAction()
        
    def indentSelection(self, indent = 1):
        """
        Private method to indent or unindent the current selection. 
        
        Arguments
        
            indent -- flag indicating an indent operation
                If the flag is true, an indent operation is performed.
                Otherwise the current line is unindented.
        """
        if not self.hasSelectedText():
            return
            
        # get the selection
        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
        
        if indexTo == 0:
            endLine = lineTo - 1
        else:
            endLine = lineTo
            
        self.beginUndoAction()
        # iterate over the lines
        for line in range(lineFrom, endLine+1):
            if indent:
                self.indent(line)
            else:
                self.unindent(line)
        self.endUndoAction()
        
    def indentLineOrSelection(self):
        """
        Public slot to indent the current line or current selection
        """
        if self.hasSelectedText():
            self.indentSelection(1)
        else:
            self.indentLine(1)

    def unindentLineOrSelection(self):
        """
        Public slot to unindent the current line or current selection.
        """
        if self.hasSelectedText():
            self.indentSelection(0)
        else:
            self.indentLine(0)

    def gotoLine(self, line):
        """
        Public slot to jump to the beginning of a line.
        
        Arguments
        
            line -- line number to go to
        """
        self.setCursorPosition(line-1, 0)
        self.ensureVisible(line)
        
    def readSettings(self):
        """
        Public slot to read the settings into our lexer.
        """
        # read the lexer settings
        if self.lexer is not None:
            self.lexer.readSettings(Preferences.Prefs.settings, "/eric3/Scintilla")
            if self.lexer.language() == 'Python':
                self.lexer.setAutoIndentStyle(QextScintilla.AiMaintain)
            elif self.lexer.language() in ['C++', 'C#', 'IDL', 'Java', 'JavaScript']:
                self.lexer.setAutoIndentStyle(QextScintilla.AiOpening | QextScintilla.AiClosing)

        # set the line marker colours
        self.setLineMarkerColours()
        
        # set margin 0 and 2 configuration
        self.setMargin0and2()
        
        # set the text display
        self.setTextDisplay()
        
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()
        
    def setLineMarkerColours(self):
        """
        Private method to set the line marker colours.
        """
        self.setMarkerForegroundColor(Preferences.getEditorColour("CurrentMarker"),
            self.currentline)
        self.setMarkerBackgroundColor(Preferences.getEditorColour("CurrentMarker"),
            self.currentline)
        self.setMarkerForegroundColor(Preferences.getEditorColour("ErrorMarker"),
            self.errorline)
        self.setMarkerBackgroundColor(Preferences.getEditorColour("ErrorMarker"),
            self.errorline)
        
    def setMargin0and2(self):
        """
        Private method to configure margins 0 and 2.
        """
        # set the font for all margins
        self.setMarginsFont(Preferences.getEditorOtherFonts("MarginsFont"))
        
        # set margin 0 settings
        linenoMargin = Preferences.getEditor("LinenoMargin")
        self.setMarginLineNumbers(0, linenoMargin)
        if linenoMargin:
            self.setMarginWidth(0, ' ' + '8' * Preferences.getEditor("LinenoWidth") + ' ')
        else:
            self.setMarginWidth(0, 0)
        
        # set margin 2 settings
        if Preferences.getEditor("FoldingMargin"):
            self.setFolding(Preferences.getEditor("FoldingStyle"))
        else:
            self.setFolding(QextScintilla.NoFoldStyle)
        
    def setTextDisplay(self):
        """
        Private method to configure the text display.
        """
        self.setTabWidth(Preferences.getEditor("TabWidth"))
        self.setIndentationWidth(Preferences.getEditor("IndentWidth"))
        self.setIndentationsUseTabs(Preferences.getEditor("TabForIndentation"))      # no tabs for indentation
        self.setTabIndents(Preferences.getEditor("TabIndents"))
        self.setBackspaceUnindents(Preferences.getEditor("TabIndents"))
        self.setIndentationGuides(Preferences.getEditor("IndentationGuides"))
        self.setWhitespaceVisibility(Preferences.getEditor("ShowWhitespace"))
        self.setEolVisibility(Preferences.getEditor("ShowEOL"))
        self.setAutoIndent(Preferences.getEditor("AutoIndentation"))
        if Preferences.getEditor("BraceHighlighting"):
            self.setBraceMatching(QextScintilla.SloppyBraceMatch)
        else:
            self.setBraceMatching(QextScintilla.NoBraceMatch)
        self.setMatchedBraceForegroundColor(
            Preferences.getEditorColour("MatchingBrace"))
        self.setUnmatchedBraceForegroundColor(
            Preferences.getEditorColour("NonmatchingBrace"))
        self.setEolMode(Preferences.getEditor("EOLMode"))
        
    def setAutoCompletion(self):
        """
        Private method to configure the autocompletion function.
        """
        self.setAutoCompletionCaseSensitivity(
            Preferences.getEditor("AutoCompletionCaseSensitivity"))
        self.setAutoCompletionReplaceWord(
            Preferences.getEditor("AutoCompletionReplaceWord"))
        self.setAutoCompletionShowSingle(
            Preferences.getEditor("AutoCompletionShowSingle"))
        api = self.vm.getAPIs(self.apiLanguage)
        self.setAutoCompletionAPIs(api)
        if api is None:
            self.setAutoCompletionSource(QextScintilla.AcsDocument)
            self.acAPI = 0
        else:
            self.setAutoCompletionSource(
                Preferences.getEditor("AutoCompletionSource"))
            self.acAPI = 1
                
        if Preferences.getEditor("AutoCompletionEnabled"):
            self.setAutoCompletionThreshold(
                Preferences.getEditor("AutoCompletionThreshold"))
        else:
            self.setAutoCompletionThreshold(-1)
        
    def setCallTips(self):
        """
        Private method to configure the calltips function.
        """
        if Preferences.getEditor("CallTipsEnabled"):
            api = self.vm.getAPIs(self.apiLanguage)
            self.setCallTipsAPIs(api)
            self.setCallTipsBackgroundColor(
                Preferences.getEditorColour("CallTipsBackground"))
            self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible"))
        else:
            self.setCallTipsAPIs(None)

    def checkDirty(self):
        """
        Private method to check dirty status and open a message window.
        
        Returns
        
            flag indicating successful reset of the dirty flag (boolean)
        """
        if self.isModified():
            fn = self.fileName
            if fn is None:
                fn = self.trUtf8('Noname')
            res = QMessageBox.warning(self.vm, 
                self.trUtf8("File Modified"),
                self.trUtf8("The file <b>%1</b> has unsaved changes.")
                    .arg(fn),
                self.trUtf8("&Save"), self.trUtf8("&Abort"), None, 0, 1)
            if res == 0:
                ok, newName = self.saveFile()
                if ok:
                    self.vm.setEditorName(self, newName)
                return ok
            elif res == 1:
                return 0
        
        return 1
        
    def revertToUnmodified(self):
        """
        Private method to revert back to the last saved state.
        """
        while self.isModified():
            self.undo()
        
    def handleBookmark(self, line):
        """
        Public method to toggle a bookmark.
        
        Arguments
        
            line -- line number of the bookmark
        """
        for handle in self.bookmarks:
            if self.markerLine(handle) == line-1:
                break
        else:
            # set a new bookmark
            handle = self.markerAdd(line-1, self.bookmark)
            self.bookmarks.append(handle)
            self.emit(PYSIGNAL('bookmarkToggled'), (self,))
            return
          
        self.bookmarks.remove(handle)
        self.markerDeleteHandle(handle)
        self.emit(PYSIGNAL('bookmarkToggled'), (self,))
        
    def getBookmarks(self):
        """
        Public method to retrieve the bookmarks.
        
        Returns
        
            sorted list of all lines containing a bookmark
            (list of integer)
        """
        bmlist = []
        for handle in self.bookmarks:
            bmlist.append(self.markerLine(handle)+1)
            
        bmlist.sort()
        return bmlist
        
    def hasBookmarks(self):
        """
        Public method to check for the presence of bookmarks.
        
        Returns
        
            flag indicating the presence of bookmarks (boolean)
        """
        return len(self.bookmarks) > 0
    
    #################################################################
    ## Methods needed by the context menu
    #################################################################
    
    def marginsWidth(self):
        """
        Private method to determin the width of all margins.
        
        Returns
        
            width of all margins in pixels (integer)
        """
        return self.marginWidth(0) + self.marginWidth(1) + self.marginWidth(2)
        
    def contextMenuEvent(self, evt):
        """
        Private method implementing the context menu event.
        
        Arguments
        
            evt -- the context menu event (QContextMenuEvent)
        """
        evt.accept()
        self.line = self.lineAt(evt.pos())
        if evt.x() > self.marginsWidth():
            self.menu.popup(evt.globalPos())
        else:
            self.marginMenu.popup(evt.globalPos())
        
    def handleShowContextMenu(self):
        """
        Private slot handling the aboutToShow signal of the context menu.
        """
        self.menu.setItemEnabled(self.menuIds["Save"], self.isModified())
        self.menu.setItemEnabled(self.menuIds["Undo"], self.isUndoAvailable())
        self.menu.setItemEnabled(self.menuIds["Redo"], self.isRedoAvailable())
        self.menu.setItemEnabled(self.menuIds["Revert"], self.isModified())
        self.menu.setItemEnabled(self.menuIds["Cut"], self.hasSelectedText())
        self.menu.setItemEnabled(self.menuIds["Copy"], self.hasSelectedText())
        self.menu.setItemEnabled(self.menuIds["PrintSel"], self.hasSelectedText())
        self.menu.setItemEnabled(self.menuIds["acAPI"], self.acAPI)
        if self.fileName and self.fileName.endswith(".py"):
            self.menu.setItemEnabled(self.menuIds["Check"], 1)
            self.menu.setItemEnabled(self.menuIds["Show"], 1)
        else:
            self.menu.setItemEnabled(self.menuIds["Check"], 0)
            self.menu.setItemEnabled(self.menuIds["Show"], 0)
        if self.lexer is not None:
            self.menu.setItemEnabled(self.menuIds["Comment"], self.lexer.canBlockComment())
            self.menu.setItemEnabled(self.menuIds["Uncomment"], self.lexer.canBlockComment())
            self.menu.setItemEnabled(self.menuIds["StreamComment"], self.lexer.canStreamComment())
            self.menu.setItemEnabled(self.menuIds["BoxComment"], self.lexer.canBoxComment())
        else:
            self.menu.setItemEnabled(self.menuIds["Comment"], 0)
            self.menu.setItemEnabled(self.menuIds["Uncomment"], 0)
            self.menu.setItemEnabled(self.menuIds["StreamComment"], 0)
            self.menu.setItemEnabled(self.menuIds["BoxComment"], 0)
        
    def handleShowShowMenu(self):
        """
        Private slot called before the show menu is shown.
        """
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.getFileName()):
            fn = project.getMainScript(1)
        else:
            fn = self.getFileName()
            
        if fn is not None:
            basename = os.path.splitext(fn)[0]
            self.showMenu.setItemEnabled(self.profileMenuItem, 
                glob.glob("%s/*.profile" % os.path.dirname(basename)) > 0)
            self.showMenu.setItemEnabled(self.coverageMenuItem,
                glob.glob("%s/*.coverage" % os.path.dirname(basename)) > 0)
        else:
            self.showMenu.setItemEnabled(self.profileMenuItem, 0)
            self.showMenu.setItemEnabled(self.coverageMenuItem, 0)
        
    def handleShowMarginContextMenu(self):
        """
        Private slot handling the aboutToShow signal of the margins context menu.
        """
        if self.fileName and self.fileName.endswith(".py"):
            self.marginMenu.setItemEnabled(self.marginMenuIds["Breakpoint"], 1)
            self.marginMenu.setItemEnabled(self.marginMenuIds["TempBreakpoint"], 1)
            if self.markersAtLine(self.line) & self.breakpointMask:
                self.marginMenu.setItemEnabled(self.marginMenuIds["EditBreakpoint"], 1)
                self.marginMenu.setItemEnabled(self.marginMenuIds["EnableBreakpoint"], 1)
            else:
                self.marginMenu.setItemEnabled(self.marginMenuIds["EditBreakpoint"], 0)
                self.marginMenu.setItemEnabled(self.marginMenuIds["EnableBreakpoint"], 0)
            if self.markersAtLine(self.line) & (1 << self.dbreakpoint):
                self.marginMenu.changeItem(self.marginMenuIds["EnableBreakpoint"],
                    self.trUtf8('Enable breakpoint'))
            else:
                self.marginMenu.changeItem(self.marginMenuIds["EnableBreakpoint"],
                    self.trUtf8('Disable breakpoint'))
            if self.breaks:
                self.marginMenu.setItemEnabled(self.marginMenuIds["NextBreakpoint"], 1)
                self.marginMenu.setItemEnabled(self.marginMenuIds["PreviousBreakpoint"], 1)
                self.marginMenu.setItemEnabled(self.marginMenuIds["ClearBreakpoint"], 1)
            else:
                self.marginMenu.setItemEnabled(self.marginMenuIds["NextBreakpoint"], 0)
                self.marginMenu.setItemEnabled(self.marginMenuIds["PreviousBreakpoint"], 0)
                self.marginMenu.setItemEnabled(self.marginMenuIds["ClearBreakpoint"], 0)
        else:
            self.marginMenu.setItemEnabled(self.marginMenuIds["Breakpoint"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["TempBreakpoint"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["EditBreakpoint"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["EnableBreakpoint"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["NextBreakpoint"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["PreviousBreakpoint"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["ClearBreakpoint"], 0)
            
        if self.bookmarks:
            self.marginMenu.setItemEnabled(self.marginMenuIds["NextBookmark"], 1)
            self.marginMenu.setItemEnabled(self.marginMenuIds["PreviousBookmark"], 1)
            self.marginMenu.setItemEnabled(self.marginMenuIds["ClearBookmark"], 1)
        else:
            self.marginMenu.setItemEnabled(self.marginMenuIds["NextBookmark"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["PreviousBookmark"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["ClearBookmark"], 0)
        
    def handleContextSave(self):
        """
        Private slot handling the save context menu entry.
        """
        ok, newName = self.saveFile()
        if ok:
            self.vm.setEditorName(self, newName)
        
    def handleContextSaveAs(self):
        """
        Private slot handling the save as context menu entry.
        """
        ok, newName = self.saveFileAs()
        if ok:
            self.vm.setEditorName(self, newName)
        
    def handleContextClose(self):
        """
        Private slot handling the close context menu entry.
        """
        self.vm.closeEditor(self)
    
    def handleSelectAll(self):
        """
        Private slot handling the select all context menu action.
        """
        self.selectAll(1)
    
    def handleDeselectAll(self):
        """
        Private slot handling the deselect all context menu action.
        """
        self.selectAll(0)

    def handleTabnanny(self):
        """
        Private method to handle the tabnanny context menu action.
        """
        if not self.checkDirty():
            return
            
        self.tabnanny = TabnannyDialog(self.vm)
        self.tabnanny.show()
        self.tabnanny.start(self.fileName)
        
    def handleSyntaxCheck(self):
        """
        Private method to handle the syntax check context menu action.
        """
        if not self.checkDirty():
            return
            
        self.syntaxcheck = SyntaxCheckerDialog(self.vm)
        self.syntaxcheck.show()
        self.syntaxcheck.start(self.fileName)
        
    def handleCodeMetrics(self):
        """
        Private method to handle the code metrics context menu action.
        """
        if not self.checkDirty():
            return
            
        self.codemetrics = CodeMetricsDialog()
        self.codemetrics.show()
        self.codemetrics.start(self.fileName)
        
    def handleCodeCoverage(self):
        """
        Private method to handle the code coverage context menu action.
        """
        if not self.checkDirty():
            return
            
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.fileName):
            fn = project.getMainScript(1)
        else:
            covfiles = glob.glob("%s/*.coverage" % os.path.dirname(self.fileName))
            if covfiles:
                if len(covfiles) > 1:
                    covlist = QStringList()
                    for f in covfiles:
                        covlist.append(f)
                    fn, ok = QInputDialog.getItem(\
                        self.trUtf8("Code Coverage"),
                        self.trUtf8("Please select a coverage file"),
                        covlist,
                        0, 0)
                    if not ok:
                        return
                    fn = str(fn)
                else:
                    fn = covfiles[0]
            else:
                return
            
        self.codecoverage = PyCoverageDialog()
        self.codecoverage.show()
        self.codecoverage.start(fn, self.fileName)
        
    def handleProfileData(self):
        """
        Private method to handle the show profile data context menu action.
        """
        if not self.checkDirty():
            return
            
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.fileName):
            fn = project.getMainScript(1)
        else:
            proffiles = glob.glob("%s/*.profile" % os.path.dirname(self.fileName))
            if proffiles:
                if len(proffiles) > 1:
                    proflist = QStringList()
                    for f in proffiles:
                        proflist.append(f)
                    fn, ok = QInputDialog.getItem(\
                        self.trUtf8("Profile Data"),
                        self.trUtf8("Please select a profile file"),
                        proflist,
                        0, 0)
                    if not ok:
                        return
                    fn = str(fn)
                else:
                    fn = proffiles[0]
            else:
                return
            
        self.profiledata = PyProfileDialog()
        self.profiledata.show()
        self.profiledata.start(fn, self.fileName)
        
    def handleLMBbookmarks(self):
        """
        Private method to handle the 'LMB toggles bookmark' context menu action.
        """
        self.marginMenu.setItemChecked(self.marginMenuIds["LMBbookmarks"], 1)
        self.marginMenu.setItemChecked(self.marginMenuIds["LMBbreakpoints"], 0)
        
    def handleLMBbreakpoints(self):
        """
        Private method to handle the 'LMB toggles breakpoint' context menu action.
        """
        self.marginMenu.setItemChecked(self.marginMenuIds["LMBbookmarks"], 0)
        self.marginMenu.setItemChecked(self.marginMenuIds["LMBbreakpoints"], 1)
        
    def handleToggleBookmark(self):
        """
        Private slot to handle the 'Toggle bookmark' context menu action.
        """
        if self.line < 0:
            self.line, index = self.getCursorPosition()
        self.line += 1
        self.handleBookmark(self.line)
        self.line = -1
        
    def handleNextBookmark(self):
        """
        Private slot to handle the 'Next bookmark' context menu action.
        """
        line, index = self.getCursorPosition()
        if line == self.lines()-1:
            line = 0
        else:
            line += 1
        bmline = self.markerFindNext(line, 1 << self.bookmark)
        if bmline < 0:
            # wrap around
            bmline = self.markerFindNext(0, 1 << self.bookmark)
        if bmline >= 0:
            self.setCursorPosition(bmline, 0)
            self.ensureLineVisible(bmline)
        
    def handlePreviousBookmark(self):
        """
        Private slot to handle the 'Previous bookmark' context menu action.
        """
        line, index = self.getCursorPosition()
        if line == 0:
            line = self.lines()-1
        else:
            line -= 1
        bmline = self.markerFindPrevious(line, 1 << self.bookmark)
        if bmline < 0:
            # wrap around
            bmline = self.markerFindPrevious(self.lines()-1, 1 << self.bookmark)
        if bmline >= 0:
            self.setCursorPosition(bmline, 0)
            self.ensureLineVisible(bmline)
        
    def handleClearBookmarks(self):
        """
        Private slot to handle the 'Clear all bookmarks' context menu action.
        """
        for handle in self.bookmarks:
            self.markerDeleteHandle(handle)
        self.bookmarks = []
    
    #################################################################
    ## Macro handling methods
    #################################################################
    
    def getMacroName(self):
        """
        Private method to select a macro name from the list of macros.
        
        Returns
        
            Tuple of macro name and a flag, indicating, if the user pressed ok or
            canceled the operation. (QString, boolean)
        """
        qs = QStringList()
        for s in self.macros.keys():
            qs.append(s)
        qs.sort()
        return QInputDialog.getItem(\
            self.trUtf8("Macro Name"),
            self.trUtf8("Select a macro name:"),
            qs, 0, 0, self)
        
    def handleRunMacro(self):
        """
        Public method to execute a macro.
        """
        name, ok = self.getMacroName()
        if ok and not name.isEmpty():
            self.macros[str(name)].play()
        
    def handleDeleteMacro(self):
        """
        Public method to delete a macro.
        """
        name, ok = self.getMacroName()
        if ok and not name.isEmpty():
            del self.macros[str(name)]
        
    def handleLoadMacro(self):
        """
        Public method to load a macro from a file.
        """
        configDir = Preferences.getConfigDir()
        fname = QFileDialog.getOpenFileName(configDir,
            self.trUtf8("Macro files (*.macro)"),
            self, None,
            self.trUtf8("Load macro file"))
            
        if fname.isEmpty():
            return  # user aborted
            
        try:
            f = open(str(fname), "rb")
            lines = f.readlines()
            f.close()
        except IOError:
            QMessageBox.critical(self,
                self.trUtf8("Error loading macro"),
                self.trUtf8("The macro file <b>%1</b> could not be read.")
                    .arg(fname),
                self.trUtf8("OK"))
            return
            
        if len(lines) != 2:
            QMessageBox.critical(self,
                self.trUtf8("Error loading macro"),
                self.trUtf8("The macro file <b>%1</b> is corrupt.")
                    .arg(fname),
                self.trUtf8("OK"))
            return
            
        macro = QextScintillaMacro(lines[1], self)
        self.macros[lines[0].strip()] = macro
        
    def handleSaveMacro(self):
        """
        Public method to save a macro to a file.
        """
        configDir = Preferences.getConfigDir()
        
        name, ok = self.getMacroName()
        if not ok or name.isEmpty():
            return  # user abort
        name = str(name)
        
        selectedFilter = QString('')
        fname = QFileDialog.getSaveFileName(configDir,
            self.trUtf8("Macro files (*.macro)"),
            self, None,
            self.trUtf8("Save macro file"), selectedFilter, 0)
            
        if fname.isEmpty():
            return  # user aborted
            
        ext = QFileInfo(fname).extension()
        if ext.isEmpty():
            ex = selectedFilter.section('(*',1,1).section(')',0,0)
            if not ex.isEmpty():
                fname.append(ex)
        if QFileInfo(fname).exists():
            abort = QMessageBox.warning(self,
                self.trUtf8("Save macro"),
                self.trUtf8("The macro file <b>%1</b> already exists.")
                    .arg(fname),
                self.trUtf8("&Overwrite"),
                self.trUtf8("&Abort"), None, 1)
            if abort:
                return
        fname = str(QDir.convertSeparators(fname))
        
        try:
            f = open(fname, "wb")
            f.write("%s%s" % (name, os.linesep))
            f.write(str(self.macros[name].save()))
            f.close()
        except IOError:
            QMessageBox.critical(self,
                self.trUtf8("Error saving macro"),
                self.trUtf8("The macro file <b>%1</b> could not be written.")
                    .arg(fname),
                self.trUtf8("OK"))
            return
        
    def handleStartMacroRecording(self):
        """
        Public method to start macro recording.
        """
        if self.recording:
            res = QMessageBox.warning(self, 
                self.trUtf8("Start Macro Recording"),
                self.trUtf8("Macro recording is already active."),
                self.trUtf8("&Start new"), self.trUtf8("&Cancel"))
            if res == 0:
                self.handleStopMacroRecording()
            else:
                return
        else:
            self.recording = 1
            
        self.curMacro = QextScintillaMacro(self)
        self.curMacro.startRecording()
        
    def handleStopMacroRecording(self):
        """
        Public method to stop macro recording.
        """
        if not self.recording:
            return      # we are not recording
            
        self.curMacro.endRecording()
        self.recording = 0
        
        name, ok = QInputDialog.getText(self.trUtf8("Macro Recording"),
            self.trUtf8("Enter name of the macro:"))
            
        if ok and not name.isEmpty():
            self.macros[str(name)] = self.curMacro
            
        self.curMacro = None
        
    #################################################################
    ## Overwritten methods
    #################################################################
    
    def close(self, alsoDelete=0):
        """
        Public method called when the window gets closed.
        
        This overwritten method redirects the action to our
        ViewManager.closeEditor, which in turn calls our closeIt
        method.
        
        Arguments
        
            alsoDelete -- ignored
        """
        return self.vm.closeEditor(self)
        
    def closeIt(self):
        """
        Public method called by the viewmanager to finally get rid of us.
        """
        QextScintilla.close(self, 1)
        
    def focusInEvent(self, event):
        """
        Public method called when the editor receives focus.
        
        Arguments
        
            event -- the event object (QFocusEvent)
        """
        self.vm.editorActGrp.setEnabled(1)
        self._updateReadOnly(0)
        if self.vm.editorsCheckFocusInEnabled() and \
           not self.inReopenPrompt and self.fileInfo is not None and \
           self.fileInfo.lastModified().toString().compare(self.lastModified.toString()):
            self.inReopenPrompt = 1
            msg = self.trUtf8("""The file <b>%1</b> has been changed while it was opened in"""
                    """ eric3.""").arg(self.fileName)
            if self.isModified():
                msg.append(self.trUtf8("""<br><b>Warning:</b> You will loose"""
                    """ your changes upon reopening it."""))
            res = QMessageBox.warning(None,
                self.trUtf8("File changed"), msg,
                self.trUtf8("&OK"), self.trUtf8("&Reopen"),
                None, 0, -1)
            self.inReopenPrompt = 0
            if res == 1:
                self.readFile(self.fileName)
                self.setModified(0)
                self.gotoLine(0)
                self.emit(PYSIGNAL('editorSaved'), (self.fileName,))
            else:
                # do not prompt for this change again...
                self.lastModified = self.fileInfo.lastModified()
                
        QextScintilla.focusInEvent(self, event)
        
    def focusOutEvent(self, event):
        """
        Public method called when the editor loses focus.
        
        Arguments
        
            event -- the event object (QFocusEvent)
        """
        self.vm.editorActGrp.setEnabled(0)
        
    def lineAt(self, pos):
        """
        Public method to calculate the line at a position.
        
        This variant is able to calculate the line for positions in the
        margins and for empty lines.
        
        Arguments
        
            pos -- position to calculate the line for (QPoint)
            
        Returns
        
            linenumber at position or -1, if there is no line at pos
            (integer, zero based)
        """
        textHeight = self.SendScintilla(QextScintilla.SCI_TEXTHEIGHT, 0)
        firstVisibleLine = self.SendScintilla(QextScintilla.SCI_GETFIRSTVISIBLELINE)
        line = firstVisibleLine + pos.y() / textHeight
        if line >= self.lines():
            line = -1
        return line
        
    def zoomIn(self, zoom = 1):
        """
        Public method used to increase the zoom factor.
        
        Arguments
        
            zoom -- zoom factor increment
        """
        self.zoom += zoom
        QextScintilla.zoomIn(self, zoom)
        
    def zoomOut(self, zoom = 1):
        """
        Public method used to decrease the zoom factor.
        
        Arguments
        
            zoom -- zoom factor decrement
        """
        self.zoom -= zoom
        QextScintilla.zoomOut(self, zoom)
        
    def zoomTo(self, zoom):
        """
        Public method used to zoom to a specific zoom factor.
        
        Arguments
        
            zoom -- zoom factor
        """
        self.zoom = zoom
        QextScintilla.zoomTo(self, zoom)
        
    def getZoom(self):
        """
        Public method used to retrieve the current zoom factor.
        
        Returns
        
            zoom factor (int)
        """
        return self.zoom
        
    def eventFilter(self, object, event):
        """
        Private method called to filter an event.
        
        Arguments:
        
            object -- object, that generated the event (QObject)
            
            event -- the event, that was generated by object (QEvent)
            
        Returns:
        
            flag indicating if event was filtered out
        """
        if event.type() == QEvent.MouseButtonPress:
            self.vm.eventFilter(self, event)
        return QextScintilla.eventFilter(self, object, event)

    def _updateReadOnly(self, bForce=1):
        """
        Private method to update the readOnly information for this editor. 
        
        If bForce is True, then updates everything regardless if
        the attributes have actually changed, such as during
        initialization time.  A signal is emitted after the
        caption change.

        Arguments

            bForce -- 1 to force change, 0 to only update and emit
                signal if there was an attribute change.
        """
        if self.fileName is None:
            return
        readOnly = not QFileInfo(self.fileName).isWritable() and 1 or 0
        if not bForce and (readOnly == self.isReadOnly()):
            return
        cap = self.fileName
        if readOnly:
            cap = "%s (ro)" % str(cap)
        self.setReadOnly(readOnly)
        self.setCaption(cap)
        self.emit(PYSIGNAL('captionChanged'), (cap, self))
        
    def refresh(self):
        """
        Public slot to refresh the editor contents.
        """
        self.readFile(self.fileName)
        self.setModified(0)
        self.emit(PYSIGNAL('editorSaved'), (self.fileName,))
