Table of Contents

Eric3 Source Documentation: QScintilla  
# -*- coding: utf-8 -*-

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

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

import re
import os

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

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

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

        self._updateReadOnly(1)

        QWhatsThis.add(self,self.trUtf8(
            """<b>A Source Editor Window</b>"""
            """<p>This window is used to display and edit a Python source file."""
            """  You can open as many of these as you like. The name of the file"""
            """ is displayed in the window's titlebar.</p>"""
            """<p>In order to set breakpoints just click in the space between"""
            """ the line numbers and the fold markers. If you press the Shift-key"""
            """ while clicking in this space, you are presented with a dialog"""
            """ to enter a breakpoint condition.</p>"""
        ))

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

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

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

    def initContextMenuShow(self):
        """
        Private method used to setup the Show context sub menu.
        """
        menu = QPopupMenu()
        
        menu.insertItem(self.trUtf8('Code metrics...'), self.handleCodeMetrics)
        self.coverageMenuItem = \
            menu.insertItem(self.trUtf8('Code coverage...'), self.handleCodeCoverage)
        self.profileMenuItem = \
            menu.insertItem(self.trUtf8('Profile data...'), self.handleProfileData)
        
        self.connect(menu, SIGNAL('aboutToShow()'), self.handleShowShowMenu)
        
        return menu
        
    def initContextMenuLanguages(self):
        """
        Private method used to setup the Languages context sub menu.
        """
        menu = QPopupMenu()
        
        # this list must be kept in synch with the supported languages and the order
        # of the menu items defined below.
        self.supportedLanguages = [\
            (self.trUtf8("C/C++"), 'cpp'), 
            (self.trUtf8("C#"), 'cs'), 
            (self.trUtf8("HTML"), 'html'), 
            (self.trUtf8("IDL"), 'idl'), 
            (self.trUtf8("Java"), 'java'), 
            (self.trUtf8("JavaScript"), 'js'), 
            (self.trUtf8("PHP"), 'php'), 
            (self.trUtf8("Pyrex"), 'pyx'),
            (self.trUtf8("Python"), 'py'), 
            (self.trUtf8("SQL"), 'sql'), 
            (self.trUtf8("XML"), 'xml'),
        ]
        
        idx = 0
        
        for lang in self.supportedLanguages:
            id = menu.insertItem(lang[0], self.handleLanguage)
            menu.setItemParameter(id, idx)
            
            idx += 1
        
        return menu
        
    def handleLanguage(self, idx):
        """
        Private method to handle the selection of a lexer language.
        
        Arguments
        
            idx -- index of the lexer language (int)
        """
        self.setLanguage(self.supportedLanguages[idx][1])
        
    def setLanguage(self, language):
        """
        Private method to set a lexer language.
        
        Arguments
        
            language -- language (extension) of the desired lexer (string)
        """
        self.bindLexer(language)
                
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()

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

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

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

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

    def getHighlightPosition(self):
        """
        Public method to return the position of the highlight bar.
        
        Returns
        
            line number of the highlight bar
        """
        if self.lastHighlight is not None:
            return self.markerLine(self.lastHighlight)
        else:
            return 1
            
    def handleBreakpoint(self, line, state, newcond = None):
        """
        Public method to set, delete and change a breakpoint.
        
        Arguments
        
            line -- line number of the breakpoint
            
            state -- mouse button state (only Qt.LeftButton and Qt.ShiftButton
                is accepted)
                
            newcond -- new breakpoint condition
        """
        for handle, (ln, cond) in self.breaks.items():
            if ln == line:
                break
        else:
            # set a new breakpoint
            if state & Qt.RightButton or state & Qt.MidButton:
                return
            if state & Qt.ShiftButton:
                marker = self.cbreakpoint
            else:
                marker = self.breakpoint
            handle = self.markerAdd(line-1, marker)
            cond = None
            
            # get condition for a conditional breakpoint
            if state & Qt.ShiftButton:
                if newcond:
                    cond = QString(newcond)
                    ok = 1
                else:
                    if len(self.condHistory) > 0:
                        curr = 0
                    else:
                        curr = -1
                        
                    cond, ok = QInputDialog.getItem(
                                self.trUtf8('Conditional Breakpoint'),
                                self.trUtf8('Enter condition for breakpoint'),
                                self.condHistory, curr, 1)
                                
                if not ok or cond.isEmpty():
                    self.markerDeleteHandle(handle)
                    return
                else:
                    self.condHistory.remove(cond)
                    self.condHistory.prepend(cond)

            self.breaks[handle] = (line, cond)
            self.dbs.remoteBreakpoint(self.fileName,line,1,cond)
            return
          
        # we are not interested in right or middle button presses
        if state & Qt.RightButton or state & Qt.MidButton:
            return
            
        # change condition (left button + shift) or delete (left button) breakpoint
        if state & Qt.ShiftButton:
            # change or remove condition
            oldcond = cond
            if newcond:
                cond = QString(newcond)
                ok = 1
            else:
                if cond == None:
                    cond = ''
                curr = self.condHistory.findIndex(cond)
                if curr == -1:
                    self.condHistory.prepend(cond)
                    curr = 0
                    
                cond, ok = QInputDialog.getItem(
                                self.trUtf8('Breakpoint'),
                                self.trUtf8('Enter condition for breakpoint'),
                                self.condHistory, curr, 1)
                            
            if not ok:
                return
            elif cond.isEmpty():
                # change a conditional bp to an unconditional bp
                del self.breaks[handle]
                self.markerDeleteHandle(handle)
                handle = self.markerAdd(line-1, self.breakpoint)
                cond = None
            else:
                # change condition of the bp
                self.condHistory.remove(cond)
                self.condHistory.prepend(cond)
                if oldcond == None:
                    # change unconditional bp to conditional bp
                    del self.breaks[handle]
                    self.markerDeleteHandle(handle)
                    handle =  self.markerAdd(line-1, self.cbreakpoint)
                
            self.dbs.remoteBreakpoint(self.fileName,line,0,oldcond)
            self.breaks[handle] = (line, cond)
            self.dbs.remoteBreakpoint(self.fileName,line,1,cond)
            
        # delete breakpoint
        else:
            del self.breaks[handle]
            self.markerDeleteHandle(handle)
            self.dbs.remoteBreakpoint(self.fileName,line,0,cond)
    
    def handleToggleBreakpoint(self):
        """
        Private slot to handle the 'Set/Reset breakpoint' context menu action.
        """
        line, index = self.getCursorPosition()
        line += 1
        state = Qt.LeftButton
        self.handleBreakpoint(line, state)
        
    def handleToggleCBreakpoint(self):
        """
        Private slot to handle the 'SetChange conditional breakpoint' context menu action.
        """
        line, index = self.getCursorPosition()
        line += 1
        state = Qt.LeftButton | Qt.ShiftButton
        self.handleBreakpoint(line, state)
        
    def sendAllBreakpoints(self):
        """
        Private slot to send all breakpoints to the debug server.
        """
        self.dbs.clearFileBreakpoints(self.fileName)
        for handle, (line, cond) in self.breaks.items():
            line = self.markerLine(handle)+1
            self.breaks[handle] = (line, cond)
            self.dbs.remoteBreakpoint(self.fileName, line, 1, cond)
                
    def printFile(self):
        """
        Public slot to print the text.
        """
        printer = Printer()
        sb = qApp.mainWidget().statusBar()
        if printer.setup(self):
            sb.message(self.trUtf8('Printing...'))
            qApp.setOverrideCursor(Qt.waitCursor)
            qApp.processEvents()
            fn = self.getFileName()
            if fn is not None:
                printer.setDocName(os.path.basename(fn))
            else:
                printer.setDocName(self.trUtf8('Noname'))
            res = printer.printRange(self)
            qApp.restoreOverrideCursor()
            if res:
                sb.message(self.trUtf8('Printing completed'), 2000)
            else:
                sb.message(self.trUtf8('Error while printing'), 2000)
        else:
            sb.message(self.trUtf8('Printing aborted'), 2000)
    
    def printSelection(self):
        """
        Public slot to print the selected text.
        """
        if not self.hasSelectedText():
            QMessageBox.warning(self, self.trUtf8('Print Selection'),
                self.trUtf8('There is no selected text, printing aborted.'),
                QMessageBox.Ok, QMessageBox.NoButton)
            return
            
        # get the selection
        fromLine, fromIndex, toLine, toIndex = self.getSelection()
        
        printer = Printer()
        sb = qApp.mainWidget().statusBar()
        if printer.setup(self):
            sb.message(self.trUtf8('Printing...'))
            qApp.setOverrideCursor(Qt.waitCursor)
            qApp.processEvents()
            fn = self.getFileName()
            if fn is not None:
                printer.setDocName(os.path.basename(fn))
            else:
                printer.setDocName(self.trUtf8('Noname'))
            res = printer.printRange(self, fromLine, toLine)
            qApp.restoreOverrideCursor()
            if res:
                sb.message(self.trUtf8('Printing completed'), 2000)
            else:
                sb.message(self.trUtf8('Error while printing'), 2000)
        else:
            sb.message(self.trUtf8('Printing aborted'), 2000)
    
    def readFile(self, fn):
        """
        Public slot to read the text from a file.
        
        Arguments
        
            fn -- filename to read from (string or QString)
        """
        fn = str(fn)
        try:
            f = open(fn, 'rb')
        except:
            QMessageBox.critical(self.vm, self.trUtf8('Open File'),
                self.trUtf8('The file <b>%1</b> could not be opened.')
                    .arg(fn))
            raise
            
        QApplication.setOverrideCursor(Qt.waitCursor)
        
        txt, self.encoding = Utilities.decode(f.read())
        if (not Preferences.getEditor("TabForIndentation")) and \
                Preferences.getEditor("ConvertTabsOnLoad"):
            txt = txt.expandtabs(Preferences.getEditor("TabWidth"))

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

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

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

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

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

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

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

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

    def gotoLine(self, line):
        """
        Public slot to jump to the beginning of a line.
        
        Arguments
        
            line -- line number to go to
        """
        self.setCursorPosition(line, 0)
        
    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")

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

    def checkDirty(self):
        """
        Private method to check dirty status and open a message window.
        
        Returns
        
            flag indicating successful reset of the dirty flag (boolean)
        """
        if self.isModified():
            fn = self.fileName
            if fn is None:
                fn = self.trUtf8('Noname')
            res = QMessageBox.warning(self.vm, 
                self.trUtf8("File Modified"),
                self.trUtf8("The file <b>%1</b> has unsaved changes.")
                    .arg(fn),
                self.trUtf8("&Save"), self.trUtf8("&Abort"), None, 0, 1)
            if res == 0:
                ok, newName = self.saveFile()
                if ok:
                    self.vm.setEditorName(self, newName)
                return ok
            elif res == 1:
                return 0
        
        return 1
        
    def revertToUnmodified(self):
        """
        Private method to revert back to the last saved state.
        """
        while self.isModified():
            self.undo()
        
    #################################################################
    ## Methods needed by the context menu
    #################################################################
    
    def contextMenuEvent(self, evt):
        """
        Private method implementing the context menu event.
        
        Arguments
        
            evt -- the context menu event (QContextMenuEvent)
        """
        evt.accept()
        self.menu.popup(evt.globalPos())
        
    def handleShowContextMenu(self):
        """
        Private slot handling the aboutToShow signal of the context menu.
        """
        self.menu.setItemEnabled(self.menuIds["Save"], self.isModified())
        self.menu.setItemEnabled(self.menuIds["Undo"], self.isUndoAvailable())
        self.menu.setItemEnabled(self.menuIds["Redo"], self.isRedoAvailable())
        self.menu.setItemEnabled(self.menuIds["Revert"], self.isModified())
        self.menu.setItemEnabled(self.menuIds["Cut"], self.hasSelectedText())
        self.menu.setItemEnabled(self.menuIds["Copy"], self.hasSelectedText())
        self.menu.setItemEnabled(self.menuIds["PrintSel"], self.hasSelectedText())
        self.menu.setItemEnabled(self.menuIds["acAPI"], self.acAPI)
        if self.fileName and self.fileName.endswith(".py"):
            self.menu.setItemEnabled(self.menuIds["Check"], 1)
            self.menu.setItemEnabled(self.menuIds["Show"], 1)
        else:
            self.menu.setItemEnabled(self.menuIds["Check"], 0)
            self.menu.setItemEnabled(self.menuIds["Show"], 0)
        if self.lexer is not None:
            self.menu.setItemEnabled(self.menuIds["Comment"], self.lexer.canBlockComment())
            self.menu.setItemEnabled(self.menuIds["Uncomment"], self.lexer.canBlockComment())
            self.menu.setItemEnabled(self.menuIds["StreamComment"], self.lexer.canStreamComment())
            self.menu.setItemEnabled(self.menuIds["BoxComment"], self.lexer.canBoxComment())
        else:
            self.menu.setItemEnabled(self.menuIds["Comment"], 0)
            self.menu.setItemEnabled(self.menuIds["Uncomment"], 0)
            self.menu.setItemEnabled(self.menuIds["StreamComment"], 0)
            self.menu.setItemEnabled(self.menuIds["BoxComment"], 0)
        
    def handleShowShowMenu(self):
        """
        Private slot called before the show menu is shown.
        """
        project = self.vm.getProject()
        if project.isOpen() and project.isProjectSource(self.getFileName()):
            fn = project.getMainScript(1)
        else:
            fn = self.getFileName()
            
        if fn is not None:
            basename = os.path.splitext(fn)[0]
            self.showMenu.setItemEnabled(self.profileMenuItem, 
                os.path.isfile("%s.profile" % basename))
            self.showMenu.setItemEnabled(self.coverageMenuItem, 
                os.path.isfile("%s.coverage" % basename))
        else:
            self.showMenu.setItemEnabled(self.profileMenuItem, 0)
            self.showMenu.setItemEnabled(self.coverageMenuItem, 0)
        
    def handleContextSave(self):
        """
        Private slot handling the save context menu entry.
        """
        ok, newName = self.saveFile()
        if ok:
            self.vm.setEditorName(self, newName)
        
    def handleContextSaveAs(self):
        """
        Private slot handling the save as context menu entry.
        """
        ok, newName = self.saveFileAs()
        if ok:
            self.vm.setEditorName(self, newName)
        
    def handleContextClose(self):
        """
        Private slot handling the close context menu entry.
        """
        self.vm.closeEditor(self)
    
    def handleSelectAll(self):
        """
        Private slot handling the select all context menu action.
        """
        self.selectAll(1)
    
    def handleDeselectAll(self):
        """
        Private slot handling the deselect all context menu action.
        """
        self.selectAll(0)

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

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

        Arguments

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

Table of Contents

This document was automatically generated by HappyDoc version 2.1