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

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

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

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

from KdeQt import KQFileDialog, KQMessageBox, KQInputDialog

from QextScintillaCompat import QextScintillaCompat, QSCINTILLA_VERSION

from Debugger.EditBreakpointDialog import EditBreakpointDialog
from DebugClients.Python.PyCoverage import coverage
from Checks.TabnannyDialog import TabnannyDialog
from UI.CodeMetricsDialog import CodeMetricsDialog
from UI.PyCoverageDialog import PyCoverageDialog
from UI.PyProfileDialog import PyProfileDialog
from Printer import Printer
import Preferences
import Utilities
import UI.PixmapCache

class Editor(QextScintillaCompat):
    """
    Class implementing the editor component of the eric3 IDE.
    
    @signal modificationStatusChanged(boolean, editor) emitted when the
            modification status has changed
    @signal undoAvailable(boolean) emitted to signal the undo availability
    @signal redoAvailable(boolean) emitted to signal the redo availability
    @signal cursorChanged(string, int, int) emitted when the cursor position
            was changed
    @signal editorSaved(string) emitted after the editor has been saved
    @signal editorRenamed(string) emitted after the editor got a new name
            (i.e. after a 'Save As')
    @signal captionChanged(string, editor) emitted when the caption is
            updated. Typically due to a readOnly attribute change.
    @signal breakpointToggled(editor) emitted when a breakpoint is toggled
    @signal breakpointEnabledToggled(editor) emitted when a breakpoint's enabled
            status is toggled
    @signal bookmarkToggled(editor) emitted when a bookmark is toggled
    @signal syntaxerrorToggled(editor) emitted when a syntax error was discovered
    @signal autoCompletionAPIsAvailable(avail) emitted after the autocompletion
            function has been configured
    @signal coverageMarkersShown(boolean) emitted after the coverage markers have been 
            shown or cleared
    """
    def __init__(self, dbs, fn=None, parent=None, name=None, flags=0,
                 isPythonFile=0, editor=None, tv=None):
        """
        Constructor
        
        @param dbs reference to the debug server object
        @param fn name of the file to be opened (string). If it is None,
                a new (empty) editor is opened
        @param parent parent widget (viewmanager) of this editor (QWidget)
        @param name name of this editor (string or QString)
        @param flags window flags
        @param isPythonFile flag indicating that this is a Python file
                even if it doesn't have the .py extension (boolean)
        @param editor reference to an Editor object, if this is a cloned view
        @param tv reference to the task viewer object
        """
        QextScintillaCompat.__init__(self, parent, name, flags | Qt.WDestructiveClose)
        self.setUtf8(1)
        
        self.pyExtensions = ['py', 'ptl']
        self.rbExtensions = ['rb']
        
        self.dbs = dbs
        self.taskViewer = tv
        self.fileName = fn
        self.vm = parent
        self.refactoring = qApp.mainWidget().getRefactoring()
        self.isPythonFile = isPythonFile
        
        # 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.syntaxerrors = {}  # key:   marker handle
                                # value: error message
        self.notcoveredMarkers = [] # just a list of marker handles
        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 if the prompt to reload a changed source is present
        self.inDragDrop = 0     # true if we are in drop mode
            
        self.macros = {}    # list of defined macros
        self.curMacro = None
        self.recording = 0
        
        # clear QScintilla defined keyboard commands
        # we do our own handling through the view manager
        self.clearAlternateKeys()
        
        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(UI.PixmapCache.getPixmap("break.png"))
            self.cbreakpoint = self.markerDefine(UI.PixmapCache.getPixmap("cBreak.png"))
            self.tbreakpoint = self.markerDefine(UI.PixmapCache.getPixmap("tBreak.png"))
            self.tcbreakpoint = self.markerDefine(UI.PixmapCache.getPixmap("tCBreak.png"))
            self.dbreakpoint = self.markerDefine(UI.PixmapCache.getPixmap("breakDisabled.png"))
            self.bookmark = self.markerDefine(UI.PixmapCache.getPixmap("bookmark.png"))
            self.syntaxerror = self.markerDefine(UI.PixmapCache.getPixmap("syntaxError.png"))
            self.notcovered = self.markerDefine(UI.PixmapCache.getPixmap("notcovered.png"))
            self.taskmarker = self.markerDefine(UI.PixmapCache.getPixmap("task.png"))
        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)
            
            self.syntaxerror = self.markerDefine(QextScintilla.RightArrow)
            self.setMarkerForegroundColor(QColor("#FF0000"), self.syntaxerror)
            self.setMarkerBackgroundColor(QColor("#FF0000"), self.syntaxerror)
            
            self.notcovered = self.markerDefine(QextScintilla.RightArrow)
            self.setMarkerForegroundColor(QColor("#00FF00"), self.notcovered)
            self.setMarkerBackgroundColor(QColor("#00FF00"), self.notcovered)
            
            self.taskmarker = self.markerDefine(QextScintilla.Rectangle)
            self.setMarkerForegroundColor(QColor("#00FF00"), self.taskmarker)
            self.setMarkerBackgroundColor(QColor("#00FF00"), self.taskmarker)
        
        # 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) | \
                      (1 << self.syntaxerror) | \
                      (1 << self.notcovered) | \
                      (1 << self.taskmarker)
        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)
            if editor is None:
                self.readFile(self.fileName)
                self.autoSyntaxCheck()
            else:
                # clone the given editor
                self.setDocument(editor.document())
                self.breaks = editor.breaks
                self.bookmarks = editor.bookmarks
                self.syntaxerrors = editor.syntaxerrors
                self.notcoveredMarkers = editor.notcoveredMarkers
                self.setModified(editor.isModified())
                self.lastModified = self.fileInfo.lastModified()
            self.gotoLine(0)
            line0 = self.text(0)
            if line0.startsWith("#!") and line0.contains("python"):
                self.isPythonFile = 1
            if self.isPythonFile:
                bindName = 'dummy.py'
            else:
                bindName = self.fileName
            if line0.startsWith("<?xml"):
                # override extension for XML files
                bindName = "dummy.xml"
            if line0.startsWith("#!"):
                if (line0.contains("/bash") or line0.contains("/sh")):
                    bindName = "dummy.sh"
                elif line0.contains("ruby"):
                    bindName = "dummy.rb"
                elif line0.contains("perl"):
                    bindName = "dummy.pl"
                elif line0.contains("lua"):
                    bindName = "dummy.lua"
                
            self.bindLexer(bindName)
        elif editor is not None:
            self.setDocument(editor.document())
        
        # 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>"""
            """<p>In order to set bookmarks just Shift click in the space between"""
            """ the line numbers and the fold markers.</p>"""
            """<p>These actions can be reversed via the context menu.</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)
                
        # set the autosave flag
        self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
        self.autosaveManuallyDisabled = 0
        
        self.initContextMenu()
        self.initMarginContextMenu()
        
        self.checkLanguage()
        
        self.coverageMarkersShown = 0   # flag remembering the current status of the
                                        # code coverage markers
        
    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.refactoringMenu = self.initContextMenuRefactoring()
        
        self.menuIds["Undo"] = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("editUndo.png")),
            self.trUtf8('Undo'), self.undo)
        self.menuIds["Redo"] = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("editRedo.png")),
            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(UI.PixmapCache.getPixmap("editCut.png")),
            self.trUtf8('Cut'), self.cut)
        self.menuIds["Copy"] = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("editCopy.png")),
            self.trUtf8('Copy'), self.copy)
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("editPaste.png")),
            self.trUtf8('Paste'), self.paste)
        self.menu.insertSeparator()
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("editIndent.png")),
            self.trUtf8('Indent'), self.indentLineOrSelection)
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("editUnindent.png")),
            self.trUtf8('Unindent'), self.unindentLineOrSelection)
        self.menuIds["Comment"] = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("editComment.png")),
                self.trUtf8('Comment'), self.commentLineOrSelection)
        self.menuIds["Uncomment"] = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("editUncomment.png")),
                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.menu.insertItem(self.trUtf8('Shorten empty lines'), self.handleShortenEmptyLines)
        self.menu.insertSeparator()
        self.menuIds["Languages"] = \
            self.menu.insertItem(self.trUtf8("Languages"), self.languagesMenu)
        self.menuIds["MonospacedFont"] = \
            self.menu.insertItem(self.trUtf8("Use Monospaced Font"),
                self.handleMonospacedEnable)
        self.menu.setItemChecked(self.menuIds["MonospacedFont"], 
            self.useMonospaced)
        self.menuIds["AutosaveEnable"] = \
            self.menu.insertItem(self.trUtf8("Autosave enabled"),
                self.handleAutosaveEnable)
        self.menu.setItemChecked(self.menuIds["AutosaveEnable"], 
            self.autosaveEnabled)
        self.menuIds["AutoCompletionEnable"] = \
            self.menu.insertItem(self.trUtf8("Autocompletion enabled"),
                self.handleAutoCompletionEnable)
        self.menu.setItemChecked(self.menuIds["AutoCompletionEnable"],
            self.autoCompletionThreshold() != -1)
        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["Refactoring"] = \
            self.menu.insertItem(self.trUtf8('Refactoring'), self.refactoringMenu)
        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()
        if QSCINTILLA_VERSION() > 0x010200:
            self.menu.insertItem(self.trUtf8('New view'), self.handleNewView)
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("close.png")),
            self.trUtf8('Close'), self.handleContextClose)
        self.menu.insertSeparator()
        self.menuIds["Save"] = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("fileSave.png")),
            self.trUtf8('Save'), self.handleContextSave)
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("fileSaveAs.png")),
            self.trUtf8('Save As...'), self.handleContextSaveAs)
        self.menu.insertSeparator()
        self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("print.png")),
            self.trUtf8('Print'), self.printFile)
        self.menuIds["PrintSel"] = \
            self.menu.insertItem(QIconSet(UI.PixmapCache.getPixmap("print.png")),
            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.coverageShowAnnotationMenuItem = \
            menu.insertItem(self.trUtf8('Show code coverage annotations'), 
                self.handleCodeCoverageShowAnnotations)
        self.coverageHideAnnotationMenuItem = \
            menu.insertItem(self.trUtf8('Hide code coverage annotations'), 
                self.handleCodeCoverageHideAnnotations)
        self.profileMenuItem = \
            menu.insertItem(self.trUtf8('Profile data...'), self.handleProfileData)
        self.cyclopsMenuItem = \
            menu.insertItem(self.trUtf8('Cyclops report...'), self.handleCyclopsReport)
        self.removeCyclopsMenuItem = \
            menu.insertItem(self.trUtf8('Remove cyclops report'), 
                self.handleRemoveCyclopsReport)
        self.connect(menu, SIGNAL('aboutToShow()'), self.handleShowShowMenu)
        
        return menu
        
    def initContextMenuRefactoring(self):
        """
        Private method used to setup the refactoring context sub menu.
        """
        menu = QPopupMenu()
        
        menu.insertItem(self.trUtf8('Find References'), 
                        self.refactoring.handleQueryReferences)
        menu.insertItem(self.trUtf8('Find Definition'), 
                        self.refactoring.handleQueryDefinition)
        
        return menu

    def initContextMenuLanguages(self):
        """
        Private method used to setup the Languages context sub menu.
        """
        menu = QPopupMenu()
        menu.setCheckable(1)
        
        self.supportedLanguages = [\
            [self.trUtf8("Bash"), 'dummy.sh', -1, "Bash"],
            [self.trUtf8("Batch"), 'dummy.bat', -1, "Batch"],
            [self.trUtf8("C/C++"), 'dummy.cpp', -1, "C++"],
            [self.trUtf8("C#"), 'dummy.cs', -1, "C#"],
            [self.trUtf8("CSS"), 'dummy.css', -1, "CSS"],
            [self.trUtf8("Diff"), 'dummy.diff', -1, "Diff"],
            [self.trUtf8("HTML/PHP/XML"), 'dummy.html', -1, "HTML"],
            [self.trUtf8("IDL"), 'dummy.idl', -1, "IDL"],
            [self.trUtf8("Java"), 'dummy.java', -1, "Java"],
            [self.trUtf8("JavaScript"), 'dummy.js', -1, "JavaScript"],
            [self.trUtf8("Lua"), 'dummy.lua', -1, "Lua"],
            [self.trUtf8("Makefile"), 'dummy.mak', -1, "Makefile"],
            [self.trUtf8("Perl"), 'dummy.pl', -1, "Perl"],
            [self.trUtf8("Properties"), 'dummy.ini', -1, "Properties"],
            [self.trUtf8("Python"), 'dummy.py', -1, "Python"], 
            [self.trUtf8("Ruby"), 'dummy.rb', -1, "Ruby"],
            [self.trUtf8("SQL"), 'dummy.sql', -1, "SQL"],
            [self.trUtf8("TeX"), 'dummy.tex', -1, "TeX"],
        ]
        # adjust list to various QScintilla versions
        removeLanguages = []
        if QSCINTILLA_VERSION() < 0x010100:
            removeLanguages.extend(['dummy.html', 'dummy.sql'])
        if QSCINTILLA_VERSION() < 0x010300:
            removeLanguages.extend(['dummy.pl'])
        if QSCINTILLA_VERSION() < 0x010400:
            removeLanguages.extend(['dummy.sh'])
        if QSCINTILLA_VERSION() < 0x010500:
            removeLanguages.extend(['dummy.rb', 'dummy.lua'])
        if QSCINTILLA_VERSION() < 0x010600:
            removeLanguages.extend(['dummy.css', 'dummy.diff', 'dummy.mak', 
                                    'dummy.ini', 'dummy.tex', 'dummy.bat'])
        for lang in self.supportedLanguages[:]:
            if lang[1] in removeLanguages:
                self.supportedLanguages.remove(lang)
        
        idx = 0
        
        for lang in self.supportedLanguages:
            id = menu.insertItem(lang[0], self.handleLanguage)
            menu.setItemParameter(id, idx)
            self.supportedLanguages[idx][2] = id
            
            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["GotoSyntaxError"] = \
            self.marginMenu.insertItem(self.trUtf8('Goto syntax error'),
                self.handleGotoSyntaxError)
        self.marginMenuIds["ShowSyntaxError"] = \
            self.marginMenu.insertItem(self.trUtf8('Show syntax error message'),
                self.handleShowSyntaxError)
        self.marginMenuIds["ClearSyntaxError"] = \
            self.marginMenu.insertItem(self.trUtf8('Clear syntax error'),
                self.handleClearSyntaxError)
        self.marginMenu.insertSeparator()
        self.marginMenuIds["Breakpoint"] = \
            self.marginMenu.insertItem(self.trUtf8('Toggle breakpoint'), 
                self.handleToggleBreakpoint)
        self.marginMenuIds["TempBreakpoint"] = \
            self.marginMenu.insertItem(self.trUtf8('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["NextCoverageMarker"] = \
            self.marginMenu.insertItem(self.trUtf8('Next uncovered line'),
                self.handleNextUncovered)
        self.marginMenuIds["PreviousCoverageMarker"] = \
            self.marginMenu.insertItem(self.trUtf8('Previous uncovered line'),
                self.handlePreviousUncovered)
        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.
        
        @param idx index of the lexer language (int)
        """
        id = self.supportedLanguages[idx][2]
        if not self.languagesMenu.isItemChecked(id):
            self.setLanguage(self.supportedLanguages[idx][1])
        else:
            self.languagesMenu.setItemChecked(id, 0)
            self.resetLanguage()
        
    def resetLanguage(self):
        self.apiLanguage = ""
        self.lexer = None
        self.setLexer()
        self.setMonospaced(self.useMonospaced)
        
    def setLanguage(self, filename, initTextDisplay = 1):
        """
        Private method to set a lexer language.
        
        @param filename filename used to determine the associated lexer language (string)
        @param initTextDisplay flag indicating an initialization of the text display
            is required as well (boolean)
        """
        self.bindLexer(filename)
        self.recolor()
        self.checkLanguage()

        # set the text display
        if initTextDisplay:
            self.setTextDisplay()
        
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()
    
    def checkLanguage(self):
        """
        Private method to check the selected language of the language submenu.
        """
        # step 1: uncheck all languages
        for lang in self.supportedLanguages:
            id = lang[2]
            if self.languagesMenu.isItemChecked(lang[2]):
                self.languagesMenu.setItemChecked(lang[2], 0)
                
        if self.apiLanguage == "":
            return
            
        # step 2: check the selected language
        for lang in self.supportedLanguages:
            if self.apiLanguage == lang[3]:
                self.languagesMenu.setItemChecked(lang[2], 1)
                break

    def getExtension(self, fname):
        """
        Private method to get the fileextension without a leading '.'.
        
        @param fname filename (string)
        @return 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, filename):
        """
        Private slot to set the correct lexer depending on language.
        
        @param filename filename used to determine the associated lexer language (string)
        """
        filename = os.path.basename(unicode(filename))
        self.apiLanguage = Preferences.getEditorLexerAssoc(filename)
        
        if self.apiLanguage == "Python":
            from LexerPython import LexerPython
            self.lexer = LexerPython(self)
        elif self.apiLanguage == "C++":
            from LexerCPP import LexerCPP
            self.lexer = LexerCPP(self)
        elif self.apiLanguage == "C#":
            from LexerCSharp import LexerCSharp
            self.lexer = LexerCSharp(self)
        elif self.apiLanguage == "IDL":
            from LexerIDL import LexerIDL
            self.lexer = LexerIDL(self)
        elif self.apiLanguage == "Java":
            from LexerJava import LexerJava
            self.lexer = LexerJava(self)
        elif self.apiLanguage == "JavaScript":
            from LexerJavaScript import LexerJavaScript
            self.lexer = LexerJavaScript(self)
        elif self.apiLanguage == "SQL":
            try:
                from LexerSQL import LexerSQL
                self.lexer = LexerSQL(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "HTML":
            try:
                from LexerHTML import LexerHTML
                self.lexer = LexerHTML(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "Perl":
            try:
                from LexerPerl import LexerPerl
                self.lexer = LexerPerl(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "Bash":
            try:
                from LexerBash import LexerBash
                self.lexer = LexerBash(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "Ruby":
            try:
                from LexerRuby import LexerRuby
                self.lexer = LexerRuby(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "Lua":
            try:
                from LexerLua import LexerLua
                self.lexer = LexerLua(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "CSS":
            try:
                from LexerCSS import LexerCSS
                self.lexer = LexerCSS(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "TeX":
            try:
                from LexerTeX import LexerTeX
                self.lexer = LexerTeX(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "Diff":
            try:
                from LexerDiff import LexerDiff
                self.lexer = LexerDiff(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "Makefile":
            try:
                from LexerMakefile import LexerMakefile
                self.lexer = LexerMakefile(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "Properties":
            try:
                from LexerProperties import LexerProperties
                self.lexer = LexerProperties(self)
            except ImportError:
                self.apiLanguage = ""
                return
        elif self.apiLanguage == "Batch":
            try:
                from LexerBatch import LexerBatch
                self.lexer = LexerBatch(self)
            except ImportError:
                self.apiLanguage = ""
                return
        else:
            self.apiLanguage = ""
            return

        # get the font for style 0 and set it as the default font
        key = '/eric3/Scintilla/%s/style0/font' % unicode(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 self.apiLanguage == "Python":
            from qtext import QextScintillaLexerPython
            # Python lexer stuff
            if Preferences.getEditor("PythonBadIndentation"):
                self.lexer.setIndentationWarning(QextScintillaLexerPython.Inconsistent)
            else:
                self.lexer.setIndentationWarning(QextScintillaLexerPython.NoWarning)
            self.lexer.setFoldComments(Preferences.getEditor("PythonFoldComment"))
            self.lexer.setFoldQuotes(Preferences.getEditor("PythonFoldString"))
            if Preferences.getEditor("PythonAutoIndent"):
                self.lexer.setAutoIndentStyle(0)
            else:
                self.lexer.setAutoIndentStyle(QextScintilla.AiMaintain)
        elif self.apiLanguage in ["C++", "C#", "IDL", "Java", "JavaScript"]:
            # C++ lexer stuff
            self.lexer.setFoldComments(Preferences.getEditor("CppFoldComment"))
            self.lexer.setFoldPreprocessor(Preferences.getEditor("CppFoldPreprocessor"))
            try:
                self.lexer.setFoldAtElse(Preferences.getEditor("CppFoldAtElse"))
            except StandardError:
                pass
            indentStyle = 0
            if Preferences.getEditor("CppIndentOpeningBrace"):
                indentStyle |= QextScintilla.AiOpening
            if Preferences.getEditor("CppIndentClosingBrace"):
                indentStyle |= QextScintilla.AiClosing
            self.lexer.setAutoIndentStyle(indentStyle)
        elif self.apiLanguage == "SQL":
            # SQL lexer stuff
            try:
                self.lexer.setFoldComments(Preferences.getEditor("SqlFoldComment"))
                self.lexer.setBackslashEscapes(Preferences.getEditor("SqlBackslashEscapes"))
            except StandardError:
                pass
        elif self.apiLanguage == "HTML":
            # HTML lexer stuff
            try:
                self.lexer.setFoldPreprocessor(Preferences.getEditor("CppFoldPreprocessor"))
                self.lexer.setCaseSensitiveTags(Preferences.getEditor("HtmlCaseSensitiveTags"))
            except StandardError:
                pass
        elif self.apiLanguage == "Perl":
            # Perl lexer stuff
            try:
                self.lexer.setFoldComments(Preferences.getEditor("PerlFoldComment"))
            except StandardError:
                pass
        elif self.apiLanguage == "Bash":
            # Bash lexer stuff
            try:
                self.lexer.setFoldComments(Preferences.getEditor("BashFoldComment"))
            except StandardError:
                pass
        elif self.apiLanguage == "Ruby":
            # Ruby lexer stuff
            try:
                from qtext import QextScintillaLexerRuby
                if Preferences.getEditor("RubyBadIndentation"):
                    self.lexer.setIndentationWarning(QextScintillaLexerRuby.Inconsistent)
                else:
                    self.lexer.setIndentationWarning(QextScintillaLexerRuby.NoWarning)
            except StandardError:
                pass
        elif self.apiLanguage == "CSS":
            # SQL lexer stuff
            try:
                self.lexer.setFoldComments(Preferences.getEditor("CssFoldComment"))
            except StandardError:
                pass
            
        # stuff for all (most) lexers
        try:
            self.lexer.setFoldCompact(Preferences.getEditor("AllFoldCompact"))
        except StandardError:
            pass
        
    def getLexer(self):
        """
        Public method to retrieve a reference to the lexer object.
        
        @return 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.
        
        @param m modification status
        """
        if not m and self.fileInfo is not None:
            self.lastModified = self.fileInfo.lastModified()
        if Preferences.getEditor("AutoCheckSyntax"):
            self.handleClearSyntaxError()
        self.emit(PYSIGNAL('modificationStatusChanged'), (m, self))
        self.emit(PYSIGNAL('undoAvailable'), (self.isUndoAvailable(),))
        self.emit(PYSIGNAL('redoAvailable'), (self.isRedoAvailable(),))
        
    def handleCursorPositionChanged(self, line, pos):
        """
        Private slot to handle the cursorPositionChanged signal. 
        
        It emits the signal cursorChanged with parameters fileName, 
        line and pos.
        
        @param line line number of the cursor
        @param 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.
        """
        KQMessageBox.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"),
            QString.null,
            QString.null,
            0, -1)
        
    def getFileName(self):
        """
        Public method to return the name of the file being displayed.
        
        @return filename of the displayed file (string)
        """
        return self.fileName
        
    def isPyFile(self):
        """
        Public method to return a flag indicating a Python file.
        
        @return flag indicating a Python file (boolean)
        """
        return self.isPythonFile or \
            self.getExtension(self.fileName) in self.pyExtensions

    def isRubyFile(self):
        """
        Public method to return a flag indicating a Ruby file.
        
        @return flag indicating a Ruby file (boolean)
        """
        return self.getExtension(self.fileName) in self.rbExtensions

    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,syntaxError=0):
        """
        Public method to highlight (or de-highlight) a particular line.
        
        @param line line number to highlight
        @param error flag indicating whether the error highlight should be used
        @param syntaxError flag indicating a syntax error
        """
        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
            self.setCursorPosition(line-1, 0)

    def getHighlightPosition(self):
        """
        Public method to return the position of the highlight bar.
        
        @return 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.
        
        @param 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.
        
        @param line line number of the breakpoint
        @param 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.
        
        @param 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)
        self.emit(PYSIGNAL('breakpointEnabledToggled'), (self,))
        
    def setBreakpointIgnoreCount(self, line, count):
        """
        Public method to set a breakpoints ignore count.
        
        @param line line number of the breakpoint
        @param 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.
        
        @param line line number of the breakpoint
        @param 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.
        
        @param line line number of the breakpoint
        @param 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.
        
        @return 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.
        
        @return 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((self.fileName, ln), 
                (cond, temp, enabled, ignorecount),
                self.condHistory, self, modal = 1
            )
            if dlg.exec_loop() == QDialog.Accepted:
                cond, temp, enabled, ignorecount = dlg.getData()
                if not cond.isEmpty():
                    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.
        
        @return 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.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)
            if res:
                sb.message(self.trUtf8('Printing completed'), 2000)
            else:
                sb.message(self.trUtf8('Error while printing'), 2000)
            qApp.processEvents()
        else:
            sb.message(self.trUtf8('Printing aborted'), 2000)
            qApp.processEvents()
    
    def printSelection(self):
        """
        Public slot to print the selected text.
        """
        if not self.hasSelectedText():
            KQMessageBox.warning(self, self.trUtf8('Print Selection'),
                self.trUtf8('There is no selected text, printing aborted.'),
                self.trUtf8("&OK"))
            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.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 one line more than told
            res = printer.printRange(self, fromLine, toLine-1)
            if res:
                sb.message(self.trUtf8('Printing completed'), 2000)
            else:
                sb.message(self.trUtf8('Error while printing'), 2000)
            qApp.processEvents()
        else:
            sb.message(self.trUtf8('Printing aborted'), 2000)
            qApp.processEvents()
    
    def extractTasks(self):
        """
        Public slot to extract all tasks.
        """
        # save the current selection and cursor position
        lineFrom, indexFrom, lineTo, indexTo = self.getSelection()
        cline, cindex = self.getCursorPosition()
        _firstVisibleLine = self.firstVisibleLine()
        
        # clear all task markers and tasks
        self.markerDeleteAll(self.taskmarker)
        self.taskViewer.clearFileTasks(self.fileName)
        
        # now search tasks and record them
        # normal tasks first
        tasksMarkers = unicode(Preferences.getTasks("TasksMarkers"))
        for tasksMarker in tasksMarkers.split():
            try:
                ok = self.findFirst(r"%s .+$" % tasksMarker, 1, 1, 0, 0, 1, 0, 0, 0)
            except TypeError:
                ok = self.findFirst(r"%s .+$" % tasksMarker, 1, 1, 0, 0, 1, 0, 0)
            while ok:
                line = self.getSelection()[0]
                task = self.selectedText()
                self.markerAdd(line, self.taskmarker)
                self.taskViewer.addFileTask(task, self.fileName, line+1, 0)
                ok = self.findNext()
        # bugfix tasks second
        tasksMarkers = unicode(Preferences.getTasks("TasksMarkersBugfix"))
        for tasksMarker in tasksMarkers.split():
            try:
                ok = self.findFirst(r"%s .+$" % tasksMarker, 1, 1, 0, 0, 1, 0, 0, 0)
            except TypeError:
                ok = self.findFirst(r"%s .+$" % tasksMarker, 1, 1, 0, 0, 1, 0, 0)
            while ok:
                line = self.getSelection()[0]
                task = self.selectedText()
                self.markerAdd(line, self.taskmarker)
                self.taskViewer.addFileTask(task, self.fileName, line+1, 1)
                ok = self.findNext()
        
        # reset the current selection, cursor position and text display
        self.setSelection(lineFrom, indexFrom, lineTo, indexTo)
        self.setCursorPosition(cline, cindex)
        _nFirstVisibleLine = self.firstVisibleLine()
        if _nFirstVisibleLine != _firstVisibleLine:
            self.scrollVertical(_firstVisibleLine - _nFirstVisibleLine)
        
    def readFile(self, fn):
        """
        Public slot to read the text from a file.
        
        @param fn filename to read from (string or QString)
        """
        fn = unicode(fn)
        try:
            f = open(fn, 'rb')
        except:
            KQMessageBox.critical(self.vm, self.trUtf8('Open File'),
                self.trUtf8('<p>The file <b>%1</b> could not be opened.</p>')
                    .arg(fn))
            raise
            
        qApp.setOverrideCursor(Qt.waitCursor)
        
        if fn.endswith('.ts') or fn.endswith('.ui'):
            # special treatment for Qt-Linguist and Qt-Designer files
            txt = f.read()
            self.encoding = 'latin-1'
        else:
            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)
        self.setModified(0)
        
        f.close()
        
        self.extractTasks()
        
        qApp.restoreOverrideCursor()
        
        self.lastModified = self.fileInfo.lastModified()
    
    def writeFile(self, fn):
        """
        Public slot to write the text to a file.
        
        @param fn filename to write to (string or QString)
        @return flag indicating success
        """
        fn = unicode(fn)
        try:
            txt = Utilities.encode(unicode(self.text()), self.encoding)
        except Utilities.CodingError, e:
            KQMessageBox.critical(self, self.trUtf8('Save File'),
                self.trUtf8('<p>The file <b>%1</b> could not be saved.<br>Reason: %2</p>')
                    .arg(unicode(fn)).arg(repr(e)))
            return 0
        # 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
        createBackup = Preferences.getEditor("CreateBackupFile")
        if createBackup:
            if os.path.islink(fn):
                fn = os.path.realpath(fn)
            bfn = '%s~' % fn
            try:
                permissions = os.stat(fn).st_mode
                perms_valid = 1
            except:
                # if there was an error, ignore it
                perms_valid = 0
            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()
            if createBackup and perms_valid:
                os.chmod(fn, permissions)
            return 1
        except IOError, why:
            KQMessageBox.critical(self, self.trUtf8('Save File'),
                self.trUtf8('<p>The file <b>%1</b> could not be saved.<br>Reason: %2</p>')
                    .arg(unicode(fn)).arg(str(why)))
            return 0
        
    def saveFile(self, saveas = 0, path = None):
        """
        Public slot to save the text to a file.
        
        @param saveas flag indicating a 'save as' action
        @param path directory to save the file in (string or QString)
        @return 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:
            if (path is None or path is QString.null) and self.fileName is not None:
                path = os.path.dirname(unicode(self.fileName))
            if path is None:
                path = QString.null
            selectedFilter = QString('')
            fn = KQFileDialog.getSaveFileName(path,
                self.trUtf8("Python Files (*.py);;"
                    "Pyrex Files (*.pyx);;"
                    "Quixote Template Files (*.ptl);;"
                    "Ruby Files (*.rb);;"
                    "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);;"
                    "Perl Files (*.pl);;"
                    "Perl Module Files (*.pm);;"
                    "Lua Files (*.lua);;"
                    "Shell Files (*.sh);;"
                    "Batch Files (*.bat);;"
                    "TeX Files (*.tex);;"
                    "TeX Template Files (*.sty);;"
                    "Diff Files (*.diff);;"
                    "Make Files (*.mak);;"
                    "Properties Files (*.ini);;"
                    "Configuration Files (*.cfg);;"
                    "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 = KQMessageBox.warning(self,
                        self.trUtf8("Save File"),
                        self.trUtf8("<p>The file <b>%1</b> already exists.</p>")
                            .arg(fn),
                        self.trUtf8("&Overwrite"),
                        self.trUtf8("&Abort"), QString.null, 1)
                    if abort:
                        return (0, None)
                fn = unicode(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:
                self.setLanguage(self.fileName, 0)
                
            if self.fileInfo is None or saveas:
                self.fileInfo = QFileInfo(self.fileName)
                self.fileInfo.setCaching(0)
                self.emit(PYSIGNAL('editorRenamed'), (self.fileName,))
            self.lastModified = self.fileInfo.lastModified()
            if newName is not None:
                self.vm.addToRecentList(newName)
            self.emit(PYSIGNAL('editorSaved'), (self.fileName,))
            self.autoSyntaxCheck()
            self.extractTasks()
            return (1, self.fileName)
        else:
            return (0, None)
        
    def saveFileAs(self, path = None):
        """
        Public slot to save a file with a new name.
        
        @param path directory to save the file in (string or QString)
        @return tuple of two values (boolean, string) giving a success indicator and
            the name of the saved file
        """
        return self.saveFile(1, path)

    def handleRenamed(self, fn):
        """
        Public slot to handle the editorRenamed signal.
        
        @param fn filename to be set for the editor (QString or string).
        """
        self.fileName = unicode(fn)
        self.setCaption(self.fileName)
        
        if self.lexer is None:
            self.setLanguage(self.fileName, 0)
        
        if self.fileInfo is None:
            self.fileInfo = QFileInfo(self.fileName)
            self.fileInfo.setCaching(0)
        
        self.lastModified = self.fileInfo.lastModified()
        self.vm.setEditorName(self, self.fileName)
        self._updateReadOnly(1)
        
    def fileRenamed(self, fn):
        """
        Public slot to handle the editorRenamed signal.
        
        @param fn filename to be set for the editor (QString or string).
        """
        self.handleRenamed(fn)
        self.emit(PYSIGNAL('editorRenamed'), (self.fileName,))
        
    def ensureVisible(self, line):
        """
        Public slot to ensure, that the specified line is visible.
        
        @param line line number to make visible
        """
        self.ensureLineVisible(line-1)
        
    def ensureVisibleTop(self, line):
        """
        Public slot to ensure, that the specified line is visible at the top of the editor.
        
        @param line line number to make visible
        """
        topLine = self.firstVisibleLine()
        linesToScroll = line - topLine
        self.scrollVertical(linesToScroll)
        
    def handleMarginClicked(self, margin, line, state):
        """
        Private slot to handle the marginClicked signal.
        
        @param margin id of the clicked margin
        @param line line number pf the click
        @param state mouse button state
        """
        if margin == 1:
            if state & Qt.ShiftButton:
                if self.marginMenu.isItemChecked(self.marginMenuIds["LMBbreakpoints"]):
                    self.handleBookmark(line+1)
                else:
                    self.toggleBreakpoint(line+1)
            else:
                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. 
        
        @param indent flag indicating an indent operation
                <br />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. 
        
        @param indent flag indicating an indent operation
                <br />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.
        
        @param 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':
                if Preferences.getEditor("PythonAutoIndent"):
                    self.lexer.setAutoIndentStyle(0)
                else:
                    self.lexer.setAutoIndentStyle(QextScintilla.AiMaintain)
            elif self.lexer.language() in ['C++', 'C#', 'IDL', 'Java', 'JavaScript']:
                indentStyle = 0
                if Preferences.getEditor("CppIndentOpeningBrace"):
                    indentStyle |= QextScintilla.AiOpening
                if Preferences.getEditor("CppIndentClosingBrace"):
                    indentStyle |= QextScintilla.AiClosing
                self.lexer.setAutoIndentStyle(indentStyle)

        # 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()
        
        # set the autosave flags
        self.autosaveEnabled = Preferences.getEditor("AutosaveInterval") > 0
        
        # set checked context menu item
        self.menu.setItemChecked(self.menuIds["AutoCompletionEnable"],
            self.autoCompletionThreshold() != -1)
        self.menu.setItemChecked(self.menuIds["MonospacedFont"],
            self.useMonospaced)
        self.menu.setItemChecked(self.menuIds["AutosaveEnable"], 
            self.autosaveEnabled and not self.autosaveManuallyDisabled)
        
    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"):
            folding = Preferences.getEditor("FoldingStyle")
            try:
                folding = QextScintilla.FoldStyle(folding)
            except AttributeError:
                pass
            self.setFolding(folding)
        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"))
        if Preferences.getEditor("ShowWhitespace"):
            self.setWhitespaceVisibility(QextScintilla.WsVisible)
        else:
            self.setWhitespaceVisibility(QextScintilla.WsInvisible)
        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.setMatchedBraceBackgroundColor(
            Preferences.getEditorColour("MatchingBraceBack"))
        self.setUnmatchedBraceForegroundColor(
            Preferences.getEditorColour("NonmatchingBrace"))
        self.setUnmatchedBraceBackgroundColor(
            Preferences.getEditorColour("NonmatchingBraceBack"))
        eolMode = Preferences.getEditor("EOLMode")
        try:
            eolMode = QextScintilla.EolMode(eolMode)
        except AttributeError:
            pass
        self.setEolMode(eolMode)
        self.setSelectionBackgroundColor(qApp.palette().active().highlight())
        if Preferences.getEditor("ColourizeSelText"):
            self.resetSelectionForegroundColor()
        else:
            self.setSelectionForegroundColor(\
                qApp.palette().active().highlightedText())
        self.setCaretForegroundColor(
            Preferences.getEditorColour("CaretForeground"))
        self.setCaretLineBackgroundColor(
            Preferences.getEditorColour("CaretLineBackground"))
        self.setCaretLineVisible(Preferences.getEditor("CaretLineVisible"))
        self.caretWidth = Preferences.getEditor("CaretWidth")
        self.setCaretWidth(self.caretWidth)
        self.useMonospaced = Preferences.getEditor("UseMonospacedFont")
        self.setMonospaced(self.useMonospaced)
        edgeMode = Preferences.getEditor("EdgeMode")
        try:
            edge = QextScintilla.EdgeMode(edgeMode)
        except AttributeError:
            edge = edgeMode
        self.setEdgeMode(edge)
        if edgeMode:
            self.setEdgeColumn(Preferences.getEditor("EdgeColumn"))
            self.setEdgeColor(Preferences.getEditorColour("Edge"))
        
    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:
            if Preferences.getEditor("AutoCompletionSource") == QextScintilla.AcsDocument:
                self.setAutoCompletionSource(QextScintilla.AcsDocument)
            else:
                self.setAutoCompletionSource(QextScintilla.AcsAPIs)
            self.acAPI = 1
        self.emit(PYSIGNAL("autoCompletionAPIsAvailable"), (self.acAPI,))
                
        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.
        
        @return flag indicating successful reset of the dirty flag (boolean)
        """
        if self.isModified():
            fn = self.fileName
            if fn is None:
                fn = self.trUtf8('Noname')
            res = KQMessageBox.warning(self.vm, 
                self.trUtf8("File Modified"),
                self.trUtf8("<p>The file <b>%1</b> has unsaved changes.</p>")
                    .arg(fn),
                self.trUtf8("&Save"), self.trUtf8("&Abort"), QString.null, 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.
        """
        undo_ = 1
        while self.isModified():
            if undo_:
            # try undo first
                if self.isUndoAvailable():
                    self.undo()
                else:
                    undo_ = 0
            else:
            # try redo next
                if self.isRedoAvailable():
                    self.redo()
                else:
                    break
                    # Couldn't find the unmodified state
        
    def handleBookmark(self, line):
        """
        Public method to toggle a bookmark.
        
        @param 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.
        
        @return 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.
        
        @return flag indicating the presence of bookmarks (boolean)
        """
        return len(self.bookmarks) > 0
        
    def handleSyntaxError(self, line, error, msg = ""):
        """
        Public method to toggle a syntax error indicator.
        
        @param line line number of the syntax error
        @param error flag indicating if the error marker should be
            set or deleted (boolean)
        @param msg error message (string)
        """
        if error:
            # set a new syntax error marker
            markers = self.markersAtLine(line-1)
            if not (markers & (1 << self.syntaxerror)):
                handle = self.markerAdd(line-1, self.syntaxerror)
                self.syntaxerrors[handle] = msg
                self.emit(PYSIGNAL('syntaxerrorToggled'), (self,))
        else:
            for handle in self.syntaxerrors.keys():
                if self.markerLine(handle) == line-1:
                    del self.syntaxerrors[handle]
                    self.markerDeleteHandle(handle)
                    self.emit(PYSIGNAL('syntaxerrorToggled'), (self,))
        
    def getSyntaxErrors(self):
        """
        Public method to retrieve the syntax error markers.
        
        @return sorted list of all lines containing a syntax error
            (list of integer)
        """
        selist = []
        for handle in self.syntaxerrors.keys():
            selist.append(self.markerLine(handle)+1)
            
        selist.sort()
        return selist
        
    def hasSyntaxErrors(self):
        """
        Public method to check for the presence of bookmarks.
        
        @return flag indicating the presence of bookmarks (boolean)
        """
        return len(self.syntaxerrors) > 0
        
    def canAutoCompleteFromAPIs(self):
        """
        Public method to check for API availablity.
        
        @return flag indicating autocompletion from APIs is available (boolean)
        """
        return self.acAPI
        
    def autoComplete(self):
        """
        Public method to perform an autocompletion.
        """
        acs = self.autoCompletionSource()
        if acs == QextScintilla.AcsDocument:
            self.autoCompleteFromDocument()
        elif acs == QextScintilla.AcsAPIs:
            self.autoCompleteFromAPIs()
        else:
            KQMessageBox.information(None,
                self.trUtf8("Autocompletion"),
                self.trUtf8("""Autocompletion is not available because"""
                    """ there is no autocompletion source set."""),
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)
                
    def setAutoCompletionEnabled(self, enable):
        """
        Public method to enable/disable autocompletion.
        
        @param enable flag indicating the desired autocompletion status
        """
        if enable:
            self.setAutoCompletionThreshold(
                Preferences.getEditor("AutoCompletionThreshold"))
        else:
            self.setAutoCompletionThreshold(-1)
            
    def handleAutoCompletionEnable(self):
        """
        Private slot to handle the Enable Autocompletion context menu entry.
        """
        if self.menu.isItemChecked(self.menuIds["AutoCompletionEnable"]):
            self.menu.setItemChecked(self.menuIds["AutoCompletionEnable"], 0)
            self.setAutoCompletionEnabled(0)
        else:
            self.menu.setItemChecked(self.menuIds["AutoCompletionEnable"], 1)
            self.setAutoCompletionEnabled(1)
            
    def handleMonospacedEnable(self):
        """
        Private slot to handle the Use Monospaced Font context menu entry.
        """
        if self.menu.isItemChecked(self.menuIds["MonospacedFont"]):
            self.menu.setItemChecked(self.menuIds["MonospacedFont"], 0)
            self.setMonospaced(0)
        else:
            self.menu.setItemChecked(self.menuIds["MonospacedFont"], 1)
            self.setMonospaced(1)
    
    #################################################################
    ## Methods needed by the context menu
    #################################################################
    
    def marginsWidth(self):
        """
        Private method to determin the width of all margins.
        
        @return 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.
        
        @param evt the context menu event (QContextMenuEvent)
        """
        evt.accept()
        if evt.x() > self.marginsWidth():
            self.menu.popup(evt.globalPos())
        else:
            self.line = self.lineAt(evt.pos())
            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.isPyFile():
            self.menu.setItemEnabled(self.menuIds["Refactoring"], 1)
            self.menu.setItemEnabled(self.menuIds["Check"], 1)
            self.menu.setItemEnabled(self.menuIds["Show"], 1)
        else:
            self.menu.setItemEnabled(self.menuIds["Refactoring"], 0)
            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.
        """
        prEnable = 0
        coEnable = 0
        cyEnable = 0
        
        # first check if the file belongs to a project and there is
        # a project coverage file
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.fileName):
            fn = project.getMainScript(1)
            if fn is not None:
                tfn = Utilities.getTestFileName(fn)
                basename = os.path.splitext(fn)[0]
                tbasename = os.path.splitext(tfn)[0]
                prEnable = prEnable or \
                    os.path.isfile("%s.profile" % basename) or \
                    os.path.isfile("%s.profile" % tbasename)
                coEnable = coEnable or \
                    os.path.isfile("%s.coverage" % basename) or \
                    os.path.isfile("%s.coverage" % tbasename)
                cyEnable = cyEnable or \
                    os.path.isfile("%s.cycles.html" % basename) or \
                    os.path.isfile("%s.cycles.html" % tbasename)
        
        # now check ourself
        fn = self.getFileName()
        if fn is not None:
            tfn = Utilities.getTestFileName(fn)
            basename = os.path.splitext(fn)[0]
            tbasename = os.path.splitext(tfn)[0]
            prEnable = prEnable or \
                os.path.isfile("%s.profile" % basename) or \
                os.path.isfile("%s.profile" % tbasename)
            coEnable = coEnable or \
                os.path.isfile("%s.coverage" % basename) or \
                os.path.isfile("%s.coverage" % tbasename)
            cyEnable = cyEnable or \
                os.path.isfile("%s.cycles.html" % basename) or \
                os.path.isfile("%s.cycles.html" % tbasename)
        
        self.showMenu.setItemEnabled(self.profileMenuItem, prEnable)
        self.showMenu.setItemEnabled(self.coverageMenuItem, coEnable)
        self.showMenu.setItemEnabled(self.coverageShowAnnotationMenuItem, 
            coEnable and not self.coverageMarkersShown)
        self.showMenu.setItemEnabled(self.coverageHideAnnotationMenuItem, 
            self.coverageMarkersShown)
        self.showMenu.setItemEnabled(self.cyclopsMenuItem, cyEnable)
        self.showMenu.setItemEnabled(self.removeCyclopsMenuItem, cyEnable)
        
    def handleShowMarginContextMenu(self):
        """
        Private slot handling the aboutToShow signal of the margins context menu.
        """
        if self.fileName and (self.isPyFile() or self.isRubyFile()):
            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)
            
        if len(self.syntaxerrors):
            self.marginMenu.setItemEnabled(self.marginMenuIds["GotoSyntaxError"], 1)
            self.marginMenu.setItemEnabled(self.marginMenuIds["ClearSyntaxError"], 1)
            if self.markersAtLine(self.line) & (1 << self.syntaxerror):
                self.marginMenu.setItemEnabled(self.marginMenuIds["ShowSyntaxError"], 1)
            else:
                self.marginMenu.setItemEnabled(self.marginMenuIds["ShowSyntaxError"], 0)
        else:
            self.marginMenu.setItemEnabled(self.marginMenuIds["GotoSyntaxError"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["ClearSyntaxError"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["ShowSyntaxError"], 0)
        
        if self.notcoveredMarkers:
            self.marginMenu.setItemEnabled(self.marginMenuIds["NextCoverageMarker"], 1)
            self.marginMenu.setItemEnabled(self.marginMenuIds["PreviousCoverageMarker"], 1)
        else:
            self.marginMenu.setItemEnabled(self.marginMenuIds["NextCoverageMarker"], 0)
            self.marginMenu.setItemEnabled(self.marginMenuIds["PreviousCoverageMarker"], 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 handleNewView(self):
        """
        Private slot to create a new view to an open document.
        """
        self.vm.newEditorView(self.fileName, self, self.isPythonFile)
        
    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 handleShortenEmptyLines(self):
        """
        Public slot to compress lines consisting solely of whitespace characters.
        """
        ok = self.findFirst(r"^[ \t]+$", 1, 0, 0, 0, 1, 0, 0)
        while ok:
            self.replace("")
            ok = self.findNext()
        
    def handleAutosaveEnable(self):
        """
        Private slot handling the autosave enable context menu action.
        """
        if self.menu.isItemChecked(self.menuIds["AutosaveEnable"]):
            self.menu.setItemChecked(self.menuIds["AutosaveEnable"], 0)
            self.autosaveManuallyDisabled = 1
        else:
            self.menu.setItemChecked(self.menuIds["AutosaveEnable"], 1)
            self.autosaveManuallyDisabled = 0
        
    def shouldAutosave(self):
        """
        Public slot to check the autosave flags.
        
        @return flag indicating this editor should be saved (boolean)
        """
        return self.fileName is not None and not self.autosaveManuallyDisabled
        
    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 autoSyntaxCheck(self):
        """
        Private method to perform an automatic syntax check of the file.
        """
        if Preferences.getEditor("AutoCheckSyntax"):
            self.handleClearSyntaxError()
            if self.isPyFile():
                syntaxError, _fn, errorline, _code, _error = \
                    Utilities.compile(self.fileName)
                if syntaxError:
                    self.handleSyntaxError(int(errorline), 1, _error)
    
    def syntaxCheck(self, quiet = 0):
        """
        Private method to perform a syntax check of the file.
        
        @param quiet flag indicating quiet operation (Boolean)
        """
        if not self.isPyFile() or not self.checkDirty():
            return
            
        self.handleClearSyntaxError()
        syntaxError, _fn, errorline, _code, _error = \
            Utilities.compile(self.fileName)
        if syntaxError:
            self.handleSyntaxError(int(errorline), 1, _error)
        else:
            if not quiet:
                KQMessageBox.information(None,
                    self.trUtf8("Syntax Check"),
                    self.trUtf8("""No syntax errors found."""),
                    self.trUtf8("&OK"),
                    QString.null,
                    QString.null,
                    0, -1)
    
    def handleSyntaxCheck(self):
        """
        Private method to handle the syntax check context menu action.
        """
        self.syntaxCheck(0)
        
    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 getCodeCoverageFile(self):
        """
        Private method to get the filename of the file containing coverage info.
        """
        files = []
        
        # first check if the file belongs to a project and there is
        # a project coverage file
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.fileName):
            fn = project.getMainScript(1)
            if fn is not None:
                tfn = Utilities.getTestFileName(fn)
                basename = os.path.splitext(fn)[0]
                tbasename = os.path.splitext(tfn)[0]
                
                f = "%s.coverage" % basename
                tf = "%s.coverage" % tbasename
                if os.path.isfile(f):
                    files.append(f)
                if os.path.isfile(tf):
                    files.append(tf)
        
        # now check, if there are coverage files belonging to ourself
        fn = self.getFileName()
            
        if fn is not None:
            tfn = Utilities.getTestFileName(fn)
            basename = os.path.splitext(fn)[0]
            tbasename = os.path.splitext(tfn)[0]
            
            f = "%s.coverage" % basename
            tf = "%s.coverage" % tbasename
            if os.path.isfile(f) and not f in files:
                files.append(f)
            if os.path.isfile(tf) and not tf in files:
                files.append(tf)
                
        if files:
            if len(files) > 1:
                filelist = QStringList()
                for f in files:
                    filelist.append(f)
                fn, ok = KQInputDialog.getItem(\
                    self.trUtf8("Code Coverage"),
                    self.trUtf8("Please select a coverage file"),
                    filelist,
                    0, 0)
                if not ok:
                    return
                fn = unicode(fn)
            else:
                fn = files[0]
        else:
            fn = None
            
        return fn
        
    def handleCodeCoverage(self):
        """
        Private method to handle the code coverage context menu action.
        """
        fn = self.getCodeCoverageFile()
        if fn:
            self.codecoverage = PyCoverageDialog()
            self.codecoverage.show()
            self.codecoverage.start(fn, self.fileName)
        
    def handleCodeCoverageShowAnnotations(self):
        """
        Private method to handle the show code coverage annotations context menu action.
        """
        fn = self.getCodeCoverageFile()
        if fn:
            cover = coverage(fn)
            missing = cover.analysis(self.fileName)[2]
            if missing:
                for line in missing:
                    handle = self.markerAdd(line-1, self.notcovered)
                    self.notcoveredMarkers.append(handle)
                    self.emit(PYSIGNAL('coverageMarkersShown'), (1,))
                    self.coverageMarkersShown = 1
            else:
                KQMessageBox.information(None,
                    self.trUtf8("Show Code Coverage Annotations"),
                    self.trUtf8("""All lines have been covered."""),
                    self.trUtf8("&OK"),
                    QString.null,
                    QString.null,
                    0, -1)
        else:
            KQMessageBox.warning(None,
                self.trUtf8("Show Code Coverage Annotations"),
                self.trUtf8("""There is no coverage file available."""),
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)
        
    def handleCodeCoverageHideAnnotations(self):
        """
        Private method to handle the hide code coverage annotations context menu action.
        """
        for handle in self.notcoveredMarkers:
            self.markerDeleteHandle(handle)
        self.notcoveredMarkers = []
        self.emit(PYSIGNAL('coverageMarkersShown'), (0,))
        self.coverageMarkersShown = 0
        
    def hasCoverageMarkers(self):
        """
        Public method to test, if there are coverage markers.
        """
        return len(self.notcoveredMarkers) > 0
        
    def handleNextUncovered(self):
        """
        Private slot to handle the 'Next uncovered' context menu action.
        """
        line, index = self.getCursorPosition()
        if line == self.lines()-1:
            line = 0
        else:
            line += 1
        ucline = self.markerFindNext(line, 1 << self.notcovered)
        if ucline < 0:
            # wrap around
            ucline = self.markerFindNext(0, 1 << self.notcovered)
        if ucline >= 0:
            self.setCursorPosition(ucline, 0)
            self.ensureLineVisible(ucline)
        
    def handlePreviousUncovered(self):
        """
        Private slot to handle the 'Previous uncovered' context menu action.
        """
        line, index = self.getCursorPosition()
        if line == 0:
            line = self.lines()-1
        else:
            line -= 1
        ucline = self.markerFindPrevious(line, 1 << self.notcovered)
        if ucline < 0:
            # wrap around
            ucline = self.markerFindPrevious(self.lines()-1, 1 << self.notcovered)
        if ucline >= 0:
            self.setCursorPosition(ucline, 0)
            self.ensureLineVisible(ucline)
        
    def handleProfileData(self):
        """
        Private method to handle the show profile data context menu action.
        """
        files = []
        
        # first check if the file belongs to a project and there is
        # a project coverage file
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.fileName):
            fn = project.getMainScript(1)
            if fn is not None:
                tfn = Utilities.getTestFileName(fn)
                basename = os.path.splitext(fn)[0]
                tbasename = os.path.splitext(tfn)[0]
                
                f = "%s.profile" % basename
                tf = "%s.profile" % tbasename
                if os.path.isfile(f):
                    files.append(f)
                if os.path.isfile(tf):
                    files.append(tf)
        
        # now check, if there are coverage files belonging to ourself
        fn = self.getFileName()
            
        if fn is not None:
            tfn = Utilities.getTestFileName(fn)
            basename = os.path.splitext(fn)[0]
            tbasename = os.path.splitext(tfn)[0]
            
            f = "%s.profile" % basename
            tf = "%s.profile" % tbasename
            if os.path.isfile(f) and not f in files:
                files.append(f)
            if os.path.isfile(tf) and not tf in files:
                files.append(tf)
                
        if files:
            if len(files) > 1:
                filelist = QStringList()
                for f in files:
                    filelist.append(f)
                fn, ok = KQInputDialog.getItem(\
                    self.trUtf8("Profile Data"),
                    self.trUtf8("Please select a profile file"),
                    filelist,
                    0, 0)
                if not ok:
                    return
                fn = unicode(fn)
            else:
                fn = files[0]
        else:
            return
            
        self.profiledata = PyProfileDialog()
        self.profiledata.show()
        self.profiledata.start(fn, self.fileName)
        
    def handleCyclopsReport(self):
        """
        Private method to handle the Cyclops report context menu action.
        """
        files = []
        
        # first check if the file belongs to a project and there is
        # a project coverage file
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.fileName):
            fn = project.getMainScript(1)
            if fn is not None:
                tfn = Utilities.getTestFileName(fn)
                basename = os.path.splitext(fn)[0]
                tbasename = os.path.splitext(tfn)[0]
                
                f = "%s.cycles.html" % basename
                tf = "%s.cycles.html" % tbasename
                if os.path.isfile(f):
                    files.append(f)
                if os.path.isfile(tf):
                    files.append(tf)
        
        # now check, if there are coverage files belonging to ourself
        fn = self.getFileName()
            
        if fn is not None:
            tfn = Utilities.getTestFileName(fn)
            basename = os.path.splitext(fn)[0]
            tbasename = os.path.splitext(tfn)[0]
            
            f = "%s.cycles.html" % basename
            tf = "%s.cycles.html" % tbasename
            if os.path.isfile(f) and not f in files:
                files.append(f)
            if os.path.isfile(tf) and not tf in files:
                files.append(tf)
                
        if files:
            if len(files) > 1:
                filelist = QStringList()
                for f in files:
                    filelist.append(f)
                fn, ok = KQInputDialog.getItem(\
                    self.trUtf8("Cyclops Report"),
                    self.trUtf8("Please select a Cyclops report file"),
                    filelist,
                    0, 0)
                if not ok:
                    return
                fn = unicode(fn)
            else:
                fn = files[0]
        else:
            return
            
        qApp.mainWidget().launchHelpViewer(fn)
        
    def handleRemoveCyclopsReport(self):
        """
        Private method to handle the Remove Cyclops report context menu action.
        """
        files = []
        
        # first check if the file belongs to a project and there is
        # a project coverage file
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.fileName):
            fn = project.getMainScript(1)
            if fn is not None:
                tfn = Utilities.getTestFileName(fn)
                basename = os.path.splitext(fn)[0]
                tbasename = os.path.splitext(tfn)[0]
                
                f = "%s.cycles.html" % basename
                tf = "%s.cycles.html" % tbasename
                if os.path.isfile(f):
                    files.append(f)
                if os.path.isfile(tf):
                    files.append(tf)
        
        # now check, if there are coverage files belonging to ourself
        fn = self.getFileName()
            
        if fn is not None:
            tfn = Utilities.getTestFileName(fn)
            basename = os.path.splitext(fn)[0]
            tbasename = os.path.splitext(tfn)[0]
            
            f = "%s.cycles.html" % basename
            tf = "%s.cycles.html" % tbasename
            if os.path.isfile(f) and not f in files:
                files.append(f)
            if os.path.isfile(tf) and not tf in files:
                files.append(tf)
                
        if files:
            if len(files) > 1:
                filelist = QStringList()
                for f in files:
                    filelist.append(f)
                fn, ok = KQInputDialog.getItem(\
                    self.trUtf8("Remove Cyclops Report"),
                    self.trUtf8("Please select a Cyclops report file to be removed"),
                    filelist,
                    0, 0)
                if not ok:
                    return
                fn = unicode(fn)
            else:
                fn = files[0]
        else:
            return
            
        if os.path.exists(fn):
            os.remove(fn)
        
    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 = []
        self.emit(PYSIGNAL('bookmarkToggled'), (self,))
    
    def handleGotoSyntaxError(self):
        """
        Private slot to handle the 'Goto syntax error' context menu action.
        """
        seline = self.markerFindNext(0, 1 << self.syntaxerror)
        if seline >= 0:
            self.setCursorPosition(seline, 0)
            self.ensureLineVisible(seline)
        
    def handleClearSyntaxError(self):
        """
        Private slot to handle the 'Clear all syntax error' context menu action.
        """
        for handle in self.syntaxerrors.keys():
            line = self.markerLine(handle) + 1
            self.handleSyntaxError(line, 0)
        
    def handleShowSyntaxError(self):
        """
        Private slot to handle the 'Show syntax error message' context menu action.
        """
        for handle in self.syntaxerrors.keys():
            if self.markerLine(handle) == self.line:
                KQMessageBox.critical(None,
                    self.trUtf8("Syntax Error"),
                    self.syntaxerrors[handle],
                    self.trUtf8("&OK"),
                    QString.null,
                    QString.null,
                    0, -1)
                break
        else:
            KQMessageBox.critical(None,
                self.trUtf8("Syntax Error"),
                self.trUtf8("No syntax error message available."),
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)
    
    #################################################################
    ## Macro handling methods
    #################################################################
    
    def getMacroName(self):
        """
        Private method to select a macro name from the list of macros.
        
        @return 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 KQInputDialog.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[unicode(name)].play()
        
    def handleDeleteMacro(self):
        """
        Public method to delete a macro.
        """
        name, ok = self.getMacroName()
        if ok and not name.isEmpty():
            del self.macros[unicode(name)]
        
    def handleLoadMacro(self):
        """
        Public method to load a macro from a file.
        """
        configDir = Utilities.getConfigDir()
        fname = KQFileDialog.getOpenFileName(configDir,
            self.trUtf8("Macro files (*.macro)"),
            self, None,
            self.trUtf8("Load macro file"))
            
        if fname.isEmpty():
            return  # user aborted
            
        try:
            f = open(unicode(fname), "rb")
            lines = f.readlines()
            f.close()
        except IOError:
            KQMessageBox.critical(self,
                self.trUtf8("Error loading macro"),
                self.trUtf8("<p>The macro file <b>%1</b> could not be read.</p>")
                    .arg(fname),
                self.trUtf8("OK"))
            return
            
        if len(lines) != 2:
            KQMessageBox.critical(self,
                self.trUtf8("Error loading macro"),
                self.trUtf8("<p>The macro file <b>%1</b> is corrupt.</p>")
                    .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 = Utilities.getConfigDir()
        
        name, ok = self.getMacroName()
        if not ok or name.isEmpty():
            return  # user abort
        name = unicode(name)
        
        selectedFilter = QString('')
        fname = KQFileDialog.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 = KQMessageBox.warning(self,
                self.trUtf8("Save macro"),
                self.trUtf8("<p>The macro file <b>%1</b> already exists.</p>")
                    .arg(fname),
                self.trUtf8("&Overwrite"),
                self.trUtf8("&Abort"), QString.null, 1)
            if abort:
                return
        fname = unicode(QDir.convertSeparators(fname))
        
        try:
            f = open(fname, "wb")
            f.write("%s%s" % (name, os.linesep))
            f.write(unicode(self.macros[name].save()))
            f.close()
        except IOError:
            KQMessageBox.critical(self,
                self.trUtf8("Error saving macro"),
                self.trUtf8("<p>The macro file <b>%1</b> could not be written.</p>")
                    .arg(fname),
                self.trUtf8("OK"))
            return
        
    def handleStartMacroRecording(self):
        """
        Public method to start macro recording.
        """
        if self.recording:
            res = KQMessageBox.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 = KQInputDialog.getText(self.trUtf8("Macro Recording"),
            self.trUtf8("Enter name of the macro:"))
            
        if ok and not name.isEmpty():
            self.macros[unicode(name)] = self.curMacro
            
        self.curMacro = None
        
    #################################################################
    ## Overwritten methods
    #################################################################
    
    def undo(self):
        """
        Public method to undo the last recorded change.
        """
        QextScintillaCompat.undo(self)
        self.emit(PYSIGNAL('undoAvailable'), (self.isUndoAvailable(),))
        self.emit(PYSIGNAL('redoAvailable'), (self.isRedoAvailable(),))
        
    def redo(self):
        """
        Public method to redo the last recorded change.
        """
        QextScintillaCompat.redo(self)
        self.emit(PYSIGNAL('undoAvailable'), (self.isUndoAvailable(),))
        self.emit(PYSIGNAL('redoAvailable'), (self.isRedoAvailable(),))
        
    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.
        
        @param alsoDelete ignored
        """
        return self.vm.closeEditor(self)
        
    def closeIt(self):
        """
        Public method called by the viewmanager to finally get rid of us.
        """
        QextScintillaCompat.close(self, 1)
        
    def focusInEvent(self, event):
        """
        Public method called when the editor receives focus.
        
        This method checks for modifications of the current file and
        rereads it upon request. The cursor is placed at the current position
        assuming, that it is in the vicinity of the old position after the reread.
        
        @param event the event object (QFocusEvent)
        """
        self.vm.editorActGrp.setEnabled(1)
        self.setCaretWidth(self.caretWidth)
        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("""<p>The file <b>%1</b> has been changed while it was opened in"""
                    """ eric3.</p>""").arg(self.fileName)
            if self.isModified():
                msg.append(self.trUtf8("""<br><b>Warning:</b> You will loose"""
                    """ your changes upon reopening it."""))
            res = KQMessageBox.warning(None,
                self.trUtf8("File changed"), msg,
                self.trUtf8("&OK"), self.trUtf8("&Reopen"),
                QString.null, 0, -1)
            if res == 1:
                self.refresh()
            else:
                # do not prompt for this change again...
                self.lastModified = self.fileInfo.lastModified()
            self.inReopenPrompt = 0
            
        QextScintillaCompat.focusInEvent(self, event)
        
    def focusOutEvent(self, event):
        """
        Public method called when the editor loses focus.
        
        @param event the event object (QFocusEvent)
        """
        self.vm.editorActGrp.setEnabled(0)
        self.setCaretWidth(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.
        
        @param pos position to calculate the line for (QPoint)
        @return linenumber at position or -1, if there is no line at pos
            (integer, zero based)
        """
        line = self.firstVisibleLine() + pos.y() / self.textHeight()
        if line >= self.lines():
            line = -1
        return line
        
    def zoomIn(self, zoom = 1):
        """
        Public method used to increase the zoom factor.
        
        @param zoom zoom factor increment
        """
        self.zoom += zoom
        QextScintillaCompat.zoomIn(self, zoom)
        
    def zoomOut(self, zoom = 1):
        """
        Public method used to decrease the zoom factor.
        
        @param zoom zoom factor decrement
        """
        self.zoom -= zoom
        QextScintillaCompat.zoomOut(self, zoom)
        
    def zoomTo(self, zoom):
        """
        Public method used to zoom to a specific zoom factor.
        
        @param zoom zoom factor
        """
        self.zoom = zoom
        QextScintillaCompat.zoomTo(self, zoom)
        
    def getZoom(self):
        """
        Public method used to retrieve the current zoom factor.
        
        @return zoom factor (int)
        """
        return self.zoom
        
    def event(self, evt):
        """
        Protected method called to process an event.
        
        This implements special handling for the events showMaximized,
        showMinimized and showNormal. The windows caption is shortened
        for the minimized mode and reset to the full filename for the
        other modes. This is to make the editor windows work nicer
        with the QWorkspace.
        
        @param evt the event, that was generated (QEvent)
        @return flag indicating if the event could be processed (bool)
        """
        if self.fileName is not None and \
           evt.type() in [QEvent.ShowMaximized, QEvent.ShowNormal, QEvent.ShowMinimized]:
            if evt.type() == QEvent.ShowMinimized:
                cap = os.path.basename(self.fileName)
            else:
                cap = self.fileName
            if self.isReadOnly():
                cap = "%s (ro)" % cap
            self.setCaption(cap)
        
        return QextScintillaCompat.event(self, evt)
        
    def eventFilter(self, object, event):
        """
        Private method called to filter interesting events.
        
        @param object object, that generated the event (QObject)
        @param event the event, that was generated by object (QEvent)
        @return flag indicating if event was filtered out (bool)
        """
        used = 0
        if event.type() == QEvent.MouseButtonPress:
            self.vm.eventFilter(self, event)
        elif event.type() == QEvent.DragEnter:
            used = self.dragEnterEvent(event)
        elif event.type() == QEvent.DragMove:
            used = self.dragMoveEvent(event)
        elif event.type() == QEvent.DragLeave:
            used = self.dragLeaveEvent(event)
        elif event.type() == QEvent.Drop:
            used = self.dropEvent(event)
            
        if not used:
            used = QextScintillaCompat.eventFilter(self, object, event)
            
        return used

    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.

        @param 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)" % unicode(cap)
        self.setReadOnly(readOnly)
        self.setCaption(cap)
        self.emit(PYSIGNAL('captionChanged'), (cap, self))
        
    def refresh(self):
        """
        Public slot to refresh the editor contents.
        """
        # save cursor position
        cline, cindex = self.getCursorPosition()
        
        # save bookmarks and breakpoints and clear them
        bmlist = self.getBookmarks()
        self.handleClearBookmarks()
        bplist = self.getBreakpoints()
        self.handleClearBreakpoints()
        
        # reread the file
        try:
            self.readFile(self.fileName)
        except IOError:
            # do not prompt for this change again...
            self.lastModified = self.fileInfo.lastModified()
        self.setModified(0)
        
        # reset cursor position
        self.setCursorPosition(cline, cindex)
        self.ensureCursorVisible()
        
        # reset bookmarks and breakpoints to their old position
        if bmlist:
            for bm in bmlist:
                self.handleBookmark(bm)
        if bplist:
            for bp in bplist:
                self.newBreakpointWithProperties(bp[0], bp[1:])
        
        self.emit(PYSIGNAL('editorSaved'), (self.fileName,))
        self.autoSyntaxCheck()
        
    def setMonospaced(self, on):
        """
        Public method to set/reset a monospaced font.
        
        @param on flag to indicate usage of a monospace font (boolean)
        """
        if on:
            f = Preferences.getEditorOtherFonts("MonospacedFont")
            self.monospacedStyles(f)
        else:
            if self.lexer:
                aiStyle = self.lexer.autoIndentStyle()
                self.lexer.readSettings(Preferences.Prefs.settings, "/eric3/Scintilla")
                self.lexer.setAutoIndentStyle(aiStyle)
            else:
                self.clearStyles()
        
        self.useMonospaced = on
    
    #################################################################
    ## Drag and Drop Support
    #################################################################
    
    def dragEnterEvent(self, event):
        """
        Protected method to handle the drag enter event.
        
        @param event the drag enter event (QDragEnterEvent)
        @return flag indicating that the event was handled (boolean)
        """
        self.inDragDrop = QUriDrag.canDecode(event)
        event.accept(self.inDragDrop)
        return self.inDragDrop
        
    def dragMoveEvent(self, event):
        """
        Protected method to handle the drag move event.
        
        @param event the drag move event (QDragMoveEvent)
        @return flag indicating that the event was handled (boolean)
        """
        event.accept(self.inDragDrop)
        return self.inDragDrop
        
    def dragLeaveEvent(self, event):
        """
        Protected method to handle the drag leave event.
        
        @param event the drag leave event (QDragLeaveEvent)
        @return flag indicating that the event was handled (boolean)
        """
        if self.inDragDrop:
            self.inDragDrop = 0
            return 1
        else:
            return 0
        
    def dropEvent(self, event):
        """
        Protected method to handle the drop event.
        
        @param event the drop event (QDropEvent)
        @return flag indicating that the event was handled (boolean)
        """
        used = 0
        if QUriDrag.canDecode(event):
            flist = QStringList()
            ok = QUriDrag.decodeLocalFiles(event, flist)
            used = 1
            if ok:
                event.acceptAction()
                for fname in flist:
                    if not QFileInfo(fname).isDir():
                        self.vm.handlePythonFile(unicode(fname))
                    else:
                        KQMessageBox.information(None,
                            self.trUtf8("Drop Error"),
                            self.trUtf8("""<p><b>%1</b> is not a file.</p>""")
                                .arg(fname),
                            self.trUtf8("&OK"),
                            QString.null,
                            QString.null,
                            0, -1)
            del flist   # explicit destruction
        
        self.inDragDrop = 0
        return used
