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

# Copyright (c) 2002 - 2005 Detlev Offenbach <detlev@die-offenbachs.de>
# Copyright (c) 2000 Phil Thompson <phil@river-bank.demon.co.uk>
#

"""
Module implementing a graphical Python shell.
"""

import sys

from qt import *
from qtext import QextScintilla, QextScintillaLexerPython

from KdeQt import KQMessageBox, KQInputDialog

from QextScintillaCompat import QextScintillaCompat
from Editor import Editor

import Preferences
import UI.PixmapCache
from Utilities import toUnicode

from DebugClients.Python.DebugClientCapabilities import HasShell, HasCompleter

class Shell(QextScintillaCompat):
    """
    Module implementing a graphical Python shell.
    
    A user can enter commands that are executed in the remote 
    Python interpreter.  
    """
    def __init__(self,dbs,vm,parent=None):
        """
        Constructor
        
        @param dbs reference to the debug server object
        @param vm reference to the viewmanager object
        @param parent parent widget (QWidget)
        """
        QextScintillaCompat.__init__(self,parent)
        self.setUtf8(1)
        
        self.vm = vm
        
        self.linesepRegExp = QRegExp(r"\r\n|\n|\r")

        self.passive = Preferences.getDebugger("PassiveDbgEnabled")
        if self.passive:
            self.setCaption(self.trUtf8('Shell - Passive'))
        else:
            self.setCaption(self.trUtf8('Shell'))

        QWhatsThis.add(self,self.trUtf8(
"""<b>The Shell Window</b>"""
"""<p>This is simply an interpreter running in a window. The"""
""" interpreter is the one that is used to run the program being debugged."""
""" This means that you can execute any command while the program"""
""" being debugged is running.</p>"""
"""<p>You can use the cursor keys while entering commands. There is also a"""
""" history of commands that can be recalled using the up and down cursor"""
""" keys.</p>"""
"""<p>The shell has some special commands. 'reset' kills the shell"""
""" and starts a new one. 'clear' clears the display of the shell window."""
""" 'start' is used to switch the shell language and must be followed by"""
""" either 'Python' or 'Ruby'."""
""" These commands are available through the context menu as well.</p>"""
"""<p>Pressing the Tab key after some text has been entered will show"""
""" a list of possible commandline completions. The relevant entry may"""
""" be selected from this list. If only one entry is available, this will"""
""" inserted automatically.</p>"""
"""<p>In passive debugging mode the shell is only available after the"""
""" program to be debugged has connected to the IDE until it has finished."""
""" This is indicated by a different prompt and by an indication in the"""
""" window caption.</p>"""
                      ))

        self.connect(self, SIGNAL('SCN_USERLISTSELECTION(const char *,int)'),
                     self.handleCompletionList)
        
        self.connect(dbs,PYSIGNAL('clientStatement'),self.handleClientStatement)
        self.connect(dbs,PYSIGNAL('clientOutput'),self.write)
        self.connect(dbs,PYSIGNAL('clientGone'),self.initialise)
        self.connect(dbs,PYSIGNAL('clientRawInput'),self.raw_input)
        self.connect(dbs,PYSIGNAL('clientBanner'),self.writeBanner)
        self.connect(dbs,PYSIGNAL('clientCompletionList'),self.showCompletions)
        self.connect(dbs,PYSIGNAL('clientCapabilities'),self.handleClientCapabilities)
        self.dbs = dbs

        # Initialise instance variables.
        self.initialise()
        self.prline = 0
        self.prcol = 0
        self.history = []
        self.histidx = -1
        self.inDragDrop = 0
        self.lexer = None
        self.completionText = ""

        self.clientType = ''
        
        # Make sure we have prompts.
        if self.passive:
            sys.ps1 = unicode(self.trUtf8("Passive >>> "))
        else:
            try:
                sys.ps1
            except AttributeError:
                sys.ps1 = ">>> "
        try:
            sys.ps2
        except AttributeError:
            sys.ps2 = "... "
            
        if self.passive:
            self.getBanner()

        # Create a little language context menu
        self.lmenu = QPopupMenu()
        self.lmenu.insertItem(self.trUtf8('Python'), self.handleStartPython)
        self.lmenu.insertItem(self.trUtf8('Ruby'), self.handleStartRuby)
        
        # Create a little context menu
        self.menu = QPopupMenu(self)
        self.menu.insertItem(self.trUtf8('Copy'), self.copy)
        self.menu.insertItem(self.trUtf8('Paste'), self.paste)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Clear'), self.handleClear)
        self.menu.insertItem(self.trUtf8('Reset'), self.handleReset)
        self.menu.insertItem(self.trUtf8('Reset and Clear'),
            self.handleResetAndClear)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8('Start'), self.lmenu)
        
        self.setMargin0()
        self.setTextDisplay()
        self.bindLexer()
        
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()
        
        self.setIcon(UI.PixmapCache.getPixmap("eric.png"))
        
    def bindLexer(self, language = 'Python'):
        """
        Private slot to set the lexer.
        
        @param language lexer language to set
        """
        if language == 'Python':
            from LexerPython import LexerPython
            self.lexer = LexerPython(self)
        elif language == 'Ruby':
            try:
                from LexerRuby import LexerRuby
                self.lexer = LexerRuby(self)
            except ImportError:
                self.lexer = None
        
        if self.lexer is None:
            self.setLexer(None)
            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 language == "Python":
            from qtext import QextScintillaLexerPython
            # Python lexer stuff
            if Preferences.getEditor("PythonBadIndentation"):
                self.lexer.setIndentationWarning(QextScintillaLexerPython.Inconsistent)
            else:
                self.lexer.setIndentationWarning(QextScintillaLexerPython.NoWarning)
        elif language == "Ruby":
            # Ruby lexer stuff
            from qtext import QextScintillaLexerRuby
            try:
                if Preferences.getEditor("RubyBadIndentation"):
                    self.lexer.setIndentationWarning(QextScintillaLexerRuby.Inconsistent)
                else:
                    self.lexer.setIndentationWarning(QextScintillaLexerRuby.NoWarning)
            except AttributeError:
                pass
        
    def setMargin0(self):
        """
        Private method to configure margins 0.
        """
        # set the font for all margins
        self.setMarginsFont(Preferences.getEditorOtherFonts("MarginsFont"))
        
        # set margin 0 settings
        linenoMargin = Preferences.getShell("LinenoMargin")
        self.setMarginLineNumbers(0, linenoMargin)
        if linenoMargin:
            self.setMarginWidth(0, ' ' + '8' * Preferences.getShell("LinenoWidth"))
        else:
            self.setMarginWidth(0, 0)
        
        # disable margins 1 and 2
        self.setMarginWidth(1, 0)
        self.setMarginWidth(2, 0)
        
    def setTextDisplay(self):
        """
        Private method to configure the text display.
        """
        self.setTabWidth(Preferences.getEditor("TabWidth"))
        if Preferences.getEditor("ShowWhitespace"):
            self.setWhitespaceVisibility(QextScintilla.WsVisible)
        else:
            self.setWhitespaceVisibility(QextScintilla.WsInvisible)
        self.setEolVisibility(Preferences.getEditor("ShowEOL"))
        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"))
        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)
        if Preferences.getShell("WrapEnabled"):
            self.setWrapMode(QextScintilla.WrapWord)
        else:
            self.setWrapMode(QextScintilla.WrapNone)
        
    def setAutoCompletion(self, language = 'Python'):
        """
        Private method to configure the autocompletion function.
        
        @param language of the autocompletion set to set
        """
        self.setAutoCompletionCaseSensitivity(
            Preferences.getEditor("AutoCompletionCaseSensitivity"))
##        self.setAutoCompletionReplaceWord(
##            Preferences.getEditor("AutoCompletionReplaceWord"))
##        self.setAutoCompletionShowSingle(
##            Preferences.getEditor("AutoCompletionShowSingle"))
##        api = self.vm.getAPIs(language)
##        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
##                
##        if Preferences.getShell("AutoCompletionEnabled"):
##            self.setAutoCompletionThreshold(
##                Preferences.getEditor("AutoCompletionThreshold"))
##        else:
##            self.setAutoCompletionThreshold(-1)
        self.setAutoCompletionThreshold(-1)
        
        self.racEnabled = Preferences.getShell("RemoteAutoCompletionEnabled")
        
    def setCallTips(self, language = 'Python'):
        """
        Private method to configure the calltips function.
        
        @param language of the calltips set to set
        """
        if Preferences.getShell("CallTipsEnabled"):
            api = self.vm.getAPIs(language)
            self.setCallTipsAPIs(api)
            self.setCallTipsBackgroundColor(
                Preferences.getEditorColour("CallTipsBackground"))
            self.setCallTipsVisible(Preferences.getEditor("CallTipsVisible"))
        else:
            self.setCallTipsAPIs(None)

    def initialise(self):
        """
        Private method to get ready for a new remote interpreter.
        """
        self.buff = QString()
        self.inContinue = 0
        self.inRawMode = 0
        self.echoInput = 1
        self.clientCapabilities = 0

    def handleClientCapabilities(self, cap, clType):
        """
        Private slot to handle the reporting of the clients capabilities.
        
        @param cap client capabilities (integer)
        @param clType type of the debug client (string)
        """
        self.clientCapabilities = cap
        if clType != self.clientType:
            self.clientType = clType
            self.bindLexer(clType)
            self.setAutoCompletion(clType)
            self.setCallTips(clType)
            self.racEnabled = Preferences.getShell("RemoteAutoCompletionEnabled") and \
                              (cap & HasCompleter)
        
    def getClientType(self):
        """
        Public slot to get the clients type.
        
        @return client type (string)
        """
        return unicode(self.clientType)
        
    def getBanner(self):
        """
        Private method to get the banner for the remote interpreter.
        
        It requests the Python version and platform running on the
        debug client side.
        """
        if self.passive:
            self.writeBanner('','','')
        else:
            self.dbs.remoteBanner()
            
    def writeBanner(self, version, platform, dbgclient):
        """
        Private method to write a banner with info from the debug client.
        
        @param version Python version string (string)
        @param platform platform of the remote interpreter (string)
        @param dbgclient debug client variant used (string)
        """
        self.clear()
        if self.passive:
            self.write('Passive Debug Mode')
        else:
            version = version.replace("#", unicode(self.trUtf8("No.")))
            self.write(self.trUtf8('%1 on %2, %3')
                        .arg(version)
                        .arg(platform)
                        .arg(dbgclient))
        self.write('\n')

        self.write(sys.ps1)
        
    def handleClientStatement(self,more):
        """
        Private method to handle the response from the debugger client.
        
        @param more flag indicating that more user input is required
        """
        if not self.inRawMode:
            self.inContinue = more
    
            if more:
                prompt = sys.ps2
            else:
                prompt = sys.ps1
    
            self.write(prompt)

    def getEndPos(self):
        """
        Private method to return the line and column of the last character.
        
        @return tuple of two values (int, int) giving the line and column
        """
        line = self.lines() - 1
        return (line, self.lineLength(line))

    def write(self,s):
        """
        Private method to display some text.
        
        @param s text to be displayed (string or QString)
        """
        line, col = self.getEndPos()
        self.setCursorPosition(line, col)
        self.insert(toUnicode(s))
        self.prline, self.prcol = self.getCursorPosition()
        self.ensureCursorVisible()
        self.ensureLineVisible(line)

    def raw_input(self,s,echo):
        """
        Private method to handle raw input.
        
        @param s prompt to be displayed (string or QString)
        @param echo Flag indicating echoing of the input (boolean)
        """
        self.setFocus()
        self.inRawMode = 1
        self.echoInput = echo
        self.write(s)
        self.prompt = toUnicode(s)
        if not self.prompt:
            line, col = self.getEndPos()
            self.setCursorPosition(line,col)
            self.prompt = unicode(self.text(line))
        # move cursor to end of line
        self.moveCursorToEOL()
        
    def paste(self):
        """
        Reimplemented slot to handle the paste action.
        """
        s = qApp.clipboard().text()
        if s.contains("\n") > 1 or \
           (s.contains("\n") == 1 and not s.endsWith("\n")):
            KQMessageBox.warning(None,
                self.trUtf8("Shell Error"),
                self.trUtf8("""Only single lines may be pasted into the shell."""),
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)
            return
        
        if not s.isNull():
            self.insertTextAtEnd(unicode(s).replace("\n", ""))
        
    def clearCurrentLine(self):
        """
        Private method to clear the line containing the cursor.
        """
        line, col = self.getCursorPosition()
        if self.text(line).startsWith(sys.ps1):
            col = len(sys.ps1)
        elif self.text(line).startsWith(sys.ps2):
            col = len(sys.ps2)
        else:
            col = 0
        self.setCursorPosition(line, col)
        self.deleteLineRight()
        
    def insertText(self,s):
        """
        Private method to insert some text at the current cursor position.
        
        @param s text to be inserted (string or QString)
        """
        line, col = self.getCursorPosition()
        self.insertAt(s,line,col)
        self.setCursorPosition(line, col + len(str(s)))
        
    def insertTextAtEnd(self,s):
        """
        Private method to insert some text at the end of the command line.
        
        @param s text to be inserted (string or QString)
        """
        line, col = self.getEndPos()
        self.setCursorPosition(line, col)
        self.insert(s)
        self.prline, self.prcol = self.getCursorPosition()
        
    def insertTextNoEcho(self,s):
        """
        Private method to insert some text at the end of the buffer without echoing it.
        
        @param s text to be inserted (string or QString)
        """
        self.buff.append(s)
        self.prline, self.prcol = self.getCursorPosition()
        
    def keyPressEvent(self,ev):
        """
        Re-implemented to handle the user input a key at a time.
        
        @param ev key event (QKeyPressEvent)
        """
        txt = ev.text()
        key = ev.key()
        asc = ev.ascii()
        buf = unicode('')

        # See it is text to insert.
        if txt.length() and \
           key not in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Delete, 
                       Qt.Key_Backspace, Qt.Key_Tab):
            if self.echoInput:
                if ev.state() & Qt.ControlButton:
                    if key == Qt.Key_C:
                        self.copy()
                    elif key == Qt.Key_V:
                        self.paste()
                    elif key == Qt.Key_U:
                        self.clearCurrentLine()
                    elif key in [Qt.Key_Plus, Qt.Key_Minus]:
                        # ignored keys
                        pass
                    else:
                        QextScintillaCompat.keyPressEvent(self, ev)
                else:
                    ac = self.isAutoCompletionActive()
                    QextScintillaCompat.keyPressEvent(self, ev)
                    if key != Qt.Key_Escape and \
                       ac and \
                       self.racEnabled:
                        self.dbs.remoteCompletion(self.completionText+unicode(txt))
            else:
                self.insertTextNoEcho(txt)
            return

        if key == Qt.Key_Backspace:
            line, col = self.getCursorPosition()
            db = 0
            ac = self.isAutoCompletionActive()
            if self.text(line).startsWith(sys.ps1):
                if col > len(sys.ps1):
                    self.deleteBack()
                    db = 1
            elif self.text(line).startsWith(sys.ps2):
                if col > len(sys.ps2):
                    self.deleteBack()
                    db = 1
            elif col > 0:
                self.deleteBack()
                db = 1
            if db and ac and self.racEnabled and self.completionText:
                self.dbs.remoteCompletion(self.completionText[:-1])
        elif key == Qt.Key_Delete:
            self.delete()
        elif key == Qt.Key_Enter or key == Qt.Key_Return:
            if self.isAutoCompletionActive():
                QextScintillaCompat.keyPressEvent(self, ev)
            else:
                line, col = self.getEndPos()
                self.setCursorPosition(line,col)
                buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
                self.insert('\n')
    
                if not self.inRawMode:
                    if not buf:
                        cmd = ''
                    else:
                        self.history.append(QString(buf))
                        self.histidx = -1
                        cmd = buf
                    if cmd.startswith('start'):
                        if not self.passive:
                            cmdList = cmd.split(None, 1)
                            if len(cmdList) < 2:
                                self.dbs.startClient(0) # same as reset
                            else:
                                self.dbs.startClient(0, cmdList[1].capitalize())
                            cmd = ''
                    elif cmd == 'clear':
                        # Display the banner.
                        self.getBanner()
                        if not self.passive:
                            return
                        else:
                            cmd = ''
                    elif cmd == 'reset':
                        self.dbs.startClient(0)
                        if self.passive:
                            return
                        else:
                            cmd = ''
    
                    self.dbs.remoteStatement(cmd)
                else:
                    if not self.echoInput:
                        buf = unicode(self.buff)
                    self.inRawMode = 0
                    self.echoInput = 1
                    if not buf:
                        cmd = ''
                    else:
                        cmd = str(buf.replace(unicode(self.prompt), ""))
                    self.dbs.remoteRawInput(cmd)
    
        elif key == Qt.Key_Tab:
            if self.isAutoCompletionActive():
                QextScintillaCompat.keyPressEvent(self, ev)
            else:
                line, index = self.getCursorPosition()
                buf = unicode(self.text(line)).replace(sys.ps1, "").replace(sys.ps2, "")
                if self.inContinue and not buf[:index-len(sys.ps2)].strip():
                    QextScintillaCompat.keyPressEvent(self, ev)
                elif self.racEnabled:
                    self.dbs.remoteCompletion(buf)
        elif key == Qt.Key_Left:
            line, col = self.getCursorPosition()
            if self.text(line).startsWith(sys.ps1):
                if col > len(sys.ps1):
                    self.moveCursorLeft()
            elif self.text(line).startsWith(sys.ps2):
                if col > len(sys.ps2):
                    self.moveCursorLeft()
            elif col > 0:
                self.moveCursorLeft()
        elif key == Qt.Key_Right:
            self.moveCursorRight()
        elif key == Qt.Key_Home:
            if self.isAutoCompletionActive():
                QextScintillaCompat.keyPressEvent(self, ev)
            else:
                line, col = self.getCursorPosition()
                if self.text(line).startsWith(sys.ps1):
                    col = len(sys.ps1)
                elif self.text(line).startsWith(sys.ps2):
                    col = len(sys.ps2)
                else:
                    col = 0
                self.setCursorPosition(line, col)
        elif key == Qt.Key_End:
            if self.isAutoCompletionActive():
                QextScintillaCompat.keyPressEvent(self, ev)
            else:
                self.moveCursorToEOL()
        elif key == Qt.Key_Up:
            if self.isAutoCompletionActive():
                QextScintillaCompat.keyPressEvent(self, ev)
            else:
                if self.histidx < 0:
                    self.histidx = len(self.history)
    
                if self.histidx > 0:
                    self.histidx = self.histidx - 1
                    self.useHistory()
        elif key == Qt.Key_Down:
            if self.isAutoCompletionActive():
                QextScintillaCompat.keyPressEvent(self, ev)
            else:
                if self.histidx >= 0 and self.histidx < len(self.history):
                    self.histidx = self.histidx + 1
                    self.useHistory()
        elif key == Qt.Key_PageUp:
            if self.isAutoCompletionActive():
                QextScintillaCompat.keyPressEvent(self, ev)
        elif key == Qt.Key_PageDown:
            if self.isAutoCompletionActive():
                QextScintillaCompat.keyPressEvent(self, ev)
        else:
            ev.ignore()

    def useHistory(self):
        """
        Private method to display a command from the history.
        """
        if self.histidx < len(self.history):
            cmd = self.history[self.histidx]
        else:
            cmd = QString()

        self.setCursorPosition(self.prline,self.prcol)
        self.setSelection(self.prline,self.prcol,\
                          self.prline,self.lineLength(self.prline))
        self.removeSelectedText()
        self.insertText(cmd)

    def focusNextPrevChild(self,next):
        """
        Reimplemented to stop Tab moving to the next window.
        
        While the user is entering a multi-line command, the movement to
        the next window by the Tab key being pressed is suppressed.
        
        @param next next window
        @return flag indicating the movement
        """
        if next and self.inContinue:
            return 0

        return QextScintillaCompat.focusNextPrevChild(self,next)

    def contextMenuEvent(self,ev):
        """
        Reimplemented to show our own context menu.
        
        @param ev context menu event (QContextMenuEvent)
        """
        self.menu.popup(ev.globalPos())
        ev.accept()
        
    def handleClear(self):
        """
        Private slot to handle the 'clear' context menu entry.
        """
        # Display the banner.
        self.getBanner()
        
    def handleResetAndClear(self):
        """
        Private slot to handle the 'reset and clear' context menu entry.
        """
        self.handleReset()
        self.handleClear()
        
    def handleReset(self):
        """
        Private slot to handle the 'reset' context menu entry.
        """
        self.dbs.startClient(0)
        
    def handleStartPython(self):
        """
        Private slot to handle the 'Start Python' context menu entry.
        """
        self.dbs.startClient(0, 'Python')
        
    def handleStartRuby(self):
        """
        Private slot to handle the 'Start Ruby' context menu entry.
        """
        self.dbs.startClient(0, 'Ruby')
        
    def handlePreferencesChanged(self):
        """
        Public slot to handle the preferencesChanged signal.
        """
        if self.lexer is not None:
            # read the lexer settings
            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)

        # set margin 0 configuration
        self.setMargin0()
        self.setTextDisplay()
        
        # set the autocompletion and calltips function
        self.setAutoCompletion()
        self.setCallTips()
        
    def showCompletions(self, completions, text):
        """
        Private method to display the possible completions.
        """
        if len(completions) == 0:
            return
            
        if len(completions) > 1:
            completions.sort()
            comps = QStringList()
            for comp in completions:
                comps.append(comp)
            self.showUserList(1, comps)
            self.completionText = text
        else:
            txt = completions[0]
            if text != "":
                txt = txt.replace(text, "")
            self.insertText(txt)
            self.completionText = ""

    def handleCompletionList(self, txt, id):
        """
        Private slot to handle the selection from the completion list.
        
        @param txt the selected text (QString)
        @param id the ID of the user list (should be 1) (integer)
        """
        if id == 1:
            txt = unicode(txt)
            if self.completionText != "":
                txt = txt.replace(self.completionText, "")
            self.insertText(txt)
            self.completionText = ""
    
    #################################################################
    ## Drag and Drop Support
    #################################################################
    
    def eventFilter(self, obj, evt):
        """
        Protected method to handle some event on behalve of our componentes.
        
        @param obj destination object of the event (QObject)
        @param evt the event to be handled (QEvent)
        @return flag to indicate, if the event was handled
        """
        used = 1
        evtType = evt.type()
        if evtType == QEvent.DragEnter:
            self.dragEnterEvent(evt)
        elif evtType == QEvent.DragMove:
            self.dragMoveEvent(evt)
        elif evtType == QEvent.DragLeave:
            self.dragLeaveEvent(evt)
        elif evtType == QEvent.Drop:
            self.dropEvent(evt)
        else:
            used = QextScintillaCompat.eventFilter(self, obj, evt)
        
        return used
        
    def dragEnterEvent(self, event):
        """
        Protected method to handle the drag enter event.
        
        @param event the drag enter event (QDragEnterEvent)
        """
        if QUriDrag.canDecode(event) or \
           QTextDrag.canDecode(event):
            self.inDragDrop = 1
            event.accept(1)
        else:
            QextScintillaCompat.dragEnterEvent(self, event)
        
    def dragMoveEvent(self, event):
        """
        Protected method to handle the drag move event.
        
        @param event the drag move event (QDragMoveEvent)
        """
        if self.inDragDrop:
            event.accept(1)
        else:
            QextScintillaCompat.dragMoveEvent(self, event)
        
    def dragLeaveEvent(self, event):
        """
        Protected method to handle the drag leave event.
        
        @param event the drag leave event (QDragLeaveEvent)
        """
        if self.inDragDrop:
            self.inDragDrop = 0
            event.accept(1)
        else:
            QextScintillaCompat.dragLeaveEvent(self, event)
        
    def dropEvent(self, event):
        """
        Protected method to handle the drop event.
        
        @param event the drop event (QDropEvent)
        """
        if QUriDrag.canDecode(event):
            flist = QStringList()
            ok = QUriDrag.decodeLocalFiles(event, flist)
            if ok:
                event.acceptAction()
                for fname in flist:
                    if not QFileInfo(fname).isDir():
                        self.vm.handlePythonFile(str(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
        elif QTextDrag.canDecode(event):
            s = QString()
            ok = QTextDrag.decode(event, s)
            if ok and not s.isEmpty():
                event.acceptAction()
                if s.contains("\n") > 1 or \
                   (s.contains("\n") == 1 and not s.endsWith("\n")):
                    KQMessageBox.warning(None,
                        self.trUtf8("Shell Error"),
                        self.trUtf8("""Only single lines may be pasted into the shell."""),
                        self.trUtf8("&OK"),
                        QString.null,
                        QString.null,
                        0, -1)
                else:
                    self.insertTextAtEnd(unicode(s).replace("\n", ""))
            del s
        else:
            QextScintillaCompat.dropEvent(self, event)
        self.inDragDrop = 0
    
    def focusInEvent(self, event):
        """
        Public method called when the shell receives focus.
        
        @param event the event object (QFocusEvent)
        """
        try:
            self.vm.editorActGrp.setEnabled(0)
        except AttributeError:
            pass
        self.setCaretWidth(self.caretWidth)
        QextScintillaCompat.focusInEvent(self, event)
    
    def focusOutEvent(self, event):
        """
        Public method called when the shell loses focus.
        
        @param event the event object (QFocusEvent)
        """
        self.setCaretWidth(0)
        QextScintillaCompat.focusOutEvent(self, event)
    
    def insert(self, text):
        """
        Public slot to insert text at the current cursor position.
        
        The cursor is advanced to the end of the inserted text.
        
        @param text text to be inserted (string or QString)
        """
        txt = QString(text)
        l = txt.length()
        line, col = self.getCursorPosition()
        self.insertAt(txt, line, col)
        self.setCursorPosition(\
            line + txt.contains(self.linesepRegExp),
            col  + l)
